在上一篇博文中, 我使用了幾種方法試圖找到哪個是.NET CORE下最快比較兩個文件的方法.文章發布后,引起了很多博友的討論, 在此我對大家的支持表示由衷的感謝.
其中也有博友提出了對於我最后使用ReadOnlySpan的方法的結果的懷疑, 認為它的結果快的不正常, 幾乎超出了磁盤IO速度的限制. 對此我要深刻的進行反省------我把ReadOnlySpan放在最后執行,使它利用了磁盤緩存,而大大加快了比較速度, 當發現這點時,我立即取消發布了之前的博文,並重新設計了整個測試方案, 使用更嚴謹公平的方式測試每個比較方法, 並寫下此篇博文以正視聽.
另外, 在重新測試的過程中, 也充分采取了博友的意見, 改進了以下幾點:
-
全部測試使用BenchmarkDotNet來進行
為了更專業公平的結果, 重構代碼使用BenchmarkDotNet來測試
-
嘗試不同緩存大小
為了充分測試不同緩存大小對速度的影響, 字節數組的緩存大小分為3種, 分別是:
- 4096 * 10
- 4096 * 100
- 4096 * 1000
-
嘗試使用異步IO方法
以觀察異步IO對速度的影響
-
運行前清除磁盤緩存
當然這是本篇博文最重要的一點. 這里利用了Win32API在每個比較方法運行開始時清除磁盤緩存(代碼見文后,如果代碼需要在Windows以外的平台上運行, 需要自行實現清除緩存的方法)
關於比較方法的原理請參看上一篇博文,這里就不再贅述, 只是展示結果:
BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=2.2.401
[Host] : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT
DefaultJob : .NET Core 2.2.6 (CoreCLR 4.6.27817.03, CoreFX 4.6.27818.02), 64bit RyuJIT
Method | buffer_size | Mean | Error | StdDev | Median |
---|---|---|---|---|---|
CompareByMD5 | 40960 | 3.294 s | 0.0608 s | 0.0539 s | 3.311 s |
CompareByMD5Async | 40960 | 4.723 s | 0.0137 s | 0.0128 s | 4.720 s |
CompareByToInt64 | 40960 | 4.883 s | 0.0140 s | 0.0131 s | 4.886 s |
CompareByByteArray | 40960 | 4.713 s | 0.0059 s | 0.0052 s | 4.714 s |
CompareByByteArrayAsync | 40960 | 4.687 s | 0.0070 s | 0.0066 s | 4.688 s |
CompareByString | 40960 | 5.491 s | 0.1066 s | 0.0997 s | 5.483 s |
CompareBySequenceEqual | 40960 | 5.185 s | 0.1028 s | 0.1337 s | 5.180 s |
CompareByWin32API | 40960 | 4.334 s | 0.0209 s | 0.0195 s | 4.331 s |
CompareByReadOnlySpan | 40960 | 4.316 s | 0.0209 s | 0.0195 s | 4.313 s |
CompareByReadOnlySpanAsync | 40960 | 4.699 s | 0.0235 s | 0.0220 s | 4.695 s |
CompareByMD5 | 409600 | 3.329 s | 0.0639 s | 0.0808 s | 3.334 s |
CompareByMD5Async | 409600 | 4.727 s | 0.0192 s | 0.0179 s | 4.720 s |
CompareByToInt64 | 409600 | 4.881 s | 0.0111 s | 0.0104 s | 4.879 s |
CompareByByteArray | 409600 | 3.017 s | 0.0583 s | 0.0798 s | 3.014 s |
CompareByByteArrayAsync | 409600 | 3.038 s | 0.0935 s | 0.1370 s | 2.996 s |
CompareByString | 409600 | 5.086 s | 0.0871 s | 0.0815 s | 5.075 s |
CompareBySequenceEqual | 409600 | 5.019 s | 0.0978 s | 0.0915 s | 4.998 s |
CompareByWin32API | 409600 | 3.048 s | 0.1061 s | 0.1263 s | 3.017 s |
CompareByReadOnlySpan | 409600 | 3.079 s | 0.0862 s | 0.1264 s | 3.045 s |
CompareByReadOnlySpanAsync | 409600 | 2.976 s | 0.0484 s | 0.0452 s | 2.988 s |
CompareByMD5 | 4096000 | 3.456 s | 0.0850 s | 0.2410 s | 3.369 s |
CompareByMD5Async | 4096000 | 4.766 s | 0.0412 s | 0.0385 s | 4.762 s |
CompareByToInt64 | 4096000 | 5.003 s | 0.0789 s | 0.0659 s | 4.998 s |
CompareByByteArray | 4096000 | 2.558 s | 0.0505 s | 0.1055 s | 2.607 s |
CompareByByteArrayAsync | 4096000 | 2.500 s | 0.0492 s | 0.0766 s | 2.508 s |
CompareByString | 4096000 | 6.024 s | 0.0655 s | 0.0613 s | 6.020 s |
CompareBySequenceEqual | 4096000 | 4.949 s | 0.0793 s | 0.0742 s | 4.931 s |
CompareByWin32API | 4096000 | 2.582 s | 0.0511 s | 0.0881 s | 2.620 s |
CompareByReadOnlySpan | 4096000 | 2.677 s | 0.0503 s | 0.0420 s | 2.666 s |
CompareByReadOnlySpanAsync | 4096000 | 2.460 s | 0.0492 s | 0.0657 s | 2.458 s |
"buffers_size"即是字節數組緩存的大小
"Mean"列即平均耗時, 數值越小越好
這次我測試的文件大小是500MB, 從數據中我們能觀察到以下現象:
CompareByMD5
方法因為未使用字節數組緩存,所以在各組測試中的結果基本穩定.甚至在40960組中是最快的方法CompareByString
在所有組中表現最差, 第2差的是CompareBySequenceEqual
, 所以我都沒有動力為它們寫異步方法- 使用字節數組緩存的方法, 基本上是隨着緩存的增大, 速度越快.
- 異步方法在緩存最大那組(409600)中才開始有勝出的結果, 不過整體來看意義並不大
CompareByReadOnlySpan
同樣表現優異, 這讓我對上一篇博文中犯的錯安心了很多CompareByByteArray
在各組中都相當有競爭力, 幾乎與CompareByReadOnlySpan
並駕齊驅.不過當命中磁盤緩存時,CompareByReadOnlySpan
會像開掛一樣速度起飛.CompareByWin32API
也非常出色, 不過只能在Windows平台使用
結論:
- 最簡單的是MD5, 速度也可以接受
- 最朴實的是ByteArray, 代碼通俗易懂, 速度出色
- 最實用的還是
CompareByReadOnlySpan
, 速度出色, 還可利用磁盤緩存
代碼放在GITHUB上, 其中清除磁盤的緩存需要管理員權限, 所以需要以管理員權限運行Visual Studio
關於文件比較的方法希望通過這篇博文能得到正確的結論了, 也歡迎廣大博友積極評論!