Improve library duplicates detection performance
Parallelize by chunking the manga list and using sub workers
This commit is contained in:
@@ -6,21 +6,66 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { findDuplicatesByTitle } from '@/modules/library/util/LibraryDuplicates.util.ts';
|
||||
import {
|
||||
findDuplicatesByTitle,
|
||||
findDuplicatesByTitleAndAlternativeTitles,
|
||||
} from '@/modules/library/util/LibraryDuplicates.util.ts';
|
||||
import { LibraryDuplicatesWorkerInput } from '@/modules/library/Library.types.ts';
|
||||
LibraryDuplicatesDescriptionWorkerInput,
|
||||
LibraryDuplicatesWorkerInput,
|
||||
TMangaDuplicate,
|
||||
TMangaDuplicates,
|
||||
} from '@/modules/library/Library.types.ts';
|
||||
import { Queue } from '@/lib/Queue.ts';
|
||||
import { ControlledPromise } from '@/lib/ControlledPromise.ts';
|
||||
import { enhancedCleanup } from '@/util/Strings.ts';
|
||||
|
||||
const queue = new Queue((navigator.hardwareConcurrency ?? 5) - 1);
|
||||
const MANGAS_PER_CHUNK = 200;
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
self.onmessage = async (event: MessageEvent<LibraryDuplicatesWorkerInput>) => {
|
||||
const { mangas } = event.data;
|
||||
const { checkAlternativeTitles } = event.data;
|
||||
const { mangas, checkAlternativeTitles } = event.data;
|
||||
|
||||
if (checkAlternativeTitles) {
|
||||
postMessage(findDuplicatesByTitleAndAlternativeTitles(mangas));
|
||||
if (!checkAlternativeTitles) {
|
||||
postMessage(findDuplicatesByTitle(mangas));
|
||||
return;
|
||||
}
|
||||
|
||||
postMessage(findDuplicatesByTitle(mangas));
|
||||
const chunkPromises: Promise<TMangaDuplicates<TMangaDuplicate>>[] = [];
|
||||
for (let chunkStart = 0; chunkStart < mangas.length; chunkStart += MANGAS_PER_CHUNK) {
|
||||
chunkPromises.push(
|
||||
queue.enqueue(chunkStart.toString(), () => {
|
||||
const workerPromise = new ControlledPromise<TMangaDuplicates<TMangaDuplicate>>();
|
||||
|
||||
const worker = new Worker(new URL('LibraryDuplicatesDescriptionWorker.ts', import.meta.url), {
|
||||
type: 'module',
|
||||
});
|
||||
|
||||
worker.onmessage = (subWorkerEvent: MessageEvent<TMangaDuplicates<TMangaDuplicate>>) =>
|
||||
workerPromise.resolve(subWorkerEvent.data);
|
||||
|
||||
worker.postMessage({
|
||||
mangas,
|
||||
mangasToCheck: mangas.slice(chunkStart, chunkStart + MANGAS_PER_CHUNK),
|
||||
} satisfies LibraryDuplicatesDescriptionWorkerInput);
|
||||
|
||||
return workerPromise.promise;
|
||||
}).promise,
|
||||
);
|
||||
}
|
||||
|
||||
const chunkedResults = await Promise.all(chunkPromises);
|
||||
const mergedResult: TMangaDuplicates<TMangaDuplicate> = {};
|
||||
|
||||
const cleanedUpTitleToOriginalTitle: Record<string, string> = {};
|
||||
chunkedResults.forEach((chunkedResult) =>
|
||||
Object.entries(chunkedResult).forEach(([title, duplicates]) => {
|
||||
const cleanedTitle = enhancedCleanup(title);
|
||||
cleanedUpTitleToOriginalTitle[cleanedTitle] ??= title;
|
||||
const originalTitle = cleanedUpTitleToOriginalTitle[cleanedTitle];
|
||||
|
||||
// ignore duplicated results for a title from other chunked results
|
||||
mergedResult[originalTitle] ??= duplicates;
|
||||
}),
|
||||
);
|
||||
|
||||
postMessage(mergedResult);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user