รวบรวม C # Lambda Expressions Performance


91

พิจารณาการจัดการอย่างง่ายต่อไปนี้สำหรับคอลเลกชัน:

static List<int> x = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var result = x.Where(i => i % 2 == 0).Where(i => i > 5);

ตอนนี้มาใช้นิพจน์ รหัสต่อไปนี้เทียบเท่าโดยประมาณ:

static void UsingLambda() {
    Func<IEnumerable<int>, IEnumerable<int>> lambda = l => l.Where(i => i % 2 == 0).Where(i => i > 5);
    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) 
        var sss = lambda(x).ToList();

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda: {0}", tn - t0);
}

แต่ฉันต้องการสร้างนิพจน์ได้ทันทีดังนั้นนี่คือการทดสอบใหม่:

static void UsingCompiledExpression() {
    var f1 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i % 2 == 0));
    var f2 = (Expression<Func<IEnumerable<int>, IEnumerable<int>>>)(l => l.Where(i => i > 5));
    var argX = Expression.Parameter(typeof(IEnumerable<int>), "x");
    var f3 = Expression.Invoke(f2, Expression.Invoke(f1, argX));
    var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX);

    var c3 = f.Compile();

    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) 
        var sss = c3(x).ToList();

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda compiled: {0}", tn - t0);
}

แน่นอนว่ามันไม่เหมือนข้างบนอย่างแน่นอนดังนั้นเพื่อความยุติธรรมฉันปรับเปลี่ยนอันแรกเล็กน้อย:

static void UsingLambdaCombined() {
    Func<IEnumerable<int>, IEnumerable<int>> f1 = l => l.Where(i => i % 2 == 0);
    Func<IEnumerable<int>, IEnumerable<int>> f2 = l => l.Where(i => i > 5);
    Func<IEnumerable<int>, IEnumerable<int>> lambdaCombined = l => f2(f1(l));
    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) 
        var sss = lambdaCombined(x).ToList();

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda combined: {0}", tn - t0);
}

ตอนนี้ผลลัพธ์สำหรับ MAX = 100000, VS2008, การดีบัก ON:

Using lambda compiled: 23437500
Using lambda:           1250000
Using lambda combined:  1406250

และด้วยการดีบักปิด:

Using lambda compiled: 21718750
Using lambda:            937500
Using lambda combined:  1093750

เซอร์ไพรส์ . นิพจน์ที่คอมไพล์ช้ากว่าทางเลือกอื่นประมาณ 17 เท่า ตอนนี้คำถามมาถึง:

  1. ฉันกำลังเปรียบเทียบนิพจน์ที่ไม่เทียบเท่าหรือไม่
  2. มีกลไกในการทำให้. NET "ปรับให้เหมาะสม" นิพจน์ที่คอมไพล์หรือไม่?
  3. ฉันจะแสดงการโทรลูกโซ่เดียวกันโดยใช้l.Where(i => i % 2 == 0).Where(i => i > 5);โปรแกรมได้อย่างไร

สถิติเพิ่มเติม Visual Studio 2010 การดีบักเปิดการเพิ่มประสิทธิภาพปิด:

Using lambda:           1093974
Using lambda compiled: 15315636
Using lambda combined:   781410

การดีบักเปิดการเพิ่มประสิทธิภาพเปิด:

Using lambda:            781305
Using lambda compiled: 15469839
Using lambda combined:   468783

การดีบักปิดการเพิ่มประสิทธิภาพเปิด:

Using lambda:            625020
Using lambda compiled: 14687970
Using lambda combined:   468765

