Производительность лямбда функций.
От: ZAMUNDA Земля для жалоб и предложений
Дата: 26.11.14 23:31
Оценка:
Здравствуйте, друзья.

Мне, если честно, немножко лень переводить то что я на stackoverflow нашёл, а доделать проект очень хочется.
Так вот, вопрос простой: есть функция сложная, в ней есть дублирование кода (4...6 инструкций с ветвлением). Я, как старый VB6 программист, разделил бы функцию на несколько и сделал их вызов, не раздумывая. Но как человека современного меня интересует, а не сделать-ли всё на лямбдах. Причём лямбды меня прельщают ещё тем, что у них есть доступ ко всем локальным переменным родительской функции, и, значит, передача параметров не нужна.
Собственно вопрос: а что работает быстрее? — Вызов нескольких маленьких функций или вызов и создание лямбд?

Заранее спасибо.
Наука изощряет ум; ученье вострит память.
(c) Козьма Прутков
Re: Производительность лямбда функций.
От: Pavel Dvorkin Россия  
Дата: 27.11.14 02:53
Оценка:
Здравствуйте, ZAMUNDA, Вы писали:

ZAM>Собственно вопрос: а что работает быстрее? — Вызов нескольких маленьких функций или вызов и создание лямбд?


Смотря каких лямбд

http://rsdn.ru/forum/flame.comp/4098976.1
Автор: Pavel Dvorkin
Дата: 29.12.10
With best regards
Pavel Dvorkin
Re: Производительность лямбда функций.
От: ZagSer168 Ниоткуда https://x.u168.ru
Дата: 27.11.14 04:08
Оценка:
В общем случае вызов функций быстрее создания лямбд, т.к. для лямбд создаются временные объекты.
Но в частных случаях возможны нюансы.
Одиночное наследование — это всего лишь частный случай множественного наследования.
Re: Производительность лямбда функций.
От: Sinix  
Дата: 27.11.14 07:13
Оценка: 60 (6) +1
Здравствуйте, ZAMUNDA, Вы писали:

ZAM>Собственно вопрос: а что работает быстрее? — Вызов нескольких маленьких функций или вызов и создание лямбд?


