Merge lp:~mhr3/unity-lens-music/memory-optimizations into lp:unity-lens-music

Proposed by Michal Hruby
Status: Merged
Approved by: Michal Hruby
Approved revision: 78
Merged at revision: 77
Proposed branch: lp:~mhr3/unity-lens-music/memory-optimizations
Merge into: lp:unity-lens-music
Diff against target: 425 lines (+232/-117)
4 files modified
configure.ac (+0/-1)
src/Makefile.am (+0/-1)
src/rhythmbox-collection.vala (+231/-114)
src/track.vala (+1/-1)
To merge this branch: bzr merge lp:~mhr3/unity-lens-music/memory-optimizations
Reviewer Review Type Date Requested Status
Gord Allott (community) Approve
Review via email: mp+99682@code.launchpad.net

Commit message

Optimize memory allocation

Description of the change

There were reports from multiple users with huge music collections that the new music lens is using huge amount of memory. After profiling this turned out to be mostly used by the xml parser which was keeping the entire parsed file in memory and since we constructed the song model right after the parsing, this caused fragmentation of memory, so even though the xml parser freed everything after the model was built, the mem usage of the process remained very high.

Replace the xml parser from libxml-2.0 with simple GMarkupParser that is able to process the xml by chunks and therefore doesn't have these kind of problems.

To post a comment you must log in.
Revision history for this message
Ted Gould (ted) wrote :

Using this branch lowered my memory usage from OVER ONE HUNDRED MEGABYTES to LESS THAN THIRTY. I am also now more attractive, smarter and I have more friends.

Revision history for this message
Gord Allott (gordallott) wrote :

clever ;) +1

review: Approve
Revision history for this message
Unity Merger (unity-merger) wrote :