เซอร์ไพรส์ใหม่. การเปลี่ยนจาก VS2008 (C # 3) เป็น VS2010 (C # 4) ทำให้UsingLambdaCombinedเร็วกว่าแลมด้าดั้งเดิม


โอเคฉันพบวิธีปรับปรุงประสิทธิภาพการคอมไพล์แลมบ์ดามากกว่าลำดับความสำคัญ นี่คือเคล็ดลับ หลังจากเรียกใช้ profiler แล้ว 92% ของเวลาจะถูกใช้ไปกับ:

System.Reflection.Emit.DynamicMethod.CreateDelegate(class System.Type, object)

อืมมม ... ทำไมถึงสร้าง delegate ใหม่ทุกครั้ง ฉันไม่แน่ใจ แต่วิธีแก้ไขตามในโพสต์แยกต่างหาก


3
การกำหนดเวลาเหล่านี้ทำงานใน Visual Studio หรือไม่ ในกรณีนี้ให้ทำซ้ำการกำหนดเวลาโดยใช้การสร้างโหมดรีลีสและรันโดยไม่ต้องดีบัก (เช่น Ctrl + F5 ใน Visual Studio หรือจากบรรทัดคำสั่ง) นอกจากนี้ยังพิจารณาใช้สำหรับการกำหนดเวลามากกว่าStopwatch DateTime.Now
Jim Mischel

12
ฉันไม่รู้ว่าทำไมมันถึงช้าลง แต่เทคนิคการเปรียบเทียบของคุณยังไม่ค่อยดีนัก ก่อนอื่น DateTime ตอนนี้มีความแม่นยำถึง 1/64 ของวินาทีเท่านั้นดังนั้นข้อผิดพลาดในการปัดเศษการวัดของคุณจึงมีขนาดใหญ่ ใช้นาฬิกาจับเวลาแทน มีความแม่นยำเพียงไม่กี่นาโนวินาที ประการที่สองคุณกำลังวัดทั้งเวลาในการกระตุกรหัส (การโทรครั้งแรก) และทุกครั้งที่โทรตามมา ที่สามารถทิ้งค่าเฉลี่ยได้ (แม้ว่าในกรณีนี้ MAX หนึ่งแสนน่าจะเพียงพอที่จะเฉลี่ยภาระทางจิต แต่ก็เป็นวิธีปฏิบัติที่ไม่ดีที่จะรวมไว้ในค่าเฉลี่ย)
Eric Lippert

7
@Eric ข้อผิดพลาดในการปัดเศษจะอยู่ที่นั่นก็ต่อเมื่อในแต่ละการดำเนินการ DateTime ตอนนี้มีการใช้ Ticks ก่อนเริ่มและหลังสิ้นสุดจำนวนมิลลิวินาทีจะสูงพอที่จะแสดงความแตกต่างของประสิทธิภาพ
Akash Kava

1
หากใช้นาฬิกาจับเวลาขอแนะนำให้ทำตามบทความนี้เพื่อให้ได้ผลลัพธ์ที่แม่นยำ: codeproject.com/KB/testing/stopwatch-measure-precise.aspx
Zach Green

1
@ เอริกในขณะที่ฉันยอมรับว่ามันไม่ใช่เทคนิคการวัดที่แม่นยำที่สุด แต่เรากำลังพูดถึงลำดับของขนาดของความแตกต่าง MAX สูงพอที่จะลดความเบี่ยงเบนที่สำคัญ
Hugo Sereno Ferreira

คำตอบ:


43

เป็นไปได้ไหมว่า lambdas ด้านในไม่ได้ถูกรวบรวม!? นี่คือข้อพิสูจน์ของแนวคิด:

static void UsingCompiledExpressionWithMethodCall() {
        var where = typeof(Enumerable).GetMember("Where").First() as System.Reflection.MethodInfo;
        where = where.MakeGenericMethod(typeof(int));
        var l = Expression.Parameter(typeof(IEnumerable<int>), "l");
        var arg0 = Expression.Parameter(typeof(int), "i");
        var lambda0 = Expression.Lambda<Func<int, bool>>(
            Expression.Equal(Expression.Modulo(arg0, Expression.Constant(2)),
                             Expression.Constant(0)), arg0).Compile();
        var c1 = Expression.Call(where, l, Expression.Constant(lambda0));
        var arg1 = Expression.Parameter(typeof(int), "i");
        var lambda1 = Expression.Lambda<Func<int, bool>>(Expression.GreaterThan(arg1, Expression.Constant(5)), arg1).Compile();
        var c2 = Expression.Call(where, c1, Expression.Constant(lambda1));

        var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(c2, l);

        var c3 = f.Compile();

        var t0 = DateTime.Now.Ticks;
        for (int j = 1; j < MAX; j++)
        {
            var sss = c3(x).ToList();
        }

        var tn = DateTime.Now.Ticks;
        Console.WriteLine("Using lambda compiled with MethodCall: {0}", tn - t0);
    }

และตอนนี้การกำหนดเวลาคือ:

Using lambda:                            625020
Using lambda compiled:                 14687970
Using lambda combined:                   468765
Using lambda compiled with MethodCall:   468765

ว๊อท! ไม่เพียง แต่มันเร็วเท่านั้น แต่ยังเร็วกว่าแลมด้าพื้นเมืองอีกด้วย ( เกาหัว ).


แน่นอนว่าโค้ดข้างต้นนั้นเจ็บปวดเกินกว่าจะเขียนได้ มาทำเวทมนตร์ง่ายๆกันเถอะ:

static void UsingCompiledConstantExpressions() {
    var f1 = (Func<IEnumerable<int>, IEnumerable<int>>)(l => l.Where(i => i % 2 == 0));
    var f2 = (Func<IEnumerable<int>, IEnumerable<int>>)(l => l.Where(i => i > 5));
    var argX = Expression.Parameter(typeof(IEnumerable<int>), "x");
    var f3 = Expression.Invoke(Expression.Constant(f2), Expression.Invoke(Expression.Constant(f1), argX));
    var f = Expression.Lambda<Func<IEnumerable<int>, IEnumerable<int>>>(f3, argX);

    var c3 = f.Compile();

    var t0 = DateTime.Now.Ticks;
    for (int j = 1; j < MAX; j++) {
        var sss = c3(x).ToList();
    }

    var tn = DateTime.Now.Ticks;
    Console.WriteLine("Using lambda compiled constant: {0}", tn - t0);
}

และการกำหนดเวลาบางอย่าง VS2010 การเพิ่มประสิทธิภาพเปิดการแก้จุดบกพร่องปิด:

Using lambda:                            781260
Using lambda compiled:                 14687970
Using lambda combined:                   468756
Using lambda compiled with MethodCall:   468756
Using lambda compiled constant:          468756

ตอนนี้คุณสามารถโต้แย้งได้ว่าฉันไม่ได้สร้างนิพจน์ทั้งหมดแบบไดนามิก เพียงแค่การวิงวอน แต่ในตัวอย่างข้างต้นฉันสร้างนิพจน์ทั้งหมด และการกำหนดเวลาตรงกัน นี่เป็นเพียงทางลัดในการเขียนโค้ดน้อยลง


จากความเข้าใจของฉันสิ่งที่เกิดขึ้นคือเมธอด. คอมไพล์ () ไม่แพร่กระจายการคอมไพล์ไปยังแลมบ์ดาด้านในดังนั้นการเรียกใช้คงที่ของCreateDelegate. แต่เพื่อให้เข้าใจสิ่งนี้อย่างแท้จริงฉันชอบที่จะให้กูรู. NET แสดงความคิดเห็นเล็กน้อยเกี่ยวกับสิ่งภายในที่เกิดขึ้น

แล้วทำไมโอ้ทำไมตอนนี้ถึงเร็วกว่าแลมด้าพื้นเมือง!?


1
ฉันคิดว่าจะยอมรับคำตอบของตัวเองเนื่องจากเป็นคำตอบที่มีคะแนนโหวตมากที่สุด ฉันควรรออีกหน่อยดีไหม
Hugo Sereno Ferreira

เกี่ยวกับสิ่งที่เกิดขึ้นเมื่อคุณได้รับโค้ดเร็วกว่าแลมบ์ดาดั้งเดิมคุณอาจต้องการดูหน้านี้เกี่ยวกับไมโครเบนช์มาร์ก (ซึ่งไม่มีชื่อเฉพาะ Java แต่อย่างใด): code.google.com/p/caliper/wiki / JavaMicrobenchmarks
Blaisorblade

สำหรับสาเหตุที่แลมด้าคอมไพล์แบบไดนามิกเร็วกว่าฉันสงสัยว่า "การใช้แลมด้า" ที่ถูกเรียกใช้ก่อนจะถูกลงโทษด้วยการต้อง JIT โค้ดบางส่วน
Oskar Berggren

ฉันไม่รู้ว่าเกิดอะไรขึ้นเมื่อฉันทดสอบนิพจน์ที่คอมไพล์และสร้างผู้ร่วมประชุมสำหรับการตั้งค่าและรับจากฟิลด์และคุณสมบัติผู้ร่วมสร้างเป็นวิธีที่เร็วกว่าสำหรับคุณสมบัติ แต่การคอมไพล์เร็วกว่าเล็กน้อยสำหรับฟิลด์
nawfal

10

เมื่อเร็ว ๆ นี้ฉันถามคำถามที่เหมือนกันเกือบทั้งหมด:

ประสิทธิภาพของ Expression ที่คอมไพล์ถึงผู้ร่วมประชุม

วิธีแก้ปัญหาสำหรับฉันคือฉันไม่ควรเรียกCompileใช้Expressionแต่ฉันควรเรียกCompileToMethodมันและรวบรวมวิธีการExpressionไปยังstaticเมธอดในแอสเซมบลีแบบไดนามิก

ชอบมาก:

var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
  new AssemblyName("MyAssembly_" + Guid.NewGuid().ToString("N")), 
  AssemblyBuilderAccess.Run);

