Reduce memory usage of adding tracks

Significantly reduce the memory footprint of adding tracks to the
playlist, by coalescing the NSString and NSData objects in the info
dictionaries as they are being loaded in the background, into a common
data set which will then be discarded when the whole job is completed.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
CQTexperiment
Christopher Snowhill 2022-02-16 21:38:43 -08:00
parent d30746bffa
commit 5611d08df1
4 changed files with 106 additions and 0 deletions

View File

@ -164,6 +164,7 @@
838F851C256B4AC400C3E614 /* icon_blank.icns in Resources */ = {isa = PBXBuildFile; fileRef = 838F851B256B4AC400C3E614 /* icon_blank.icns */; }; 838F851C256B4AC400C3E614 /* icon_blank.icns in Resources */ = {isa = PBXBuildFile; fileRef = 838F851B256B4AC400C3E614 /* icon_blank.icns */; };
838F851E256B4E5E00C3E614 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838F851D256B4E5E00C3E614 /* Sparkle.framework */; }; 838F851E256B4E5E00C3E614 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 838F851D256B4E5E00C3E614 /* Sparkle.framework */; };
838F851F256B4E8B00C3E614 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 838F851D256B4E5E00C3E614 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 838F851F256B4E8B00C3E614 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 838F851D256B4E5E00C3E614 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
83988F0E27BE0A5900A0E89A /* RedundantPlaylistDataStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 83988F0D27BE0A5900A0E89A /* RedundantPlaylistDataStore.m */; };
8399D4E21805A55000B503B1 /* XmlContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8399D4E01805A55000B503B1 /* XmlContainer.m */; }; 8399D4E21805A55000B503B1 /* XmlContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8399D4E01805A55000B503B1 /* XmlContainer.m */; };
839DA7CF274A2D4C001B18E5 /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 839DA7CE274A2D4C001B18E5 /* NSDictionary+Merge.m */; }; 839DA7CF274A2D4C001B18E5 /* NSDictionary+Merge.m in Sources */ = {isa = PBXBuildFile; fileRef = 839DA7CE274A2D4C001B18E5 /* NSDictionary+Merge.m */; };
83A360B220E4E81D00192DAB /* Flac.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8303A30C20E4E3D000951EF8 /* Flac.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 83A360B220E4E81D00192DAB /* Flac.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8303A30C20E4E3D000951EF8 /* Flac.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@ -974,6 +975,8 @@
838F84FF25687C5C00C3E614 /* Cog-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Cog-Bridging-Header.h"; sourceTree = "<group>"; }; 838F84FF25687C5C00C3E614 /* Cog-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Cog-Bridging-Header.h"; sourceTree = "<group>"; };
838F851B256B4AC400C3E614 /* icon_blank.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = icon_blank.icns; sourceTree = "<group>"; }; 838F851B256B4AC400C3E614 /* icon_blank.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = icon_blank.icns; sourceTree = "<group>"; };
838F851D256B4E5E00C3E614 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = ThirdParty/Frameworks/Sparkle.framework; sourceTree = "<group>"; }; 838F851D256B4E5E00C3E614 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = ThirdParty/Frameworks/Sparkle.framework; sourceTree = "<group>"; };
83988F0C27BE0A5900A0E89A /* RedundantPlaylistDataStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RedundantPlaylistDataStore.h; sourceTree = "<group>"; };
83988F0D27BE0A5900A0E89A /* RedundantPlaylistDataStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RedundantPlaylistDataStore.m; sourceTree = "<group>"; };
8399D4E01805A55000B503B1 /* XmlContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XmlContainer.m; sourceTree = "<group>"; }; 8399D4E01805A55000B503B1 /* XmlContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XmlContainer.m; sourceTree = "<group>"; };
8399D4E11805A55000B503B1 /* XmlContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XmlContainer.h; sourceTree = "<group>"; }; 8399D4E11805A55000B503B1 /* XmlContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XmlContainer.h; sourceTree = "<group>"; };
839DA7CB274A2D4C001B18E5 /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Merge.h"; sourceTree = "<group>"; }; 839DA7CB274A2D4C001B18E5 /* NSDictionary+Merge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Merge.h"; sourceTree = "<group>"; };
@ -1204,6 +1207,8 @@
177EC01D0B8BC2CF0000BC8C /* TrackingSlider.m */, 177EC01D0B8BC2CF0000BC8C /* TrackingSlider.m */,
8370D739277419D200245CE0 /* SQLiteStore.h */, 8370D739277419D200245CE0 /* SQLiteStore.h */,
8370D73C277419F700245CE0 /* SQLiteStore.m */, 8370D73C277419F700245CE0 /* SQLiteStore.m */,
83988F0C27BE0A5900A0E89A /* RedundantPlaylistDataStore.h */,
83988F0D27BE0A5900A0E89A /* RedundantPlaylistDataStore.m */,
); );
path = Utils; path = Utils;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2511,6 +2516,7 @@
179D03200E0CB2500064A77A /* DirectoryNode.m in Sources */, 179D03200E0CB2500064A77A /* DirectoryNode.m in Sources */,
179D03210E0CB2500064A77A /* FileIconCell.m in Sources */, 179D03210E0CB2500064A77A /* FileIconCell.m in Sources */,
179D03220E0CB2500064A77A /* FileNode.m in Sources */, 179D03220E0CB2500064A77A /* FileNode.m in Sources */,
83988F0E27BE0A5900A0E89A /* RedundantPlaylistDataStore.m in Sources */,
179D03230E0CB2500064A77A /* FileTreeDataSource.m in Sources */, 179D03230E0CB2500064A77A /* FileTreeDataSource.m in Sources */,
179D03240E0CB2500064A77A /* FileTreeController.m in Sources */, 179D03240E0CB2500064A77A /* FileTreeController.m in Sources */,
8370D73D277419F700245CE0 /* SQLiteStore.m in Sources */, 8370D73D277419F700245CE0 /* SQLiteStore.m in Sources */,

