编译的Linq表达式树速度

本文的评论中,这里没有争议,但是在比较IL Emit和编译的Linq Expression Tree的速度时有些“不收敛”。

这篇迷你文章是速度测试代码+此测试运行的结果。

在测试中,选择了与原始文章的内容相交的代码-序列化为字符串长度的流,然后序列化为使用UTF-16(Encoding.Unicode)编码的字符串的所有字节。

序列化代码本身可能不是最佳的,但是如果您不使用不安全的构造,则接近于最佳化。

正如您通过检查Lambda表达式的构造所看到的,两种实现中的代码是相同的。

我没有理会通过Emit生成IL-我刚刚在C#中以静态方法编写的代码应该是“最优的”(实际上,测试程序的所有方法都是静态的,因为这是一个非常简单的控制台应用程序)-该方法进一步称为Native

用于比较的第二种方法是Lambda生成的表达式,该表达式通过调用Compile方法进行编译(也许可以通过CompileToMethod实现速度提升,但这并不准确)-该方法进一步称为Expresisons

奖金! 出于某种原因,添加了一个附加测试-通过将方法分配给静态委托字段来间接调用本测试中使用的方法-该方法称为本机Dlgt

在应用程序的最开始,将同时输出这两种方法的输出,以便您可以确保代码生成完全相同的数据。

因此,这是应用程序代码(一次全部):