Сомневаешься —
  проверяй:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace ConsoleApplication1
{
    interface IProgram
    {
        int CallInterface(int a);
        int CallInterface<T>(int a);
    }

    interface IProgram<T>
    {
        T CallInterface(T a);
    }
    class Program2 : Program
    {
        public override int CallVirtual(int a)
        {
            return a + 1;
        }
        public override int CallInterface(int a)
        {
            return a + 1;
        }
        public override int CallInterface<T>(int a)
        {
            return a + 1;
        }
    }
    class Program : IProgram, IProgram<int>
    {
        private static int Call(int a)
        {
            return a + 1;
        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        private static int CallNoInline(int a)
        {
            return a + 1;
        }
        private static int Call<T>(int a)
        {
            return a + 1;
        }

        private int CallInst(int a)
        {
            return a + 1;
        }
        [MethodImpl(MethodImplOptions.NoInlining)]
        private int CallInstNoInline(int a)
        {
            return a + 1;
        }
        private int CallInst<T>(int a)
        {
            return a + 1;
        }

        public virtual int CallVirtual(int a)
        {
            return a + 1;
        }
        public virtual int CallInterface(int a)
        {
            return a + 1;
        }
        public virtual int CallInterface<T>(int a)
        {
            return a + 1;
        }


        // some work : i=>i+1
        static void Main(string[] args)
        {
            int count = 10 * 1000 * 1000;

            Measure("raw", () =>
            {
                int a = 0;
                for (int i = 0; i < count; i++) a = a + 1;
                return count;
            });

            Console.WriteLine();

            Measure("call", () =>
            {
                int a = 0;
                for (int i = 0; i < count; i++) a = Call(a);
                return count;
            });
            Measure("generic call", () =>
            {
                int a = 0;
                for (int i = 0; i < count; i++) a = Call<object>(a);
                return count;
            });

            Measure("instance call", () =>
            {
                int a = 0;
                var p = new Program();
                for (int i = 0; i < count; i++) a = p.CallInst(a);
                return count;
            });
            Measure("instance generic call", () =>
            {
                int a = 0;
                var p = new Program();
                for (int i = 0; i < count; i++) a = p.CallInst<object>(a);
                return count;
            });

            Measure("call (no inline)", () =>
            {
                int a = 0;
                for (int i = 0; i < count; i++) a = CallNoInline(a);
                return count;
            });
            Measure("instance call (no inline)", () =>
            {
                int a = 0;
                var p = new Program();
                for (int i = 0; i < count; i++) a = p.CallInstNoInline(a);
                return count;
            });

            Console.WriteLine();

            Measure("instance virtual call", () =>
            {
                int a = 0;
                var p = new Program();
                for (int i = 0; i < count; i++) a = p.CallVirtual(a);
                return count;
            });
            Measure("derived virtual call", () =>
            {
                int a = 0;
                var p = new Program2();
                for (int i = 0; i < count; i++) a = p.CallVirtual(a);
                return count;
            });
            Measure("interface call", () =>
            {
                int a = 0;
                IProgram p = new Program();
                for (int i = 0; i < count; i++) a = p.CallInterface(a);
                return count;
            });
            Measure("derived interface call", () =>
            {
                int a = 0;
                IProgram p = new Program2();
                for (int i = 0; i < count; i++) a = p.CallInterface(a);
                return count;
            });

            Measure("generic interface call", () =>
            {
                int a = 0;
                IProgram<int> p = new Program();
                for (int i = 0; i < count; i++) a = p.CallInterface(a);
                return count;
            });
            Measure("derived generic interface call", () =>
            {
                int a = 0;
                IProgram<int> p = new Program2();
                for (int i = 0; i < count; i++) a = p.CallInterface(a);
                return count;
            });

            Console.WriteLine();

            Measure(" interface generic call", () =>
            {
                int a = 0;
                IProgram p = new Program();
                for (int i = 0; i < count; i++) a = p.CallInterface<object>(a);
                return count;
            });
            Measure("derived interface generic call", () =>
            {
                int a = 0;
                IProgram p = new Program2();
                for (int i = 0; i < count; i++) a = p.CallInterface<object>(a);
                return count;
            });

            Console.WriteLine();

            Measure("lambda (cached)", () =>
            {
                int a1 = 0;
                Func<int, int> x = a => a + 1;
                for (int i = 0; i < count; i++) a1 = x(a1);
                return count;
            });
            Measure("lambda (new)", () =>
            {
                int a1 = 0;
                for (int i = 0; i < count; i++)
                {
                    Func<int, int> x = a => a + 1;
                    a1 = x(a1);
                };
                return count;
            });
            Measure("lambda (closure)", () =>
            {
                int a1 = 0;
                var t = 0;
                for (int i = 0; i < count; i++)
                {
                    t = 1;
                    Func<int, int> x = a => a + t;
                    a1 = x(a1);
                };
                return count;
            });
            Measure("lambda (closure local)", () =>
            {
                int a1 = 0;
                for (int i = 0; i < count; i++)
                {
                    var t = 1;
                    Func<int, int> x = a => a + t;
                    a1 = x(a1);
                };
                return count;
            });

            Measure("Func (cached)", () =>
            {
                int a = 0;
                Func<int, int> x = Call;
                for (int i = 0; i < count; i++) a = x(a);
                return count;
            });
            Measure("Func (new)", () =>
            {
                int a = 0;
                for (int i = 0; i < count; i++)
                {
                    Func<int, int> x = Call;
                    a = x(a);
                };
                return count;
            });

            Console.Write("Done...");
            Console.ReadKey();
        }

        static void Measure(string name, Func<long> callback)
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            var sw = Stopwatch.StartNew();
            var tmp = callback();
            sw.Stop();

            Console.WriteLine("{0,30}: {1,5}ms, ips: {2,16:N} : {3,9}", name, sw.ElapsedMilliseconds, tmp / sw.Elapsed.TotalSeconds, tmp);
        }
    }
}


