This documents the effort to make dmapd and libdmapsharing more memory
efficient. This document describes work done in both libdmapsharing and
dmapd, because dmapd is the primary application of libdmapsharing's
I found that dmapd used a significant amount of heap space while trying to
port dmapd to OpenWRT on a WRT160NL router with 32 Megabytes of RAM. A
2,419-song library caused dmapd on x86_64 to use 7,413,760 bytes of heap
space after reading the library into memory. Furthermore, connecting to
dmapd using a DAAP client and receiving a list of songs caused heap usage
to increase to over 10 Megabytes. Note that this memory usage might be
smaller on the WRT160NL due to its 32-bit architecture, but it is still
significant in size.
After attempting to optimize memory use, I measured the following on
OpenWrt / WRT160NL (*** BUT WITH NO METADATA READ! ***):
Heap, after music is loaded: 0xf5000 (1,003,520)
Heap, after client received song list: 0x926000 (9,592,832)
SOME METADATA (MP3 ONLY):
Heap, after music is loaded: 0x2d3000 (2,961,408)
Heap, after client received song list: 0xafa000 (11,509,760)
I use the following tools to troubleshoot memory issues:
grep heap /proc/PID/maps (current heap size)
valgrind / memcheck (memory leaks)
valgrind / massif (max heap size and memory over time)
objdump -t OBJECT-FILE | grep -e "\.bss" -e "\.data" | sort -k5
| c++filt | tail
I have made the following changes in order to make dmapd more memory
1. Replace the hash table-based DMAPDb implementation with a Berkeley
Database implementation. Currently measuring the memory use compared to
the in-memory database.
2. Fixed several memory leaks after analyzing with valgrind.
Heap without client connection: 3,940,352 bytes
3. Implement database cache so that dmapd does not need to use GStreamer
to read media metadata each time it is run.
Heap without client connection using DB cache: 3,346,432 bytes
4. Replace the DMAPDb in the DMAPContainerRecord implementation with a
GSList of ID's.
Heap without client connection using DB cache: 4,759,552 bytes
5. Implement "stringletons" and fix errors in 4) above.
Heap without client connection using DB cache: 2,854,912 bytes
Heap without client connection: 3,518,464 bytes
Heap after client connection and song list: 6,864,896 bytes
Max heap usages (massif): 8,945,760 bytes
6. Modify libdmapsharing so that it responds to "/1/items" requests in
chunks, avoiding the need to build the entire response in memory.
The effectiveness of this can be measured by observing the
maximum heap usage after a client has asked for a full media
list. Previous to this change, dmapd would use 7,811,072 bytes
of heap to describe a 2,232-song library. After this change,
dmapd's usage was 3,665,920 bytes, a very small increase over
the memory used by dmapd immediately after starting.
7. Write a disk-based database for dmapd.
In order to minimize the memory used by a newly started dmapd,
I wrote a disk-based DMAPDb backend. This backend stores all
database data in files and avoids storing the entire database
in memory. To measure the effectiveness, I measured the memory
usage of dmapd after loading a 3,276-photograph library (note
that thumbnails are especially problematic). The GHashTable-based
database used 35,885,056 bytes of heap space. The disk-based
database used 4,763,648 bytes.
8. Decompressing multiple scan JPEG's.
VIPS must decompress multiple scan JPEG's fully in memory due
to the way libjpeg works. Either 1) use embedded EXIF thumbnail
or 2) skip.