var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module");

var typeBuilder = moduleBuilder.DefineType("MyType_" + Guid.NewGuid().ToString("N"), 
  TypeAttributes.Public));

var methodBuilder = typeBuilder.DefineMethod("MyMethod", 
  MethodAttributes.Public | MethodAttributes.Static);

expression.CompileToMethod(methodBuilder);

var resultingType = typeBuilder.CreateType();

var function = Delegate.CreateDelegate(expression.Type,
  resultingType.GetMethod("MyMethod"));

อย่างไรก็ตามมันไม่เหมาะ ฉันไม่ได้ค่อนข้างบางอย่างที่จะประเภทนี้ว่า แต่ผมคิดว่าประเภทที่ถูกนำมาเป็นพารามิเตอร์โดยผู้ร่วมประชุมหรือส่งกลับโดยผู้ร่วมประชุมได้ที่จะเป็นpublicและไม่ทั่วไป จะต้องไม่ใช่แบบทั่วไปเนื่องจากเห็นได้ชัดว่าการเข้าถึงประเภททั่วไปSystem.__Canonซึ่งเป็นประเภทภายในที่ใช้โดย. NET ภายใต้ประทุนสำหรับประเภททั่วไปและเป็นการละเมิด "ต้องเป็นpublicกฎประเภท)

