Skip to content

Commit febefa6

Browse files
committed
feat(config): テキスト並列差分の閾値/チャンクサイズをKB単位で設定可能にする
- config.json に TextDiffParallelThresholdKilobytes / TextDiffChunkSizeKilobytes を追加 - FileDiffService の固定値を廃止し、設定値をKB→Bytes変換して適用 - ConfigSettings と関連ユニットテストを更新 - README(日本語/英語)の設定例と性能説明を更新
1 parent 1851acf commit febefa6

7 files changed

Lines changed: 82 additions & 17 deletions

File tree

FolderDiffIL4DotNet.Tests/Models/ConfigSettingsTests.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public void Constructor_DefaultDiskCacheLimits_Are1000And512()
1313

1414
Assert.Equal(1000, config.ILCacheMaxDiskFileCount);
1515
Assert.Equal(512, config.ILCacheMaxDiskMegabytes);
16+
Assert.Equal(512, config.TextDiffParallelThresholdKilobytes);
17+
Assert.Equal(64, config.TextDiffChunkSizeKilobytes);
1618
Assert.False(config.ShouldIgnoreILLinesContainingConfiguredStrings);
1719
Assert.NotNull(config.ILIgnoreLineContainingStrings);
1820
Assert.Empty(config.ILIgnoreLineContainingStrings);
@@ -25,6 +27,8 @@ public void JsonDeserialize_MissingDiskCacheLimits_UsesDefaults()
2527
Assert.NotNull(config);
2628
Assert.Equal(1000, config.ILCacheMaxDiskFileCount);
2729
Assert.Equal(512, config.ILCacheMaxDiskMegabytes);
30+
Assert.Equal(512, config.TextDiffParallelThresholdKilobytes);
31+
Assert.Equal(64, config.TextDiffChunkSizeKilobytes);
2832
Assert.False(config.ShouldIgnoreILLinesContainingConfiguredStrings);
2933
Assert.NotNull(config.ILIgnoreLineContainingStrings);
3034
Assert.Empty(config.ILIgnoreLineContainingStrings);
@@ -40,6 +44,16 @@ public void JsonDeserialize_ExplicitZeroDiskCacheLimits_KeepsZero()
4044
Assert.Equal(0, config.ILCacheMaxDiskMegabytes);
4145
}
4246