上市
using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; namespace ExpressionSpeedTest { class Program { static void Main(string[] args) { InitExpression(); InitDelegateNative(); var inst = new TestClass { StringProp = "abcdefabcdef" }; byte[] buff1, buff2; using (var ms1 = new MemoryStream()) { SaveString(ms1, inst); buff1 = ms1.ToArray(); } using (var ms2 = new MemoryStream()) { DynamicMethod(ms2, inst); buff2 = ms2.ToArray(); } Console.WriteLine($"Native string: {string.Join("", buff1.Select(b => Encoding.Default.GetString(new[] { b })))}"); Console.WriteLine($"Expressions string: {string.Join("", buff2.Select(b => Encoding.Default.GetString(new[] { b })))}"); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestNative(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestDelegateNative(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestExpressions(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); Console.ReadLine(); } private static void TestDelegateNative() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { SaveString(ms, inst); } sw.Stop(); Console.WriteLine($"Native Dlgt test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } private static void InitDelegateNative() { NativeDelegate = SaveString; } private static void InitExpression() { var intGetBytes = typeof(BitConverter).GetMethods(BindingFlags.Static | BindingFlags.Public) .Single(x => x.Name == nameof(BitConverter.GetBytes) && x.GetParameters()[0].ParameterType == typeof(int)); var stringGetBytes = typeof(Encoding).GetMethods(BindingFlags.Instance | BindingFlags.Public) .Single(x => x.Name == nameof(Encoding.GetBytes) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(string)); var unicodeProp = typeof(Encoding).GetProperty(nameof(Encoding.Unicode), BindingFlags.Static | BindingFlags.Public); var streamWrite = typeof(Stream).GetMethod(nameof(Stream.Write)); var streamPar = Expression.Parameter(typeof(Stream), "stream"); var instPar = Expression.Parameter(typeof(TestClass), "inst"); var intBuffVar = Expression.Variable(typeof(byte[]), "intBuff"); var strBuffVar = Expression.Variable(typeof(byte[]), "strBuff"); var expressionBody = Expression.Block( new[] { intBuffVar, strBuffVar }, Expression.Assign(intBuffVar, Expression.Call(null, intGetBytes, Expression.Property( Expression.Property( instPar, nameof(TestClass.StringProp)), nameof(string.Length)))), Expression.Assign(strBuffVar, Expression.Call(Expression.Property(null, unicodeProp), stringGetBytes, Expression.Property( instPar, nameof(TestClass.StringProp) ))), Expression.Call(streamPar, streamWrite, intBuffVar, Expression.Constant(0), Expression.Property(intBuffVar, nameof(Array.Length))), Expression.Call(streamPar, streamWrite, strBuffVar, Expression.Constant(0), Expression.Property(strBuffVar, nameof(Array.Length))) ); DynamicMethod = Expression.Lambda<Action<Stream, TestClass>>(expressionBody, streamPar, instPar).Compile(); } private const int loopLength = 10000000; private static Action<Stream, TestClass> DynamicMethod; private static Action<Stream, TestClass> NativeDelegate; private static void TestExpressions() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { DynamicMethod(ms, inst); } sw.Stop(); Console.WriteLine($"Expressions test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } private static void TestNative() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { SaveString(ms, inst); } sw.Stop(); Console.WriteLine($"Native test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } public static void SaveString(Stream stream, TestClass instance) { var intBuff = BitConverter.GetBytes(instance.StringProp.Length); var strBuff = Encoding.Unicode.GetBytes(instance.StringProp); stream.Write(intBuff, 0, intBuff.Length); stream.Write(strBuff, 0, strBuff.Length); } } class TestClass { public string StringProp { get; set; } } } 


这是以下配置的测试结果:

中央处理器
	英特尔(R)酷睿(TM)i7-3770 CPU @ 3.40GHz

	基本速度:3.90 GHz
	插座数:1
	核心数:4
	逻辑处理器:8
	虚拟化:已启用
	 L1快取:256 KB
	 L2快取:1.0 MB
	三级缓存:8.0 MB

	利用率8%
	速度4.05 GHz
	正常运行时间5:00:43:01
	流程239
	线程4092
	把手168774


记忆体
	 32.0 GB DDR3

	速度:1600 MHz
	使用的插槽:4个(共4个)
	外形尺寸:DIMM
	硬件保留:42.5 MB

	可用20.7 GB
	缓存20.1 GB
	已承诺13.4 / 36.7 GB
	分页池855 MB
	非页面缓冲池442 MB
	使用中(压缩)11.2 GB(48.6 MB)

目标.Net Framework 4.7
操作系统Windows 10 Pro x64 1803内部版本17134.48

因此,承诺的结果:

在Debug中进行编译,无需优化,无需调试器即可启动(Ctrl + F5):
测验时间(时间跨度)时间(滴答声)时间百分比
本机00:00:01.576065115760651101.935%
原生dlgt00:00:01.546147815461478100%
表达方式00:00:01.583545415835454102.4188%

在发行版中进行编译,并进行优化,无需调试器即可启动(Ctrl + F5):
测验时间(时间跨度)时间(滴答声)时间百分比
本机00:00:01.3182291
13182291
100%
原生dlgt00:00:01.3300925
13300925
100.8999%
表达方式00:00:01.4871786
14871786
112.8164%

您可以总结一些结果:

  • 考虑到运行时通过Expression Tree生成代码要容易得多,因此在Debug中,Compiled Expression Tree的运行速度要慢1-2%,在Release中的运行速度要慢10-12%。这非常好。
  • 在调试模式下,由于某种原因,通过委托进行的间接方法调用比直接调用要快。

扰流板下的2号奖金 -编译前调试Lambda表达式的表示形式:

拉姆达
 .Lambda#Lambda1 <System.Action`2 [System.IO.Stream,ExpressionSpeedTest.TestClass]>(
     System.IO.Stream $流,
     ExpressionSpeedTest.TestClass $ inst){
     。块(
         System.Byte [] $ intBuff,
         System.Byte [] $ strBuff){
         $ intBuff =。调用System.BitConverter.GetBytes(($ inst.StringProp).Length);
         $ strBuff = .Call(System.Text.Encoding.Unicode).GetBytes($ inst.StringProp);
        调用$ stream.Write(
             $ intBuff,
             0
             $ intBuff.Length);
        调用$ stream.Write(
             $ strBuff,
             0
             $ strBuff.Length)
     }
 }

Source: https://habr.com/ru/post/zh-CN413561/


All Articles