Compileสำหรับประเภทที่คุณสามารถใช้เห็นได้ชัดช้าลง ฉันตรวจพบด้วยวิธีต่อไปนี้:

private static bool IsPublicType(Type t)
{

  if ((!t.IsPublic && !t.IsNestedPublic) || t.IsGenericType)
  {
    return false;
  }

  int lastIndex = t.FullName.LastIndexOf('+');

  if (lastIndex > 0)
  {
    var containgTypeName = t.FullName.Substring(0, lastIndex);

    var containingType = Type.GetType(containgTypeName + "," + t.Assembly);

    if (containingType != null)
    {
      return containingType.IsPublic;
    }

    return false;
  }
  else
  {
    return t.IsPublic;
  }
}

แต่อย่างที่ฉันพูดมันไม่เหมาะและฉันก็ยังอยากรู้ว่าทำไมการรวบรวมวิธีการในแอสเซมบลีแบบไดนามิกบางครั้งจึงเรียงลำดับความสำคัญได้เร็วกว่า และบางครั้งฉันก็บอกว่าเพราะฉันเคยเห็นกรณีที่Expressionคอมไพล์ด้วยCompileเร็วพอ ๆ กับวิธีปกติ ดูคำถามของฉันสำหรับสิ่งนั้น

หรือหากมีใครรู้วิธีหลีกเลี่ยงpublicข้อ จำกัด"ไม่มีประเภท" ด้วยแอสเซมบลีไดนามิกก็ยินดีเช่นกัน


4

นิพจน์ของคุณไม่เท่ากันดังนั้นคุณจึงได้ผลลัพธ์ที่เบ้ ฉันเขียนม้านั่งทดสอบเพื่อทดสอบสิ่งนี้ การทดสอบรวมถึงการเรียกแลมบ์ดาปกตินิพจน์ที่คอมไพล์เทียบเท่านิพจน์คอมไพล์ที่ทำด้วยมือรวมถึงเวอร์ชันประกอบ ตัวเลขเหล่านี้ควรเป็นตัวเลขที่แม่นยำกว่า ที่น่าสนใจคือฉันไม่เห็นความแตกต่างระหว่างเวอร์ชันธรรมดาและเวอร์ชันประกอบมากนัก และนิพจน์ที่รวบรวมจะช้าลงตามธรรมชาติ แต่เพียงเล็กน้อยเท่านั้น คุณต้องมีอินพุตที่มากพอและจำนวนการวนซ้ำเพื่อให้ได้ตัวเลขที่ดี มันสร้างความแตกต่าง

สำหรับคำถามที่สองของคุณฉันไม่รู้ว่าคุณจะเพิ่มประสิทธิภาพได้อย่างไรดังนั้นฉันจึงไม่สามารถช่วยคุณได้ มันดูดีเท่าที่จะได้รับ

คุณจะพบคำตอบสำหรับคำถามที่สามของคุณในHandMadeLambdaExpression()วิธีการ ไม่ใช่นิพจน์ที่ง่ายที่สุดในการสร้างเนื่องจากวิธีการขยาย แต่ทำได้

using System;
using System.Collections.Generic;
using System.Linq;

using System.Diagnostics;
using System.Linq.Expressions;

namespace ExpressionBench
{
    class Program
    {
        static void Main(string[] args)
        {
            var values = Enumerable.Range(0, 5000);
            var lambda = GetLambda();
            var lambdaExpression = GetLambdaExpression().Compile();
            var handMadeLambdaExpression = GetHandMadeLambdaExpression().Compile();
            var composed = GetComposed();
            var composedExpression = GetComposedExpression().Compile();
            var handMadeComposedExpression = GetHandMadeComposedExpression().Compile();

            DoTest("Lambda", values, lambda);
            DoTest("Lambda Expression", values, lambdaExpression);
            DoTest("Hand Made Lambda Expression", values, handMadeLambdaExpression);
            Console.WriteLine();
            DoTest("Composed", values, composed);
            DoTest("Composed Expression", values, composedExpression);
            DoTest("Hand Made Composed Expression", values, handMadeComposedExpression);
        }

        static void DoTest<TInput, TOutput>(string name, TInput sequence, Func<TInput, TOutput> operation, int count = 1000000)
        {
            for (int _ = 0; _ < 1000; _++)
                operation(sequence);
            var sw = Stopwatch.StartNew();
            for (int _ = 0; _ < count; _++)
                operation(sequence);
            sw.Stop();
            Console.WriteLine("{0}:", name);
            Console.WriteLine("  Elapsed: {0,10} {1,10} (ms)", sw.ElapsedTicks, sw.ElapsedMilliseconds);
            Console.WriteLine("  Average: {0,10} {1,10} (ms)", decimal.Divide(sw.ElapsedTicks, count), decimal.Divide(sw.ElapsedMilliseconds, count));
        }

        static Func<IEnumerable<int>, IList<int>> GetLambda()
        {
            return v => v.Where(i => i % 2 == 0).Where(i => i > 5).ToList();
        }

        static Expression<Func<IEnumerable<int>, IList<int>>> GetLambdaExpression()
        {
            return v => v.Where(i => i % 2 == 0).Where(i => i > 5).ToList();
        }

        static Expression<Func<IEnumerable<int>, IList<int>>> GetHandMadeLambdaExpression()
        {
            var enumerableMethods = typeof(Enumerable).GetMethods();
            var whereMethod = enumerableMethods
                .Where(m => m.Name == "Where")
                .Select(m => m.MakeGenericMethod(typeof(int)))
                .Where(m => m.GetParameters()[1].ParameterType == typeof(Func<int, bool>))
                .Single();
            var toListMethod = enumerableMethods
                .Where(m => m.Name == "ToList")
                .Select(m => m.MakeGenericMethod(typeof(int)))
                .Single();

            // helpers to create the static method call expressions
            Func<Expression, ParameterExpression, Func<ParameterExpression, Expression>, Expression> WhereExpression =
                (instance, param, body) => Expression.Call(whereMethod, instance, Expression.Lambda(body(param), param));
            Func<Expression, Expression> ToListExpression =
                instance => Expression.Call(toListMethod, instance);

            //return v => v.Where(i => i % 2 == 0).Where(i => i > 5).ToList();
            var exprParam = Expression.Parameter(typeof(IEnumerable<int>), "v");
            var expr0 = WhereExpression(exprParam,
                Expression.Parameter(typeof(int), "i"),
                i => Expression.Equal(Expression.Modulo(i, Expression.Constant(2)), Expression.Constant(0)));
            var expr1 = WhereExpression(expr0,
                Expression.Parameter(typeof(int), "i"),
                i => Expression.GreaterThan(i, Expression.Constant(5)));
            var exprBody = ToListExpression(expr1);
            return Expression.Lambda<Func<IEnumerable<int>, IList<int>>>(exprBody, exprParam);
        }

        static Func<IEnumerable<int>, IList<int>> GetComposed()
        {
            Func<IEnumerable<int>, IEnumerable<int>> composed0 =
                v => v.Where(i => i % 2 == 0);
            Func<IEnumerable<int>, IEnumerable<int>> composed1 =
                v => v.Where(i => i > 5);
            Func<IEnumerable<int>, IList<int>> composed2 =
                v => v.ToList();
            return v => composed2(composed1(composed0(v)));
        }

        static Expression<Func<IEnumerable<int>, IList<int>>> GetComposedExpression()
        {
            Expression<Func<IEnumerable<int>, IEnumerable<int>>> composed0 =
                v => v.Where(i => i % 2 == 0);
            Expression<Func<IEnumerable<int>, IEnumerable<int>>> composed1 =
                v => v.Where(i => i > 5);
            Expression<Func<IEnumerable<int>, IList<int>>> composed2 =
                v => v.ToList();
            var exprParam = Expression.Parameter(typeof(IEnumerable<int>), "v");
            var exprBody = Expression.Invoke(composed2, Expression.Invoke(composed1, Expression.Invoke(composed0, exprParam)));
            return Expression.Lambda<Func<IEnumerable<int>, IList<int>>>(exprBody, exprParam);
        }

        static Expression<Func<IEnumerable<int>, IList<int>>> GetHandMadeComposedExpression()
        {
            var enumerableMethods = typeof(Enumerable).GetMethods();
            var whereMethod = enumerableMethods
                .Where(m => m.Name == "Where")
                .Select(m => m.MakeGenericMethod(typeof(int)))
                .Where(m => m.GetParameters()[1].ParameterType == typeof(Func<int, bool>))
                .Single();
            var toListMethod = enumerableMethods
                .Where(m => m.Name == "ToList")
                .Select(m => m.MakeGenericMethod(typeof(int)))
                .Single();

            Func<ParameterExpression, Func<ParameterExpression, Expression>, Expression> LambdaExpression =
                (param, body) => Expression.Lambda(body(param), param);
            Func<Expression, ParameterExpression, Func<ParameterExpression, Expression>, Expression> WhereExpression =
                (instance, param, body) => Expression.Call(whereMethod, instance, Expression.Lambda(body(param), param));
            Func<Expression, Expression> ToListExpression =
                instance => Expression.Call(toListMethod, instance);

            var composed0 = LambdaExpression(Expression.Parameter(typeof(IEnumerable<int>), "v"),
                v => WhereExpression(
                    v,
                    Expression.Parameter(typeof(int), "i"),
                    i => Expression.Equal(Expression.Modulo(i, Expression.Constant(2)), Expression.Constant(0))));
            var composed1 = LambdaExpression(Expression.Parameter(typeof(IEnumerable<int>), "v"),
                v => WhereExpression(
                    v,
                    Expression.Parameter(typeof(int), "i"),
                    i => Expression.GreaterThan(i, Expression.Constant(5))));
            var composed2 = LambdaExpression(Expression.Parameter(typeof(IEnumerable<int>), "v"),
                v => ToListExpression(v));

            var exprParam = Expression.Parameter(typeof(IEnumerable<int>), "v");
            var exprBody = Expression.Invoke(composed2, Expression.Invoke(composed1, Expression.Invoke(composed0, exprParam)));
            return Expression.Lambda<Func<IEnumerable<int>, IList<int>>>(exprBody, exprParam);
        }
    }
}

และผลลัพธ์บนเครื่องของฉัน:

แลมด้า:
  ผ่านไป: 340971948 123230 (มิลลิวินาที)
  ค่าเฉลี่ย: 340.971948 0.12323 (ms)
แลมบ์ดานิพจน์:
  ผ่านไป: 357077202 129051 (มิลลิวินาที)
  ค่าเฉลี่ย: 357.077202 0.129051 (ms)
นิพจน์แลมด้าทำด้วยมือ:
  ผ่านไป: 345029281 124696 (มิลลิวินาที)
  ค่าเฉลี่ย: 345.029281 0.124696 (ms)

ประกอบด้วย:
  ผ่านไป: 340409238 123027 (มิลลิวินาที)
  ค่าเฉลี่ย: 340.409238 0.123027 (มิลลิวินาที)
นิพจน์ประกอบ:
  ผ่านไป: 350800599 126782 (มิลลิวินาที)
  ค่าเฉลี่ย: 350.800599 0.126782 (มิลลิวินาที)
การแสดงออกที่ประกอบขึ้นด้วยมือ:
  ผ่านไป: 352811359 127509 (มิลลิวินาที)
  ค่าเฉลี่ย: 352.811359 0.127509 (ms)

3

ประสิทธิภาพแลมบ์ดาที่คอมไพล์เหนือผู้รับมอบสิทธิ์อาจช้าลงเนื่องจากโค้ดคอมไพล์ในรันไทม์อาจไม่ได้รับการปรับให้เหมาะสมอย่างไรก็ตามโค้ดที่คุณเขียนด้วยตนเองและที่คอมไพล์ผ่านคอมไพเลอร์ C # ได้รับการปรับให้เหมาะสม

ประการที่สองนิพจน์แลมบ์ดาหลายตัวหมายถึงวิธีการที่ไม่ระบุตัวตนหลายวิธีและการเรียกแต่ละวิธีใช้เวลาเพิ่มขึ้นเล็กน้อยในการประเมินวิธีการแบบตรง ตัวอย่างเช่นการโทร