No commit message specified.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'configure.ac'
--- configure.ac 2012-03-23 12:09:52 +0000
+++ configure.ac 2012-03-28 07:32:18 +0000
@@ -63,7 +63,6 @@
63 dee-1.0 >= 1.0.763 dee-1.0 >= 1.0.7
64 sqlite3 >= 3.7.764 sqlite3 >= 3.7.7
65 gee-1.065 gee-1.0
66 libxml-2.0
67 json-glib-1.066 json-glib-1.0
68 unity >= 4.99.0)67 unity >= 4.99.0)
6968
7069
=== modified file 'src/Makefile.am'
--- src/Makefile.am 2012-02-01 10:07:59 +0000
+++ src/Makefile.am 2012-03-28 07:32:18 +0000
@@ -28,7 +28,6 @@
28 --pkg gio-2.0 \28 --pkg gio-2.0 \
29 --pkg gio-unix-2.0 \29 --pkg gio-unix-2.0 \
30 --pkg glib-2.0 \30 --pkg glib-2.0 \
31 --pkg libxml-2.0 \
32 $(MAINTAINER_VALAFLAGS)31 $(MAINTAINER_VALAFLAGS)
3332
34unity_music_daemon_LDADD = \33unity_music_daemon_LDADD = \
3534
=== modified file 'src/rhythmbox-collection.vala'
--- src/rhythmbox-collection.vala 2012-03-22 09:13:52 +0000
+++ src/rhythmbox-collection.vala 2012-03-28 07:32:18 +0000
@@ -33,36 +33,179 @@
33 ARTWORK,33 ARTWORK,
34 MIMETYPE,34 MIMETYPE,
35 GENRE,35 GENRE,
36 ALBUM_ARTIST,
36 TRACK_NUMBER,37 TRACK_NUMBER,
37 ALBUM_ARTIST,
38 YEAR,38 YEAR,
39 PLAY_COUNT39 PLAY_COUNT,
40
41 N_COLUMNS
40 }42 }
4143
42 class RhythmboxCollection : Object44 class RhythmboxCollection : Object
43 {45 {
4446
45 GenericArray<Track> tracks = new GenericArray<Track> ();
46 SequenceModel all_tracks;47 SequenceModel all_tracks;
47 FilterModel tracks_by_play_count;48 FilterModel tracks_by_play_count;
4849
50 HashTable<unowned string, Variant> variant_store;
51 HashTable<int, Variant> int_variant_store;
52 Variant row_buffer[11];
53
49 Analyzer analyzer;54 Analyzer analyzer;
50 Index index;55 Index index;
51 ICUTermFilter ascii_filter;56 ICUTermFilter ascii_filter;
5257
53 string media_art_dir;58 string media_art_dir;
5459
55 // contains genre maps60 class XmlParser: Object
56 Genre genre = new Genre ();61 {
62 const MarkupParser parser =
63 {
64 start_tag,
65 end_tag,
66 process_text,
67 null,
68 null
69 };
70
71 // contains genre maps
72 Genre genre = new Genre ();
73
74 MarkupParseContext context;
75 bool is_rhythmdb_xml = false;
76
77 construct
78 {
79 context = new MarkupParseContext (parser, 0, this, null);
80 }
81
82 public bool parse (string content, size_t len) throws MarkupError
83 {
84 return context.parse (content, (ssize_t) len);
85 }
86
87 bool processing_song;
88 Track current_track;
89 int current_data = -1;
90
91 private void start_tag (MarkupParseContext context, string name,
92 [CCode (array_length = false, array_null_terminated = true)] string[] attr_names, [CCode (array_length = false, array_null_terminated = true)] string[] attr_values)
93 throws MarkupError
94 {
95 if (!processing_song)
96 {
97 switch (name)
98 {
99 case "rhythmdb": is_rhythmdb_xml = true; break;
100 case "entry":
101 bool is_song = false;
102 for (int i = 0; attr_names[i] != null; i++)
103 {
104 if (attr_names[i] == "type" && attr_values[i] == "song")
105 is_song = true;
106 }
107 if (!is_song) return;
108 processing_song = true;
109 current_track = new Track ();
110 break;
111 }
112 }
113 else
114 {
115 switch (name)
116 {
117 case "location": current_data = Columns.URI; break;
118 case "title": current_data = Columns.TITLE; break;
119 case "artist": current_data = Columns.ARTIST; break;
120 case "album": current_data = Columns.ALBUM; break;
121 case "genre": current_data = Columns.GENRE; break;
122 case "track-number": current_data = Columns.TRACK_NUMBER; break;
123 case "play-count": current_data = Columns.PLAY_COUNT; break;
124 case "date": current_data = Columns.YEAR; break;
125 case "media-type": current_data = Columns.MIMETYPE; break;
126 case "album-artist": current_data = Columns.ALBUM_ARTIST; break;
127 default: current_data = -1; break;
128 }
129 }
130 }
131
132 public signal void track_info_ready (Track track);
133
134 private void end_tag (MarkupParseContext content, string name)
135 throws MarkupError
136 {
137 switch (name)
138 {
139 case "location":
140 case "title":
141 case "artist":
142 case "album":
143 case "genre":
144 case "track-number":
145 case "play-count":
146 case "date":
147 case "media-type":
148 case "album-artist":
149 if (current_data >= 0) current_data = -1;
150 break;
151 case "entry":
152 if (processing_song && current_track != null)
153 {
154 track_info_ready (current_track);
155 }
156 processing_song = false;
157 break;
158 }
159 }
160
161 private void process_text (MarkupParseContext context,
162 string text, size_t text_len)
163 throws MarkupError
164 {
165 if (!processing_song || current_data < 0) return;
166 switch (current_data)
167 {
168 case Columns.URI: current_track.uri = text; break;
169 case Columns.TITLE: current_track.title = text; break;
170 case Columns.ARTIST: current_track.artist = text; break;
171 case Columns.ALBUM: current_track.album = text; break;
172 case Columns.ALBUM_ARTIST:
173 current_track.album_artist = text;
174 break;
175 case Columns.GENRE:
176 current_track.genre = genre.get_id_for_genre (text.down ());
177 break;
178 case Columns.MIMETYPE:
179 current_track.mime_type = text;
180 break;
181 case Columns.YEAR:
182 current_track.year = int.parse (text) / 365;
183 break;
184 case Columns.PLAY_COUNT:
185 current_track.play_count = int.parse (text);
186 break;
187 case Columns.TRACK_NUMBER:
188 current_track.track_number = int.parse (text);
189 break;
190 }
191 }
192 }
57193
58 construct194 construct
59 {195 {
196 static_assert (11 == Columns.N_COLUMNS); // sync with row_buffer size
60 media_art_dir = Path.build_filename (197 media_art_dir = Path.build_filename (
61 Environment.get_user_cache_dir (), "media-art");198 Environment.get_user_cache_dir (), "media-art");
62199
200 variant_store = new HashTable<unowned string, Variant> (str_hash,
201 str_equal);
202 int_variant_store = new HashTable<int, Variant> (direct_hash,
203 direct_equal);
63 all_tracks = new SequenceModel ();204 all_tracks = new SequenceModel ();
64 // the columns correspond to the Columns enum205 // the columns correspond to the Columns enum
65 all_tracks.set_schema ("s", "s", "s", "s", "s", "s", "s", "s", "s", "i", "i");206 all_tracks.set_schema ("s", "s", "s", "s", "s", "s", "s", "s", "i", "i", "i");
207 assert (all_tracks.get_schema ().length == Columns.N_COLUMNS);
208
66209
67 var filter = Dee.Filter.new_sort ((row1, row2) =>210 var filter = Dee.Filter.new_sort ((row1, row2) =>
68 {211 {
@@ -137,116 +280,90 @@
137 return null;280 return null;
138 }281 }
139282
283 private Variant cached_variant_for_string (string? input)
284 {
285 unowned string text = input != null ? input : "";
286 Variant? v = variant_store[text];
287 if (v != null) return v;
288
289 v = new Variant.string (text);
290 // key is owned by value... awesome right?
291 variant_store[v.get_string ()] = v;
292 return v;
293 }
294
295 private Variant cached_variant_for_int (int input)
296 {
297 Variant? v = int_variant_store[input];
298 if (v != null) return v;
299
300 v = new Variant.int32 (input);
301 // let's not cache every random integer
302 if (input < 128)
303 int_variant_store[input] = v;
304 return v;
305 }
306
307 private void prepare_row_buffer (Track track)
308 {
309 Variant uri = new Variant.string (track.uri);
310 Variant title = new Variant.string (track.title);
311 Variant artist = cached_variant_for_string (track.artist);
312 Variant album_artist = cached_variant_for_string (track.album_artist);
313 Variant album = cached_variant_for_string (track.album);
314 Variant mime_type = cached_variant_for_string (track.mime_type);
315 Variant artwork = cached_variant_for_string (track.artwork_path);
316 Variant genre = cached_variant_for_string (track.genre);
317 Variant track_number = cached_variant_for_int (track.track_number);
318 Variant year = cached_variant_for_int (track.year);
319 Variant play_count = cached_variant_for_int (track.play_count);
320
321 row_buffer[0] = uri;
322 row_buffer[1] = title;
323 row_buffer[2] = artist;
324 row_buffer[3] = album;
325 row_buffer[4] = artwork;
326 row_buffer[5] = mime_type;
327 row_buffer[6] = genre;
328 row_buffer[7] = album_artist;
329 row_buffer[8] = track_number;
330 row_buffer[9] = year;
331 row_buffer[10] = play_count;
332 }
333
140 public void parse_file (string path)334 public void parse_file (string path)
141 {335 {
142 Xml.Parser.init ();336 var parser = new XmlParser ();
143337 parser.track_info_ready.connect ((track) =>
144 Xml.Doc* doc = Xml.Parser.parse_file (path);338 {
145 if (doc == null) {339 // Get cover art
146 return;340 string albumart = get_albumart (track);
147 }341 if (albumart != null)
148342 track.artwork_path = albumart;
149 Xml.Node* root = doc->get_root_element ();343 else
150 if (root == null) {344 track.artwork_path = "audio-x-generic";
151 delete doc;345
152 return;346 prepare_row_buffer (track);
153 }347 all_tracks.append_row (row_buffer);
154 parse_node (root);348 });
155 delete doc;349
156350 var file = File.new_for_path (path);
157 Xml.Parser.cleanup ();351
158 }352 try
159353 {
160 private void parse_node (Xml.Node* node)354 var stream = file.read (null);
161 {355 uint8 buffer[65536];
162 for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {356
163 if (iter->type != Xml.ElementType.ELEMENT_NODE) {357 size_t bytes_read;
164 continue;358 while ((bytes_read = stream.read (buffer, null)) > 0)
165 }359 {
166 for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {360 parser.parse ((string) buffer, bytes_read);
167 string attr_content = prop->children->content;361 }
168 if (attr_content == "song") {362 }
169 Track track = new Track ();363 catch (Error err)
170 364 {
171 for (Xml.Node* iter_track = iter->children; iter_track != null; 365 warning ("Error while parsing rhythmbox DB: %s", err.message);
172 iter_track = iter_track->next) {366 }
173 if (iter_track->type != Xml.ElementType.ELEMENT_NODE) {
174 continue;
175 }
176 switch (iter_track->name) {
177 case "title":
178 string node_content = iter_track->get_content ();
179 track.title = node_content;
180 break;
181 case "location":
182 string node_content = iter_track->get_content ();
183 track.uri = node_content;
184 break;
185 case "artist":
186 string node_content = iter_track->get_content ();
187 track.artist = node_content;
188 break;
189 case "media-type":
190 string node_content = iter_track->get_content ();
191 track.mime_type = node_content;
192 break;
193 case "album":
194 string node_content = iter_track->get_content ();
195 track.album = node_content;
196 break;
197 case "genre":
198 string node_content = iter_track->get_content ();
199 track.genre = node_content;
200 break;
201 case "track-number":
202 string node_content = iter_track->get_content ();
203 track.track_number = node_content;
204 break;
205 case "album-artist":
206 string node_content = iter_track->get_content ();
207 track.album_artist = node_content;
208 break;
209 case "date":
210 string node_content = iter_track->get_content ();
211 track.year = int.parse (node_content);
212 break;
213 case "play-count":
214 string node_content = iter_track->get_content ();
215 track.play_count = int.parse (node_content);
216 break;
217 }
218 }
219 // append to tracks array
220 tracks.add (track);
221
222 // Get cover art
223 string albumart = get_albumart (track);
224 if (albumart != null)
225 track.artwork_path = albumart;
226 else
227 track.artwork_path = "audio-x-generic";
228
229 // Get genre filter id
230 track.genre = genre.get_id_for_genre(track.genre.down ());
231
232 if (track.year > 0) {
233 track.year /= 365;
234 }
235
236 all_tracks.append (track.uri, track.title,
237 track.artist, track.album,
238 track.artwork_path,
239 track.mime_type,
240 track.genre,
241 track.track_number,
242 track.album_artist,
243 track.year,
244 track.play_count);
245 }
246 // Next track
247 parse_node (iter);
248 }
249 }
250 }367 }
251368
252 public void search (LensSearch search, 369 public void search (LensSearch search,
253370
=== modified file 'src/track.vala'
--- src/track.vala 2012-03-19 18:52:51 +0000
+++ src/track.vala 2012-03-28 07:32:18 +0000
@@ -29,7 +29,7 @@
29 public string album { get; set; }29 public string album { get; set; }
30 public string album_artist { get; set; }30 public string album_artist { get; set; }
31 public string genre { get; set; }31 public string genre { get; set; }
32 public string track_number { get; set; }32 public int track_number { get; set; }
33 public int year { get; set; }33 public int year { get; set; }
34 public int play_count { get; set; }34 public int play_count { get; set; }
35 }35 }

Subscribers

People subscribed via source and target branches

to all changes: