static async Task Main(string[] args) { //Console.WriteLine(System.Reflection.Assembly.GetEntryAssembly().Location); //var aaa = new MergedEnumerator(0, 2, 0x0000FFFF00000000); //aaa.Enumerator.Read(); var config = Config.Instance; var hashfile = new HashFile(); var db = new DBHandler(hashfile); var sw = Stopwatch.StartNew(); HashSet <long> NewHash = null; long NewLastUpdate = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); long MinDownloadedAt = await db.Min_downloaded_at().ConfigureAwait(false); Directory.CreateDirectory(config.hash.TempDir); //前回正常に終了せず残ったファイルを消す hashfile.DeleteNewerHash(true); hashfile.DeleteAllHash(true); foreach (var filePath in Directory.EnumerateFiles(config.hash.TempDir, Path.GetFileName(SplitQuickSort.SortingFilePath("*"))).ToArray()) { File.Delete(filePath); } if (MinDownloadedAt < hashfile.LastUpdate) { //前回の更新以降のハッシュを読む(つもり) Console.WriteLine("Loading New hash."); //とりあえず60秒前のハッシュから取得する NewHash = await db.NewerMediaHash(NewLastUpdate, hashfile.LastUpdate - 60).ConfigureAwait(false); if (NewHash == null) { Console.WriteLine("New hash load failed."); Environment.Exit(1); } Console.WriteLine("{0} New hash", NewHash.Count); } else { //前回のハッシュ追加から時間が経ち過ぎたりしたらハッシュ取得を全部やり直す hashfile.DeleteAllHash(); hashfile.DeleteNewerHash(); } //全ハッシュの取得はファイルが残っていなければやる if (HashFile.AllHashFilePath == null) { Console.WriteLine("Loading All hash."); //NewHashの中身はAllHashにも含まれることになるので消してしまう hashfile.DeleteNewerHash(); long Count = await db.AllMediaHash(NewLastUpdate).ConfigureAwait(false); if (Count < 0) { Console.WriteLine("Hash load failed."); Environment.Exit(1); } Console.WriteLine("{0} Hash loaded.", Count); hashfile.LastHashCount = Count; } sw.Stop(); Console.WriteLine("Hash Load: {0}ms", sw.ElapsedMilliseconds); sw.Restart(); MediaHashSorter media = new MediaHashSorter(NewHash, db, config.hash.MaxHammingDistance, //MergeSorterBaseの仕様上SortMaskで最上位bitだけ0にされるとまずいので制限 Math.Min(config.hash.ExtraBlocks, 32 - config.hash.MaxHammingDistance)); await media.Proceed().ConfigureAwait(false); sw.Stop(); Console.WriteLine("Multiple Sort, Store: {0}ms", sw.ElapsedMilliseconds); hashfile.LastUpdate = NewLastUpdate; }
const int bitcount = sizeof(long) * 8; //longのbit数 /// <summary>複合ソート法のマスク1個分をやる</summary> /// <param name="Index">nCxの組合せの何個目か(0~(nCx)-1)</param> async Task <Task> MultipleSortUnit(int Index) { int[] BaseBlocks = Combi[Index]; int StartBlock = BaseBlocks.Last(); long SortMask = UnMask(BaseBlocks, Combi.Choice); var QuickSortSW = Stopwatch.StartNew(); //適当なサイズに切ってそれぞれをクイックソート int SortedFileCount = await SplitQuickSort.QuickSortAll(Index, SortMask).ConfigureAwait(false); //↓ベンチマーク用 sortのファイル数を入れてやる //int SortedFileCount = 52; GC.Collect(GC.MaxGeneration, GCCollectionMode.Default, true, false); GC.WaitForPendingFinalizers(); GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(); QuickSortSW.Stop(); Console.WriteLine("{0}\tFile Sort\t{1}ms ", Index, QuickSortSW.ElapsedMilliseconds); long[] UnMasks = Enumerable.Range(0, Combi.Choice).Select(i => UnMask(i, Combi.Choice)).ToArray(); var MultipleSortBlock = new ActionBlock <AddOnlyList <long> >((BlockList) => { var Block = BlockList.InnerArray; var NewHash = this.NewHash; int LocalPairCount = 0; //見つけた組合せの数を数える int BlockIndex = 0; int CurrentBlockLength; //(要素数,実際の要素,…),0 という配列を読み込んでいく for (; (CurrentBlockLength = (int)Block[BlockIndex]) > 0; BlockIndex += CurrentBlockLength + 1) { //「実際の要素」1セット分を取り出す var SortedSpan = Block.AsSpan(BlockIndex + 1, CurrentBlockLength); //新しい値を含まないやつは省略 if (NewHash != null) { for (int i = 0; i < SortedSpan.Length; i++) { if (NewHash.Contains(SortedSpan[i])) { goto HasNewHash; } } continue; } HasNewHash: for (int i = 0; i < SortedSpan.Length; i++) { long Sorted_i = SortedSpan[i]; bool NeedInsert_i = NewHash?.Contains(Sorted_i) ?? true; //long maskedhash_i = Sorted_i & SortMask; for (int j = i + 1; j < SortedSpan.Length; j++) { long Sorted_j = SortedSpan[j]; //重複排除を行う(同じハッシュ同士を比較しないだけ) if (Sorted_i == Sorted_j) { continue; } //if (maskedhash_i != (Sorted[j] & FullMask)) { break; } //これはSortedFileReaderがやってくれる //間違いなくすでにDBに入っているペアは処理しない if ((NeedInsert_i || NewHash.Contains(Sorted_j)) //NewHashがnullなら後者は処理されないからセーフ //ブロックソートで一致した組のハミング距離を測る && BitOperations.PopCount((ulong)(Sorted_i ^ Sorted_j)) <= MaxHammingDistance) { //一致したペアが見つかる最初の組合せを調べる int matchblockindex = 0; int x; for (x = 0; x < UnMasks.Length && x < StartBlock && matchblockindex < BaseBlocks.Length; x++) { if (BaseBlocks.Contains(x)) { if (x < BaseBlocks[matchblockindex]) { break; } matchblockindex++; } else { if ((Sorted_i & UnMasks[x]) == (Sorted_j & UnMasks[x])) { if (x < BaseBlocks[matchblockindex]) { break; } matchblockindex++; } } } //最初の組合せだったときだけ入れる if (x == StartBlock) { LocalPairCount++; PairBatchBlock.Post(new HashPair(Sorted_i, Sorted_j)); } } } } } BlockList.Dispose(); Interlocked.Add(ref PairCount, LocalPairCount); }, new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount, BoundedCapacity = Environment.ProcessorCount << 1, SingleProducerConstrained = true }); var MultipleSortSW = Stopwatch.StartNew(); //クイックソートしたファイル群をマージソートしながら読み込む using (var Reader = new MergeSortReader(Index, SortedFileCount, SortMask)) { int PostponeCount = 1 + config.hash.MergeSortPostponePairCount / DBHandler.StoreMediaPairsUnit; var GCStopWatch = Stopwatch.StartNew(); AddOnlyList <long> Sorted; while ((Sorted = Reader.ReadBlocks()) != null) { await MultipleSortBlock.SendAsync(Sorted).ConfigureAwait(false); //DBにハッシュのペアを入れる処理が詰まったら休んだりGCしたりする while (PostponeCount < PairStoreBlock.InputCount) { if (60000 <= GCStopWatch.ElapsedMilliseconds) { GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(); GCStopWatch.Restart(); } await Task.Delay(1000).ConfigureAwait(false); } } } //余りをDBに入れるついでにここで制御を戻してしまう MultipleSortBlock.Complete(); return(MultipleSortBlock.Completion .ContinueWith((_) => { MultipleSortSW.Stop(); Console.WriteLine("{0}\tMerge+Comp\t{1}ms", Index, MultipleSortSW.ElapsedMilliseconds); })); }