diff --git a/TIASshot.Tests/ChartDetectorTests.cs b/TIASshot.Tests/ChartDetectorTests.cs index 99eb72a..9281b0f 100644 --- a/TIASshot.Tests/ChartDetectorTests.cs +++ b/TIASshot.Tests/ChartDetectorTests.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using NUnit.Framework; using OpenCvSharp; @@ -206,10 +208,59 @@ } } + /// + /// 固定判定成立後の検出(ホモグラフィ計算・ワープ)で生成する中間 Mat を呼び出し内で解放し, + /// ネイティブメモリを GC ファイナライザ任せで滞留させないこと + /// + /// GC を止めた状態(NoGCRegion)でマスク生成まで進む検出を繰り返し,プライベートメモリの増分を見る. + /// 中間 Mat(とくに 810 x 1545 x 3 のワープ画像 約 3.75MB)が未解放だと呼び出し回数に比例して増える. + /// 返されたマスクはテスト側で解放する.Windows ヒープは大きなブロックを解放時に OS へ返すため, + /// 解放されていれば増分はほぼ 0 になる. + /// + [Test] + public void DetectChart_固定判定成立後の検出を繰り返す_中間Matのネイティブメモリを滞留させない() { + const int Iterations = 10; + const long LeakThresholdBytes = 20L * 1024 * 1024; // 未解放なら約 37MB 増える + var detector = new ChartDetector(RecordMessage); + using (var img = CreateChartImage(Marker40X, Marker40Y, Marker41X, Marker41Y)) { + // Arrange: 固定判定を成立させ,ネイティブ側の初回確保(検出器の内部バッファ等)も済ませる + DisposeMasks(DetectUntilStable(detector, img)); + for (int i = 0; i < 3; i++) DisposeMasks(detector.DetectChart(img)); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + Assert.That(GC.TryStartNoGCRegion(64L * 1024 * 1024), Is.True, "NoGCRegion を開始できること(前提)"); + long before, after; + try { + before = GetPrivateBytes(); + // Act + for (int i = 0; i < Iterations; i++) { + var masks = detector.DetectChart(img); + Assert.That(masks, Has.Count.EqualTo(ExpectedMaskCount), "毎回マスク生成まで進むこと(前提)"); + DisposeMasks(masks); + } + after = GetPrivateBytes(); + } finally { + if (System.Runtime.GCSettings.LatencyMode == System.Runtime.GCLatencyMode.NoGCRegion) GC.EndNoGCRegion(); + } + + // Assert + Assert.That(after - before, Is.LessThan(LeakThresholdBytes), + $"{Iterations} 回の検出でプライベートメモリが {(after - before) / 1024 / 1024}MB 増えた(中間 Mat の未解放)"); + } + } + // --------------------------------------------------------------- // ヘルパー // --------------------------------------------------------------- + private static long GetPrivateBytes() { + using (var process = Process.GetCurrentProcess()) { + return process.PrivateMemorySize64; + } + } + private void RecordMessage(string message) { _messages.Add(message); }