هنا في
هذه المقالة في التعليقات لم يكن هناك نزاع ، ولكن بعض "عدم التقارب" في مقارنة سرعة IL Emit وشجرة التعبير Linq المترجمة.
هذه المقالة المصغرة هي رمز اختبار السرعة + نتائج تشغيل هذا الاختبار.
للاختبار ، تم تحديد رمز يتقاطع مع محتويات المقالة الأصلية - التسلسل إلى دفق بطول سلسلة ، ثم جميع بايتات سلسلة مشفرة في UTF-16 (Encoding.Unicode).
قد لا يكون رمز التسلسل نفسه هو الأمثل ، ولكنه قريب من ذلك إذا لم تستخدم بنيات غير آمنة.
الرمز في كلا التطبيقين هو نفسه ، كما يمكنك أن ترى من خلال فحص بناء تعبير لامدا.
لم أزعجني بتوليد IL من خلال Emit - الرمز الذي يجب أن يكون "مثاليًا" الذي كتبته للتو في C # في الطريقة الثابتة (في الواقع ، جميع طرق برنامج الاختبار ثابتة ، لأن هذا هو تطبيق وحدة تحكم بسيط للغاية) - هذا وتسمى هذه الطريقة
الأصلية .
الطريقة الثانية للمقارنة هي تعبير Lambda الذي تم إنشاؤه عن طريق استدعاء طريقة Compile (ربما يمكن تحقيق زيادة في السرعة باستخدام CompileToMethod ، لكن هذا ليس دقيقًا) - تسمى هذه الطريقة أيضًا
Expresisons .
مكافأة! لسبب ما ، تمت إضافة اختبار إضافي - استدعاء غير مباشر للطريقة المستخدمة في الاختبار الأصلي - عن طريق تعيين الطريقة إلى حقل مفوض ثابت - تسمى هذه الطريقة Native 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; } } }
وهنا نتائج الاختبار على التكوين التالي:
وحدة المعالجة المركزية معالج Intel (R) Core (TM) i7-3770 بسرعة 3.40 جيجاهرتز
السرعة الأساسية: 3.90 جيجاهرتز
المقابس: 1
النوى: 4
المعالجات المنطقية: 8
المحاكاة الافتراضية: ممكّنة
ذاكرة التخزين المؤقت L1: 256 كيلوبايت
ذاكرة التخزين المؤقت L2: 1.0 ميغابايت
ذاكرة التخزين المؤقت L3: 8.0 ميغابايت
الاستفادة 8٪
السرعة 4.05 جيجاهرتز
وقت التشغيل 5: 00: 43: 01
العمليات 239
المواضيع 4092
يعالج 168774
الذاكرة 32.0 جيجابايت DDR3
السرعة: 1600 ميجا هرتز
الفتحات المستخدمة: 4 من 4
عامل الشكل: DIMM
الأجهزة المحجوزة: 42.5 ميغابايت
متاح 20.7 جيجابايت
نسخة مخبأة 20.1 جيجابايت
ارتكبت 13.4 / 36.7 غيغابايت
تجمع مقسم إلى صفحات 855 ميغابايت
تجمع غير مقسم إلى صفحات 442 ميغابايت
قيد الاستخدام (مضغوط) 11.2 جيجابايت (48.6 ميجابايت)
الهدف .Net Framework 4.7
نظام التشغيل Windows 10 Pro x64 1803 build 17134.48
لذلك ، النتائج الموعودة:
التحويل البرمجي في Debug ، بدون تحسين ، التشغيل بدون مصحح (Ctrl + F5):
اختبر | الوقت (النطاق الزمني) | الوقت (القراد) | ٪ الوقت |
---|
أصلي | 00: 00: 01.5760651 | 15760651 | 101.935٪ |
Dlgt الأصلي | 00: 00: 01.5461478 | 15461478 | 100٪ |
التعبيرات | 00: 00: 01.5835454 | 15835454 | 102.4188٪ |
الترجمة في الإصدار ، مع التحسين ، تبدأ بدون مصحح (Ctrl + F5):
اختبر | الوقت (النطاق الزمني) | الوقت (القراد) | ٪ الوقت |
---|
أصلي | 00: 00: 01.3182291
| 13182291
| 100٪ |
Dlgt الأصلي | 00: 00: 01.3300925
| 13300925
| 100.8999٪ |
التعبيرات | 00: 00: 01.4871786
| 14871786
| 112.8164٪ |
يمكن تلخيص بعض النتائج:
- تعمل شجرة التعبير المجمعة بشكل أبطأ بنسبة 1-2٪ في Debug و 10-12 في الإصدار ، وهو أمر جيد جدًا ، مع الأخذ في الاعتبار أن إنشاء التعليمات البرمجية من خلال Expression Tree في وقت التشغيل أسهل بكثير.
- في وضع التصحيح ، لسبب ما ، يكون استدعاء الأسلوب غير المباشر من خلال المفوض أسرع من المكالمة المباشرة.
رقم المكافأة 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 = .Call 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)
}}
}}