Compiled Linq Expression Tree speed

Here in this article in the comments there was not a dispute, but some “not convergence” in comparing the speed of IL Emit and the compiled Linq Expression Tree.

This mini article is the speed test code + the results of this test run.

For the test, a code was chosen that intersects with the contents of the original article — serialization to a stream, the length of the string, and then all bytes of the string in the UTF-16 encoding (Encoding.Unicode).

The serialization code itself may not be the most optimal, but is close to if you do not use unsafe constructs.

The code in both implementations is the same, as can be seen by parsing the construction of the Lambda expression.

I did not bother with the generation of IL through Emit - the code that should be “optimal” I just wrote in C # in a static method (in fact, all test program methods are static, because this is a very simple console application) the method is further called Native .

The second method to compare is the expression generated by Lambda, combined by calling the Compile method (perhaps a speed increase can be achieved using CompileToMethod, but this is not exact) - this method is further called Expresisons .

BONUS! For some thought, an additional test was added — an indirect method call used in the Native test — by assigning a method to a static delegate field — this method is called Native Dlgt .

At the very beginning of the application, the output of the results of the work of both methods occurs so that you can make sure that the code generates exactly the same data.

So, here is the application code (all at once):

Listing
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; } } } 


But the test results for the following configuration:

CPU
	 Intel (R) Core (TM) i7-3770 CPU @ 3.40GHz

	 Base speed: 3.90 GHz
	 Sockets: 1
	 Cores: 4
	 Logical processors: 8
	 Virtualization: Enabled
	 L1 cache: 256 KB
	 L2 cache: 1.0 MB
	 L3 cache: 8.0 MB

	 Utilization 8%
	 Speed ​​4.05 GHz
	 Up time 5: 00: 43: 01
	 Processes 239
	 Threads 4092
	 Handles 168774


Memory
	 32.0 GB DDR3

	 Speed: 1600 MHz
	 Slots used: 4 of 4
	 Form factor: DIMM
	 Hardware reserved: 42.5 MB

	 Available 20.7 GB
	 Cached 20.1 GB
	 Committed 13.4 / 36.7 GB
	 Paged pool 855 MB
	 Non-paged pool 442 MB
	 In use (Compressed) 11.2 GB (48.6 MB)

Target .Net Framework 4.7
OS Windows 10 Pro x64 1803 build 17134.48

So, the promised results:

Compiling in Debug, without optimization, launch without debugger (Ctrl + F5):
TestTime (timespan)Time (ticks)% time
Native00: 00: 01.576065115760651101.935%
Native dlgt00: 00: 01.546147815461478100%
Expressions00: 00: 01.583545415835454102.4188%

Compilation in Release, with optimization, launch without debugger (Ctrl + F5):
TestTime (timespan)Time (ticks)% time
Native00: 00: 01.3182291
13182291
100%
Native dlgt00: 00: 01.3300925
13300925
100.8999%
Expressions00: 00: 01.4871786
14871786
112.8164%

We can sum up some of the results:


Bonus number 2 under the spoiler - Debug representation of Lambda expressions before compilation:

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

Source: https://habr.com/ru/post/413561/


All Articles