Console.WriteLine(x);

และ

Action x => Console.WriteLine(x);
x(); // this means two different calls..

แตกต่างกันและด้วยค่าใช้จ่ายที่สองอีกเล็กน้อยจำเป็นต้องใช้จากมุมมองของคอมไพเลอร์จริงๆแล้วมันเป็นสองสายที่แตกต่างกัน ขั้นแรกให้เรียก x เองจากนั้นภายในคำสั่งเรียกของ x

ดังนั้นแลมด้าที่รวมกันของคุณจะมีประสิทธิภาพที่ช้ากว่านิพจน์แลมด้าเดี่ยวเล็กน้อย

และสิ่งนี้ไม่ขึ้นกับสิ่งที่ดำเนินการอยู่ภายในเนื่องจากคุณยังคงประเมินตรรกะที่ถูกต้อง แต่คุณกำลังเพิ่มขั้นตอนเพิ่มเติมเพื่อให้คอมไพเลอร์ดำเนินการ

แม้หลังจากคอมไพล์แผนผังนิพจน์แล้วก็จะไม่มีการปรับให้เหมาะสมและจะยังคงรักษาโครงสร้างที่ซับซ้อนเล็กน้อยการประเมินและการเรียกมันอาจมีการตรวจสอบความถูกต้องเพิ่มเติมการตรวจสอบค่าว่าง ฯลฯ ซึ่งอาจทำให้ประสิทธิภาพของนิพจน์แลมบ์ดาที่คอมไพล์ช้าลง


2
ถ้าคุณมองอย่างใกล้ชิดในการทดสอบคือการรวมฟังก์ชั่นแลมบ์ดาหลายและประสิทธิภาพการทำงานของมันอยู่ใกล้กับUsingLambdaCombined UsingLambdaเกี่ยวกับการปรับให้เหมาะสมฉันเชื่อว่าพวกเขาได้รับการจัดการโดยเอ็นจิน JIT ดังนั้นโค้ดที่สร้างรันไทม์ (หลังจากการคอมไพล์) ก็จะเป็นเป้าหมายของการเพิ่มประสิทธิภาพ JIT
Hugo Sereno Ferreira

1
การเพิ่มประสิทธิภาพ JIT และการเพิ่มประสิทธิภาพเวลาคอมไพล์เป็นสองสิ่งที่แตกต่างกันที่คุณสามารถปิดการเพิ่มประสิทธิภาพเวลาคอมไพล์ในการตั้งค่าโครงการ ประการที่สองการคอมไพล์นิพจน์อาจจะปล่อย MSIL แบบไดนามิกซึ่งจะช้าลงอีกเล็กน้อยเนื่องจากตรรกะและลำดับของการดำเนินการจะมีการตรวจสอบค่าว่างและความถูกต้องตามความต้องการ คุณสามารถดูตัวสะท้อนแสงเกี่ยวกับวิธีการคอมไพล์
Akash Kava

2
ในขณะที่เหตุผลของคุณฟังดูดีฉันต้องไม่เห็นด้วยกับคุณในปัญหานี้ (กล่าวคือลำดับความแตกต่างของขนาดไม่ได้เกิดจากการรวบรวมแบบคงที่) ประการแรกเนื่องจากหากคุณปิดใช้งานการเพิ่มประสิทธิภาพเวลาคอมไพล์จริงความแตกต่างยังคงมีอยู่ ประการที่สองเนื่องจากฉันได้พบวิธีเพิ่มประสิทธิภาพการสร้างแบบไดนามิกให้ช้าลงเพียงเล็กน้อย ให้ฉันพยายามทำความเข้าใจว่า "ทำไม" แล้วฉันจะโพสต์ผลลัพธ์
Hugo Sereno Ferreira
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.