47+
[Fact]
48+
public void JsonDeserialize_TextDiffParallelSettings_AreApplied()
49+
{
50+
var json = "{\"TextDiffParallelThresholdKilobytes\":128,\"TextDiffChunkSizeKilobytes\":8}";
51+
var config = JsonSerializer.Deserialize<ConfigSettings>(json);
52+
Assert.NotNull(config);
53+
Assert.Equal(128, config.TextDiffParallelThresholdKilobytes);
54+
Assert.Equal(8, config.TextDiffChunkSizeKilobytes);
55+
}
56+
4357
[Fact]
4458
public void JsonDeserialize_IlIgnoreContainsSettings_AreApplied()
4559
{

Models/ConfigSettings.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ public sealed class ConfigSettings
5959
/// </summary>
6060
public int MaxParallelism { get; set; }
6161

62+
/// <summary>
63+
/// テキスト差分で並列チャンク比較へ切り替えるサイズ閾値(KiB)。既定値は 512。
64+
/// </summary>
65+
public int TextDiffParallelThresholdKilobytes { get; set; } = 512;
66+
67+
/// <summary>
68+
/// テキスト差分の並列チャンク比較で使用するチャンクサイズ(KiB)。既定値は 64。
69+
/// </summary>
70+
public int TextDiffChunkSizeKilobytes { get; set; } = 64;
71+
6272
/// <summary>
6373
/// IL 逆アセンブル結果をキャッシュして再実行時の再逆アセンブルを回避するか
6474
/// </summary>

README.en.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ Place `config.json` next to the executable. Example:
177177
"ILIgnoreLineContainingStrings": [],
178178
"ShouldOutputFileTimestamps": true,
179179
"MaxParallelism": 0,
180+
"TextDiffParallelThresholdKilobytes": 512,
181+
"TextDiffChunkSizeKilobytes": 64,
180182
"EnableILCache": true,
181183
"ILCacheDirectoryAbsolutePath": "",
182184
"ILCacheStatsLogIntervalSeconds": 60,
@@ -199,6 +201,8 @@ Place `config.json` next to the executable. Example:
199201
| `ILIgnoreLineContainingStrings` | List of strings used for IL line-ignore filtering (substring match, multiple values allowed), e.g. `buildserver`. |
200202
| `ShouldOutputFileTimestamps` | Adds last modified timestamps to each file line inside the report. |
201203
| `MaxParallelism` | Degree of parallelism for file comparisons. `0` or omitted uses the logical core count. |
204+
| `TextDiffParallelThresholdKilobytes` | Size threshold (KiB) that switches text diffing to parallel chunk mode. Default `512`. Values `<= 0` fall back to default. |
205+
| `TextDiffChunkSizeKilobytes` | Chunk size (KiB) for parallel text diffing. Default `64`. Values `<= 0` fall back to default. |
202206
| `EnableILCache` | Caches IL disassembly results (MD5 + tool/version) in memory and optionally on disk. |
203207
| `ILCacheDirectoryAbsolutePath` | Custom cache folder. Blank defaults to `<exe>/ILCache` with LRU + TTL control. |
204208
| `ILCacheStatsLogIntervalSeconds` | Interval (seconds) for logging IL cache statistics. `<= 0` falls back to 60 seconds. |
@@ -256,7 +260,7 @@ After writing the report, the following files are marked read-only (failures onl
256260
| IL cache | Reuses IL text based on MD5 + tool label (command + version). | In-memory cache (LRU up to 2000 items, TTL 12h) with optional disk persistence. |
257261
| MD5 pre-warming | Precomputes MD5 for all targets in parallel before diffing. | Evens out cache key generation time. |
258262
| IL cache prefetch | Promotes disk cache entries into memory before diffing. | Further reduces disassembler launches. |
259-
| Parallel text diff | Files ≥512 KiB are split into 64 KiB chunks compared in parallel. | Only determines equality (no diff output). |
263+
| Parallel text diff | Files at/above `TextDiffParallelThresholdKilobytes` (KiB) are split by `TextDiffChunkSizeKilobytes` (KiB) and compared in parallel. | Only determines equality (no diff output). |
260264
| Tool failure blacklist | Skips IL tools that failed repeatedly (default: 3 times) for 10 minutes. | Reduces repeated launch overhead. |
261265

262266
### IL cache notes

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ CI との関係:
201201
"ILIgnoreLineContainingStrings": [],
202202
"ShouldOutputFileTimestamps": true,
203203
"MaxParallelism": 0,
204+
"TextDiffParallelThresholdKilobytes": 512,
205+
"TextDiffChunkSizeKilobytes": 64,
204206
"EnableILCache": true,
205207
"ILCacheDirectoryAbsolutePath": "",
206208
"ILCacheStatsLogIntervalSeconds": 60,
@@ -223,6 +225,8 @@ CI との関係:
223225
| ILIgnoreLineContainingStrings | IL 比較時に無視したい文字列のリスト(部分一致、複数指定可)。例: `"buildserver"`|
224226
| ShouldOutputFileTimestamps | `diff_report.md` の各ファイル行に最終更新日時を併記するか否か( `true` で併記)。 |
225227
| MaxParallelism | ファイル比較の並列度。0 または未指定で論理コア数、自動判定。1 で逐次実行。 |
228+
| TextDiffParallelThresholdKilobytes | テキスト差分で並列チャンク比較へ切り替える閾値(KiB)。既定値 `512``0` 以下は既定値を使用。 |
229+
| TextDiffChunkSizeKilobytes | テキスト差分の並列比較で使うチャンクサイズ(KiB)。既定値 `64``0` 以下は既定値を使用。 |
226230
| EnableILCache | IL 逆アセンブル結果(MD5 + ツール / バージョン単位)をメモリ & 任意ディスクにキャッシュし再実行時の逆アセンブルをスキップ。 |
227231
| ILCacheDirectoryAbsolutePath | キャッシュ格納ディレクトリ。空 / 未指定で実行ディレクトリ配下 `ILCache`。容量制御 (LRU) と TTL(現在 12h)あり。 |
228232
| ILCacheStatsLogIntervalSeconds | IL キャッシュの内部統計(ヒット率など)をログへ出力する間隔(秒)。0 以下で 60 秒が既定。 |
@@ -274,7 +278,7 @@ dotnet run "/Users/UserA/workspace/old" "/Users/UserA/workspace/new" "YYYYMMDD"
274278
| IL キャッシュ | MD5 + ツールラベル (コマンド + バージョン) で IL テキストを再利用 | LRU (capacity=2000), TTL=12h, ディスク永続化可 |
275279
| MD5 プリウォーム | 全対象ファイルの MD5 を先読み並列計算 | キャッシュキー生成の待ち時間平準化 |
276280
| IL キャッシュ先読み | 既存ディスク IL キャッシュをメモリへ昇格 | 初回以降の逆アセンブル起動を更に削減 |
277-
| 並列テキスト差分 | 512KB 以上のテキストを 64KB チャンクで並列バイト比較 | 完全一致判定のみ(差分位置抽出なし) |
281+
| 並列テキスト差分 | `TextDiffParallelThresholdKilobytes` 以上(KiB)のテキストを `TextDiffChunkSizeKilobytes`(KiB)単位で並列バイト比較 | 完全一致判定のみ(差分位置抽出なし) |
278282
| ツール失敗ブラックリスト | 同一ツール連続失敗 (既定 3 回) で 10 分間スキップ | 起動オーバーヘッド削減 |
279283

280284
### IL キャッシュ補足

Services/FileDiffService.cs

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ public sealed class FileDiffService
2626
private const int BYTES_PER_KILOBYTE = 1024;
2727

2828
/// <summary>
29-
/// テキスト差分の高速化を検討するサイズ閾値(バイト)
29+
/// テキスト差分の高速化を検討するサイズ閾値(バイト)の既定値。
3030
/// </summary>
31-
private const int TEXT_DIFF_PARALLEL_THRESHOLD_BYTES = 512 * BYTES_PER_KILOBYTE;
31+
private const int DEFAULT_TEXT_DIFF_PARALLEL_THRESHOLD_BYTES = 512 * BYTES_PER_KILOBYTE;
3232

3333
/// <summary>
34-
/// テキスト差分比較時のチャンクサイズ(バイト)
34+
/// テキスト差分比較時のチャンクサイズ(バイト)の既定値。
3535
/// </summary>
36-
private const int TEXT_DIFF_CHUNK_SIZE_BYTES = 64 * BYTES_PER_KILOBYTE;
36+
private const int DEFAULT_TEXT_DIFF_CHUNK_SIZE_BYTES = 64 * BYTES_PER_KILOBYTE;
3737
#endregion
3838

3939
/// <summary>
@@ -127,6 +127,12 @@ public async Task<bool> FilesAreEqualAsync(string fileRelativePath, int maxParal
127127
// 3) テキスト拡張子ならテキスト比較: ネットワーク最適化時は逐次、それ以外は閾値に応じて並列比較を選択。
128128
if (_config.TextFileExtensions.Contains(Path.GetExtension(file1AbsolutePath).ToLower()))
129129
{
130+
int textDiffParallelThresholdBytes = GetEffectiveBytesFromConfiguredKilobytes(
131+
configuredKilobytes: _config.TextDiffParallelThresholdKilobytes,
132+
defaultBytes: DEFAULT_TEXT_DIFF_PARALLEL_THRESHOLD_BYTES);
133+
int textDiffChunkSizeBytes = GetEffectiveBytesFromConfiguredKilobytes(
134+
configuredKilobytes: _config.TextDiffChunkSizeKilobytes,
135+
defaultBytes: DEFAULT_TEXT_DIFF_CHUNK_SIZE_BYTES);
130136
bool areTextFilesEqual;
131137
try
132138
{
@@ -138,10 +144,15 @@ public async Task<bool> FilesAreEqualAsync(string fileRelativePath, int maxParal
138144
else
139145
{
140146
var file1Info = new FileInfo(file1AbsolutePath);
141-
if (file1Info.Length >= TEXT_DIFF_PARALLEL_THRESHOLD_BYTES)
147+
if (file1Info.Length >= textDiffParallelThresholdBytes)
142148
{
143149
// 大きいファイルは並列チャンク比較で高速化
144-
areTextFilesEqual = await DiffTextFilesParallelAsync(file1AbsolutePath, file2AbsolutePath, largeFileSizeThresholdBytes: TEXT_DIFF_PARALLEL_THRESHOLD_BYTES, maxParallel);
150+
areTextFilesEqual = await DiffTextFilesParallelAsync(
151+
file1AbsolutePath,
152+
file2AbsolutePath,
153+
largeFileSizeThresholdBytes: textDiffParallelThresholdBytes,
154+
chunkSizeBytes: textDiffChunkSizeBytes,
155+
maxParallel: maxParallel);
145156
}
146157
else
147158
{
@@ -177,9 +188,10 @@ public async Task<bool> FilesAreEqualAsync(string fileRelativePath, int maxParal
177188
/// <param name="file1AbsolutePath">ファイル1の絶対パス</param>
178189
/// <param name="file2AbsolutePath">ファイル2の絶対パス</param>
179190
/// <param name="largeFileSizeThresholdBytes">並列化閾値(バイト)。これ未満は逐次比較。</param>
191+
/// <param name="chunkSizeBytes">チャンクサイズ(バイト)。</param>
180192
/// <param name="maxParallel">最大並列度</param>
181193
/// <returns>一致すれば true。エラーや引数不正時は false。</returns>
182-
private static async Task<bool> DiffTextFilesParallelAsync(string file1AbsolutePath, string file2AbsolutePath, long largeFileSizeThresholdBytes, int maxParallel)
194+
private static async Task<bool> DiffTextFilesParallelAsync(string file1AbsolutePath, string file2AbsolutePath, long largeFileSizeThresholdBytes, int chunkSizeBytes, int maxParallel)
183195
{
184196
try
185197
{
@@ -205,7 +217,7 @@ private static async Task<bool> DiffTextFilesParallelAsync(string file1AbsoluteP
205217
}
206218

207219
// 大きなファイルは固定サイズのチャンクに分割し、読み取り→比較を並列実行する。
208-
int chunkCount = (int)((file1Info.Length + TEXT_DIFF_CHUNK_SIZE_BYTES - 1) / TEXT_DIFF_CHUNK_SIZE_BYTES);
220+
int chunkCount = (int)((file1Info.Length + chunkSizeBytes - 1) / chunkSizeBytes);
209221
var differences = 0;
210222
await Parallel.ForEachAsync(Enumerable.Range(0, chunkCount), new ParallelOptions { MaxDegreeOfParallelism = maxParallel }, async (index, cancellationToken) =>
211223
{
@@ -214,16 +226,16 @@ private static async Task<bool> DiffTextFilesParallelAsync(string file1AbsoluteP
214226
{
215227
return;
216228
}
217-
var buffer1 = new byte[TEXT_DIFF_CHUNK_SIZE_BYTES];
218-
var buffer2 = new byte[TEXT_DIFF_CHUNK_SIZE_BYTES];
229+
var buffer1 = new byte[chunkSizeBytes];
230+
var buffer2 = new byte[chunkSizeBytes];
219231
int read1, read2;
220232
using (var file1Stream = new FileStream(file1AbsolutePath, FileMode.Open, FileAccess.Read, FileShare.Read))
221233
using (var file2Stream = new FileStream(file2AbsolutePath, FileMode.Open, FileAccess.Read, FileShare.Read))
222234
{
223-
file1Stream.Seek((long)index * TEXT_DIFF_CHUNK_SIZE_BYTES, SeekOrigin.Begin);
224-
file2Stream.Seek((long)index * TEXT_DIFF_CHUNK_SIZE_BYTES, SeekOrigin.Begin);
225-
read1 = await file1Stream.ReadAsync(buffer1.AsMemory(0, TEXT_DIFF_CHUNK_SIZE_BYTES), cancellationToken);
226-
read2 = await file2Stream.ReadAsync(buffer2.AsMemory(0, TEXT_DIFF_CHUNK_SIZE_BYTES), cancellationToken);
235+
file1Stream.Seek((long)index * chunkSizeBytes, SeekOrigin.Begin);
236+
file2Stream.Seek((long)index * chunkSizeBytes, SeekOrigin.Begin);
237+
read1 = await file1Stream.ReadAsync(buffer1.AsMemory(0, chunkSizeBytes), cancellationToken);
238+
read2 = await file2Stream.ReadAsync(buffer2.AsMemory(0, chunkSizeBytes), cancellationToken);
227239
}
228240
// 同じオフセットのチャンクでも読み取りバイト数が異なれば即時不一致。
229241
if (read1 != read2)
@@ -249,5 +261,24 @@ private static async Task<bool> DiffTextFilesParallelAsync(string file1AbsoluteP
249261
return false;
250262
}
251263
}
264+
265+
/// <summary>
266+
/// KiB 指定の設定値をバイトへ変換します。設定値が 0 以下または変換でオーバーフローする場合は既定値を返します。
267+
/// </summary>
268+
private static int GetEffectiveBytesFromConfiguredKilobytes(int configuredKilobytes, int defaultBytes)
269+
{
270+
if (configuredKilobytes <= 0)
271+
{
272+
return defaultBytes;
273+
}
274+
275+
long bytes = (long)configuredKilobytes * BYTES_PER_KILOBYTE;
276+
if (bytes > int.MaxValue)
277+
{
278+
return defaultBytes;
279+
}
280+
281+
return (int)bytes;
282+
}
252283
}
253284
}

config.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@
8282
"ILIgnoreLineContainingStrings": [],
8383
"ShouldOutputFileTimestamps": true,
8484
"MaxParallelism": 0,
85+
"TextDiffParallelThresholdKilobytes": 512,
86+
"TextDiffChunkSizeKilobytes": 64,
8587
"EnableILCache": true,
8688
"ILCacheDirectoryAbsolutePath": "",
8789
"ILCacheStatsLogIntervalSeconds": 60,

version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
3-
"version": "1.2.0",
3+
"version": "1.2.1",
44
"publicReleaseRefSpec": [
55
"^refs/heads/main$",
66
"^refs/tags/v\\d+\\.\\d+(?:\\.\\d+)?$"

0 commit comments

Comments
 (0)