.net 4.5.1, release, no debugger, any cpu:
                           raw:     2ms, ips: 3 430 884 825,20 :  10000000

                          call:     2ms, ips: 3 462 843 687,24 :  10000000
                  generic call:     3ms, ips: 3 305 566 574,11 :  10000000
                 instance call:     3ms, ips: 3 290 772 673,42 :  10000000
         instance generic call:     2ms, ips: 3 422 782 037,24 :  10000000
              call (no inline):    20ms, ips:   496 428 199,11 :  10000000
     instance call (no inline):    21ms, ips:   463 226 744,86 :  10000000

         instance virtual call:    19ms, ips:   515 820 205,71 :  10000000
          derived virtual call:    19ms, ips:   503 443 553,91 :  10000000
                interface call:    26ms, ips:   383 765 197,10 :  10000000
        derived interface call:    23ms, ips:   429 856 084,18 :  10000000
        generic interface call:    25ms, ips:   385 528 791,29 :  10000000
derived generic interface call:    25ms, ips:   386 239 074,26 :  10000000

        interface generic call:   106ms, ips:    93 702 446,01 :  10000000
derived interface generic call:   105ms, ips:    94 401 250,63 :  10000000

               lambda (cached):    34ms, ips:   290 324 641,01 :  10000000
                  lambda (new):    43ms, ips:   231 436 479,94 :  10000000
              lambda (closure):    32ms, ips:   311 350 920,51 :  10000000
        lambda (closure local):   120ms, ips:    82 701 015,15 :  10000000
                 Func (cached):    34ms, ips:   290 684 154,23 :  10000000
                    Func (new):    74ms, ips:   133 860 386,29 :  10000000
Done...


Маленький квестЪ: поменять в "Func (cached)" строчку
                Func<int, int> x = Call;

так, чтобы стоимость вызова приблизилась к "call (no inline)"

P.S. Когда меряете — следите за опечатками! Одна буква (у меня это было "a1 = x(i)" вместо "a1 = x(a1)"), и на основе неправильных тестов делаются очень неправильные выводы
Re[2]: Производительность лямбда функций.
От: vorona  
Дата: 27.11.14 09:41
Оценка: 45 (1)
Здравствуйте, Sinix, Вы писали:

S>Маленький квестЪ: поменять в "Func (cached)" строчку

S>
S>                Func<int, int> x = Call;
S>

S>так, чтобы стоимость вызова приблизилась к "call (no inline)"

  Func<int, int> x = new Program().CallInst
Re[3]: Производительность лямбда функций.
От: Sinix  
Дата: 27.11.14 10:03
Оценка: 12 (1) +1
Здравствуйте, vorona, Вы писали:

S>>так, чтобы стоимость вызова приблизилась к "call (no inline)"

V>
  Func<int, int> x = new Program().CallInst


Именно
В referencesource есть подсказка:
        // _methodPtr is a pointer to the method we will invoke
        // It could be a small thunk if this is a static or UM call                  <-- here
        #if !FEATURE_CORECLR
        [System.Runtime.ForceTokenStabilization]
        #endif //!FEATURE_CORECLR
        [System.Security.SecurityCritical]
        internal IntPtr _methodPtr;
        
        // In the case of a static method passed to a delegate, this field stores
        // whatever _methodPtr would have stored: and _methodPtr points to a         <-- and here
        // small thunk which removes the "this" pointer before going on
        // to _methodPtrAux.
        #if !FEATURE_CORECLR
        [System.Runtime.ForceTokenStabilization]
        #endif //!FEATURE_CORECLR
        [System.Security.SecurityCritical]
        internal IntPtr _methodPtrAux;


Этот нюанс кстати учтён в c#6, правда, не с первой попытки
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.