3

以下の新しいコードで編集

私はマルチスレッドについては比較的初心者ですが、目標を達成し、それをすばやく実行して新しいことを学ぶために、マルチスレッド アプリを使用することにしました。

目標: ファイルから大量の文字列を解析し、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");
}
4

2 に答える 2

2

いくつかの考え:

  • 最大同時操作を非常に高く設定しても、あまり効果がありません。コアが 2 つある場合は、2 つを超えることはほとんどありません
  • NSManagedObjectContextすべてのプロセスで同じものを使用しているように見えます。これは良くない
  • 最大同時操作数が100であると仮定します。ボトルネックはメイン スレッドであり、すべての操作のラベルを更新しようとしています。1 レコードごとではなく、nレコードごとにメイン スレッドを更新してみてください
  • Core Data を正しく使用している場合は、コンテキストをロックする必要はありません...つまり、スレッドごとに異なるコンテキストを使用することを意味します
  • コンテキストを保存していないようですか?
  • 操作のバッチ処理は、パフォーマンスを向上させる良い方法です...ただし、前のポイントを参照してください
  • ご指摘のとおり、GCD 操作の作成にはオーバーヘッドがあります。単語ごとに新しいものを作成することは、おそらく最適ではありません。新しいプロセスを作成するオーバーヘッドと、並列化の利点とのバランスを取る必要があります

つまり、GCD のようなものを使用しても、スレッド化は困難です。

于 2012-08-20T12:37:24.930 に答える
0

測定とプロファイリングを行わずに進むのは難しいですが、疑わしいと思われるのは、これまでに保存された単語の完全な辞書を各単語を保存することです。したがって、1 回の保存あたりのデータ量は、どんどん大きくなります。

// the dictionary at this point contains all words saved so far
// which each contains a full dictionary
[toSaveWord addDictionariesObject:dictionary];

// add each time so it gets bigger each time
[dictionary addWordsObject:toSaveWord];

したがって、保存するたびに、より多くのデータが保存されます。単語ごとにすべての単語の辞書を保存するのはなぜですか?

その他の考え:

  • 決して使用しない coreDataWords を構築するのはなぜですか?
  • 作業ブロック全体を同期しているので、同時実行性が得られているのだろうか。

試すこと:

  • 作成中の辞書に加えて、toSaveWord の辞書をコメント アウトして、もう一度試してください。それがデータ/データ構造または DB/coreData であるかどうかを確認してください。
  • 最初に実行するだけでなく、シリアル バージョンも作成して、実際に同時実行のメリットが得られるかどうかを確認します。
于 2012-08-20T12:01:12.113 に答える