以下の新しいコードで編集
私はマルチスレッドについては比較的初心者ですが、目標を達成し、それをすばやく実行して新しいことを学ぶために、マルチスレッド アプリを使用することにしました。
目標: ファイルから大量の文字列を解析し、CoreData を使用してすべての単語を SQLite データベースに保存します。単語数が約300.000なので巨大です...
これが私のアプローチです。
ステップ 1. ファイル内のすべての単語を解析して、巨大な NSArray に配置します。(手早くできました)
ステップ 2. NSBlockOperation を挿入して NSOperationQueue を作成します。
主な問題は、プロセスが非常に迅速に開始されますが、すぐに遅くなることです。最大同時操作を 100 に設定して NSOperationQueue を使用しています。コア 2 デュオ プロセス (HT なしのデュアル コア) を使用しています。
NSOperationQueue を使用すると、NSOperation の作成に多くのオーバーヘッドがあることがわかりました (キューのディスパッチを停止して、300k NSOperation を作成するだけで約 3 分かかります)。キューのディスパッチを開始すると、CPU は 170% になります。
また、NSOperationQueue を削除して GDC を使用しようとしました (300k ループは瞬時に行われます (コメント行)) が、使用される CPU は 95% しかなく、問題は NSOperations と同じです。すぐにプロセスが遅くなります。
上手に作るコツは?
ここにいくつかのコード(元の質問コード):
- (void)inserdWords:(NSArray *)words insideDictionary:(Dictionary *)dictionary {
NSDate *creationDate = [NSDate date];
__block NSUInteger counter = 0;
NSArray *dictionaryWords = [dictionary.words allObjects];
NSMutableSet *coreDataWords = [NSMutableSet setWithCapacity:words.count];
NSLog(@"Begin Adding Operations");
for (NSString *aWord in words) {
void(^wordParsingBlock)(void) = ^(void) {
@synchronized(dictionary) {
NSManagedObjectContext *context = [(PRDGAppDelegate*)[[NSApplication sharedApplication] delegate] managedObjectContext];
[context lock];
Word *toSaveWord = [NSEntityDescription insertNewObjectForEntityForName:@"Word" inManagedObjectContext:context];
[toSaveWord setCreated:creationDate];
[toSaveWord setText:aWord];
[toSaveWord addDictionariesObject:dictionary];
[coreDataWords addObject:toSaveWord];
[dictionary addWordsObject:toSaveWord];
[context unlock];
counter++;
[self.countLabel performSelectorOnMainThread:@selector(setStringValue:) withObject:[NSString stringWithFormat:@"%lu/%lu", counter, words.count] waitUntilDone:NO];
}
};
[_operationsQueue addOperationWithBlock:wordParsingBlock];
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// dispatch_async(queue, wordParsingBlock);
}
NSLog(@"Operations Added");
}
前もって感謝します。
編集...
Stephen Darlington のおかげでコードを書き直し、問題を解決しました。最も重要なことは、スレッド間で CoreData オブジェクトを共有しないことです...つまり、異なるコンテキストで取得された Core データ オブジェクトを混在させないことです。
これにより、 @synchronized(dictionary) を使用するようになり、スローモーションコードの実行が発生します! MAXTHREAD インスタンスのみを使用して大規模な NSOperation の作成を削除したよりも。(300kではなく2または4 ...大きな違いです)
これで、300k 以上の文字列をわずか 30/40 秒で解析できます。印象的!!まだいくつかの問題があります (スレッドが 1 つだけの場合よりも多くの単語を解析し、スレッドが 1 つ以上の場合はすべての単語を解析するわけではありません ... 理解する必要があります) が、コードは本当に効率的です。次のステップは、OpenCL を使用して GPU に注入することかもしれません :)
ここに新しいコード
- (void)insertWords:(NSArray *)words forLanguage:(NSString *)language {
NSDate *creationDate = [NSDate date];
NSPersistentStoreCoordinator *coordinator = [(PRDGAppDelegate*)[[NSApplication sharedApplication] delegate] persistentStoreCoordinator];
// The number of words to be parsed by the single thread.
NSUInteger wordsPerThread = (NSUInteger)ceil((double)words.count / (double)MAXTHREADS);
NSLog(@"Start Adding Operations");
// Here I minimized the number of threads. Every thread will parse and convert a finite number of words instead of 1 word per thread.
for (NSUInteger threadIdx = 0; threadIdx < MAXTHREADS; threadIdx++) {
// The NSBlockOperation.
void(^threadBlock)(void) = ^(void) {
// A new Context for the current thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coordinator];
// Dictionary now is in accordance with the thread context.
Dictionary *dictionary = [PRDGMainController dictionaryForLanguage:language usingContext:context];
// Stat Variable. Needed to update the UI.
NSTimeInterval beginInterval = [[NSDate date] timeIntervalSince1970];
NSUInteger operationPerInterval = 0;
// The NSOperation Core. It create a CoreDataWord.
for (NSUInteger wordIdx = 0; wordIdx < wordsPerThread && wordsPerThread * threadIdx + wordIdx < words.count; wordIdx++) {
// The String to convert
NSString *aWord = [words objectAtIndex:wordsPerThread * threadIdx + wordIdx];
// Some Exceptions to skip certain words.
if (...) {
continue;
}
// CoreData Conversion.
Word *toSaveWord = [NSEntityDescription insertNewObjectForEntityForName:@"Word" inManagedObjectContext:context];
[toSaveWord setCreated:creationDate];
[toSaveWord setText:aWord];
[toSaveWord addDictionariesObject:dictionary];
operationPerInterval++;
NSTimeInterval endInterval = [[NSDate date] timeIntervalSince1970];
// Update case.
if (endInterval - beginInterval > UPDATE_INTERVAL) {
NSLog(@"Thread %lu Processed %lu words", threadIdx, wordIdx);
// UI Update. It will be updated only by the first queue.
if (threadIdx == 0) {
// UI Update code.
}
beginInterval = endInterval;
operationPerInterval = 0;
}
}
// When the NSOperation goes to finish the CoreData thread context is saved.
[context save:nil];
NSLog(@"Operation %lu finished", threadIdx);
};
// Add the NSBlockOperation to queue.
[_operationsQueue addOperationWithBlock:threadBlock];
}
NSLog(@"Operations Added");
}