内容简介:上回研究這個現象挺有趣,初次起始慢讓我們優先想到的原因多半與笨重資源有關,例如:建立網路連線、開啟檔案、配置記憶體... 等等,首次資源初始化後,後續執行可共用或重複利用,省去初始化時間因此變快。但回歸到單純的雜湊函數運算,不需要引用外部資源,然後不管用哪個雜湊演算法延遲的時間都差不多,就不是那麼容易想像延遲的來源。我做了一個範例程式突顯這個現象。程式分別使用 MD5、SHA1、SHA256 三種雜湊演算法計算同一組資料的雜湊值,參數 1,2,3 決定測試順序,1 是 MD5、SHA1、SHA256,2
上回研究 Stopwatch 測量微秒精確度 有個小插曲,第一次執行 MD5、SHA1 運算總會特別慢,慢了五倍有餘,先跑 MD5 慢的是 MD5,先跑 SHA1 慢的就是 SHA1,之後的數字才正常。
這個現象挺有趣,初次起始慢讓我們優先想到的原因多半與笨重資源有關,例如:建立網路連線、開啟檔案、配置記憶體... 等等,首次資源初始化後,後續執行可共用或重複利用,省去初始化時間因此變快。但回歸到單純的雜湊函數運算,不需要引用外部資源,然後不管用哪個雜湊演算法延遲的時間都差不多,就不是那麼容易想像延遲的來源。
我做了一個範例程式突顯這個現象。程式分別使用 MD5、SHA1、SHA256 三種雜湊演算法計算同一組資料的雜湊值,參數 1,2,3 決定測試順序,1 是 MD5、SHA1、SHA256,2 是 SHA1、MD5、SHA256,3 是 SHA256、MD5、SHA1,每回合測試用迴圈計算三次,每次程式執行跑兩回合。
using System;
using System.Diagnostics;
using System.Security.Cryptography;
namespace PerfTest
{
class Program
{
static byte[] data = new byte[512 * 1024];
private const int COUNT = 3;
static void Main(string[] args)
{
var flag = args.Length > 0 ? args[0] : "1";
switch (flag)
{
case "1":
Test1();
Test1();
break;
case "2":
Test2();
Test2();
break;
case "3":
Test3();
Test3();
break;
}
}
static void Test(Action doJob, string name)
{
Stopwatch sw = new Stopwatch();
sw.Start();
doJob();
sw.Stop();
Console.Write("{0,-7} {1,7}μs\t", name,
$"{sw.Elapsed.TotalMilliseconds * 1000:n0}");
}
private static void Test1()
{
Console.WriteLine("=== Test 1 MD5 First ===");
for (var i = 0; i < COUNT; i++)
{
Test(()=>MD5.Create().ComputeHash(data), "MD5");
Test(()=>SHA1.Create().ComputeHash(data), "SHA1");
Test(() => SHA256.Create().ComputeHash(data), "SHA256");
Console.WriteLine();
}
}
private static void Test2()
{
Console.WriteLine("=== Test 2 SHA1 First ===");
for (var i = 0; i < COUNT; i++)
{
Test(() => SHA1.Create().ComputeHash(data), "SHA1");
Test(() => MD5.Create().ComputeHash(data), "MD5");
Test(() => SHA256.Create().ComputeHash(data), "SHA256");
Console.WriteLine();
}
}
private static void Test3()
{
Console.WriteLine("=== Test 3 SHA256 First ===");
for (var i = 0; i < COUNT; i++)
{
Test(() => SHA256.Create().ComputeHash(data), "SHA256");
Test(() => MD5.Create().ComputeHash(data), "MD5");
Test(() => SHA1.Create().ComputeHash(data), "SHA1");
Console.WriteLine();
}
}
}
}
測試結果如下:
如同上回的測試,第一回合測試的第一個運算,不管是 MD5、SHA1、SHA256,總是比正常值慢 6-8ms,而這只會出現在第一回合,第二回合就正常了。
追進原始碼試著找原因。MD5.Create()、SHA1.Create()、SHA256.Create() 背後都會呼叫 CryptoConfig.CreateFromName(),其中呼叫 InitializeConfigInfo() 跑迴圈初始化,疑似拖累效能的嫌犯,另外還有幾個貌似笨重只需執行一次的屬性 CryptoConfig.DefaultNameHT、Type.DefaultBinder,我修改了程式,在呼叫 MD5.Create() 前先執行這些具有嫌疑的一次性方法:(InitializeConfigInfo、DefaultNameHT 為 private static,要用 Reflection 技巧摸進去觸發)
static byte[] data = new byte[512 * 1024];
private const int COUNT = 3;
static void Main(string[] args)
{
Test(() =>
{
MethodInfo mi =
typeof(CryptoConfig).GetMethod("InitializeConfigInfo",
BindingFlags.Static | BindingFlags.NonPublic);
mi.Invoke(null, null);
}, "InitializeConfigInfo");
Test(() =>
{
var pi = typeof(CryptoConfig).GetProperty("DefaultNameHT",
BindingFlags.Static | BindingFlags.NonPublic);
var o = pi.GetValue(null, null);
}, "\nDefaultNameHT");
Test(() =>
{
var binder = Type.DefaultBinder;
}, "\nType.DefaultBinder");
Console.WriteLine();
var flag = args.Length > 0 ? args[0] : "1";
switch (flag)
{
case "1":
Test1();
Test1();
break;
case "2":
Test2();
Test2();
break;
case "3":
Test3();
Test3();
break;
}
}
預先執行這些靜態方法、屬性後,第一次執行延遲明顯縮短(9ms -> 3ms),測量得到 InitializeConfigInfo 耗時 6ms,DefaultNameHT 耗時 1ms,也接近原本首次執行出現 6-8ms 差距。ComputeHash() 的部分可能也有類似情況,但比重不高,我懶得繼續深入。
這個結果可印證我們的推論方向正確但仍稱不上鐵證,於是我想到可以 Line-By-Line 測量各方法耗時的神奇工具 - JetBrains dotTrace Profiler 並將程式碼簡化如下:
static byte[] data = new byte[512 * 1024];
static void TestMD5_First()
{
var hash = MD5.Create().ComputeHash(data);
}
static void TestMD5_Second()
{
var hash = MD5.Create().ComputeHash(data);
}
static void Main(string[] args)
{
TestMD5_First();
TestMD5_Second();
}
啟用 dotTrace Line-By-Line 欄截追蹤,程式執行速度雖因此嚴重變慢,但捕捉到的耗時比例卻極具參考價值。由以下結果可知第一次執行 MD5.Create() 耗時 218ms,第二次 MD5.Create() 則縮短到 1ms 搞定。而第一次 Create() 執行 InitializeConfigInfo() 耗时 142ms,第二次則為 0 秒,與先前的實驗結果相符。而其他如 GetConstructors()、GetParameters() 的第二次執行時間也是 0 ms。dotTrace 的追蹤結果為第一次執行較慢的現象提供了極具說服力的解釋。
除了靜態方法、屬性,靜態建構式或變數也具有首次使用前執行並只執行一次的特性,也是首次執行較慢常見的原因。下回再遇到難以理解的首跑較慢現象,可往這方面推敲,實務上則可用略過首次結果方式避免其影響結果。
希望今天的剖析實驗有助解答大家對「未引用外部或大量資源的效能測試,第一次執行也會偏慢」的疑惑。至少,我的好奇心已被滿足了,呵~
The first run in .NET performance is always slower, even though it has nothing to do with external or large resource. The article uses a MD1/SHA1/SHA256 performane test to provide a classic exmaple and digs into the root cause.
以上所述就是小编给大家介绍的《.NET 效能測試首次執行偏慢現象解析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 研发效能领域洞察 | Google 捐赠 CDF tektoncd/pipeline 项目解析
- 解密蚂蚁研发效能:如何用数据驱动效能提升?
- 淺談效能測試
- 效能改进的团队介绍
- TiDB 單機效能
- 阿里如何定义团队的研发效能?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Impractical Python Projects
Lee Vaughan / No Starch Press / 2018-11 / USD 29.95
Impractical Python Projects picks up where the complete beginner books leave off, expanding on existing concepts and introducing new tools that you’ll use every day. And to keep things interesting, ea......一起来看看 《Impractical Python Projects》 这本书的介绍吧!