View File

@ -34,6 +34,8 @@
#import "NSDictionary+Merge.h" #import "NSDictionary+Merge.h"
#import "RedundantPlaylistDataStore.h"
@implementation PlaylistLoader @implementation PlaylistLoader
- (id)init { - (id)init {
@ -540,9 +542,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSLock *outLock = [[NSLock alloc] init]; NSLock *outLock = [[NSLock alloc] init];
NSMutableArray *outArray = [[NSMutableArray alloc] init]; NSMutableArray *outArray = [[NSMutableArray alloc] init];
RedundantPlaylistDataStore *dataStore = [[RedundantPlaylistDataStore alloc] init];
__block NSLock *weakLock = outLock; __block NSLock *weakLock = outLock;
__block NSMutableArray *weakArray = outArray; __block NSMutableArray *weakArray = outArray;
__block RedundantPlaylistDataStore *weakDataStore = dataStore;
{ {
[load_info_indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *_Nonnull stop) { [load_info_indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *_Nonnull stop) {
@ -566,6 +570,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:entryMetadata]; NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:entryMetadata];
[weakLock lock]; [weakLock lock];
entryInfo = [weakDataStore coalesceEntryInfo:entryInfo];
[weakArray addObject:weakPe]; [weakArray addObject:weakPe];
[weakArray addObject:entryInfo]; [weakArray addObject:entryInfo];
[self setProgressBarStatus:progress]; [self setProgressBarStatus:progress];

View File

@ -0,0 +1,27 @@
//
// RedundantPlaylistDataStore.h
// Cog
//
// Created by Christopher Snowhill on 2/16/22.
//
// This is designed primarily for the PlaylistEntry info loader, to prevent
// memory overrun due to redundant blobs of data being duplicated repeatedly
// until the list is fully loaded. This instance will be discarded after the
// info is loaded, freeing up hopefully way less memory afterward than now.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RedundantPlaylistDataStore : NSObject {
NSMutableArray *stringStore;
NSMutableArray *artStore;
}
- (id)init;
- (NSDictionary *)coalesceEntryInfo:(NSDictionary *)pe;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,68 @@
//
// RedundantPlaylistDataStore.m
// Cog
//
// Created by Christopher Snowhill on 2/16/22.
//
// Coalesce an entryInfo dictionary from tag loading into a common data dictionary, to
// reduce the memory footprint of adding a lot of tracks to the playlist.
#import "RedundantPlaylistDataStore.h"
@implementation RedundantPlaylistDataStore
- (id)init {
self = [super init];
if(self) {
stringStore = [[NSMutableArray alloc] init];
artStore = [[NSMutableArray alloc] init];
}
return self;
}
- (NSString *)coalesceString:(NSString *)in {
if(in == nil) return in;
NSUInteger index = [stringStore indexOfObject:in];
if(index == NSNotFound) {
[stringStore addObject:in];
return in;
} else {
return [stringStore objectAtIndex:index];
}
}
- (NSData *)coalesceArt:(NSData *)in {
if(in == nil) return in;
NSUInteger index = [artStore indexOfObject:in];
if(index == NSNotFound) {
[artStore addObject:in];
return in;
} else {
return [artStore objectAtIndex:index];
}
}
- (NSDictionary *)coalesceEntryInfo:(NSDictionary *)entryInfo {
__block NSMutableDictionary *ret = [[NSMutableDictionary alloc] initWithCapacity:[entryInfo count]];
[entryInfo enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
if([[obj class] isSubclassOfClass:[NSString class]]) {
NSString *stringObj = (NSString *)obj;
[ret setObject:[self coalesceString:stringObj] forKey:key];
} else if([[obj class] isSubclassOfClass:[NSData class]]) {
NSData *dataObj = (NSData *)obj;
[ret setObject:[self coalesceArt:dataObj] forKey:key];
} else {
[ret setObject:obj forKey:key];
}
}];
return [NSDictionary dictionaryWithDictionary:ret];
}
@end