Re[6]: Стоит ли увлекаться лямбдами?
От: Пельмешко Россия blog
Дата: 13.10.10 14:16
Оценка:
Здравствуйте, xvost, Вы писали:

X>К тому же (при определенных условиях) блокирующее, что немедленно сказывается на многопоточных приложениях


Евгений, а не поясните, блокирующее что конкретно?
При создание делегата какие-нибудь стабы методов генерируются и в глобальных синхронизованных таблицах хранятся или что-то подобное?

p.s. А вот функциональные значения в F# лишены проблем производительности создания экземпляров, так как являются просто экземплярами обычных классов, да и вызываются чуть быстрее (самый обычный виртуальный вызов, так же как в Nemerle, если мне не изменяет память). Вот только они не бывают никаких других сигнатур кроме как 'a -> 'b, поэтому придётся либо время тратить и нагружать GC, складывая аргументы в тупль, либо использовать каррированную форму, но тогда все вызовы становятся медленнее на один downcast (надо бы сравнить с вызовом делегата)
Re[8]: Стоит ли увлекаться лямбдами?
От: Пельмешко Россия blog
Дата: 13.10.10 14:22
Оценка: 12 (1) +1
Здравствуйте, Jolly Roger, Вы писали:

JR>Здравствуйте, _FRED_, Вы писали:


_FR>>То есть, да: использование API, основанного на вызове методов через делегаты (linq/Rx и многое уже что появилось) уже чревато (из-за дороговизны создания делегата) но использование этого API посредстром анонимных методов или лямбд с точки зрения производительности всё-тки не хуже использования этого же API посредством именованных методов?


JR>В NET альтернатива делегату, по-моему, только интерфейс, то есть выбор не особо велик, а созданный делегат, в том числе и на базе анонима, можно "кэшировать". Кстати, кто-нибудь интересовался, при использовании лямбд кэширование выполняется?


Да, выполняется конечно, но только если лямбда имеет CLI-представление в виде статического метода, то есть если ни замыкается на локальные переменные, ни на this.
Много подобных кэшируется:
   .Select(person => person.Name)

Интерфейсный вызов тоже не айс, в дотнете он дважды косвенный (где-то здесь обсуждалось давно), самая быстрая альтернатива — обычный виртуальный вызов экземплярного метода.
Re[8]: Стоит ли увлекаться лямбдами?
От: _FRED_ Черногория
Дата: 13.10.10 14:27
Оценка: 12 (1)
Здравствуйте, Jolly Roger, Вы писали:

JR>В NET альтернатива делегату, по-моему, только интерфейс, то есть выбор не особо велик, а созданный делегат, в том числе и на базе анонима, можно "кэшировать".


Иногда безсмысленно: если анонимная функция использует замыкания, то кешированиеособо не спасёт.

JR>Кстати, кто-нибудь интересовался, при использовании лямбд кэширование выполняется?


Тогда, когда анонимную функцию можно представить в виде статического метода, делегат на неё кэшируется. Других оптимизаций по кешированию анонимных функций я не видел.
Help will always be given at Hogwarts to those who ask for it.
Re[7]: Стоит ли увлекаться лямбдами?
От: Пельмешко Россия blog
Дата: 13.10.10 14:29
Оценка:
Здравствуйте, _FRED_, Вы писали:

_FR>Кстати, получается, если в примере топикстартера, например, мы заменим все лямбды на обычные, именованные методы или именованные методы во вспомагательных классах, то (поскольку и создание вспомагательных классов и создание делегатов останется) прироста производительности всё-таки не получится?


Если среди лямбд есть статические (у топикстартера таких нет), то получите проигрыш.
Если, конечно, сами не реализуете кэширование создание экземпляров делегатов.
Re[8]: Стоит ли увлекаться лямбдами?
От: Пельмешко Россия blog
Дата: 13.10.10 14:36
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Если, конечно, сами не реализуете кэширование создание экземпляров делегатов.


Ой ой, вру, создание делегатов из обычных статических методов тоже кэшируются конечно же
Re[7]: Стоит ли увлекаться лямбдами?
От: xvost Германия http://www.jetbrains.com/company/people/Pasynkov_Eugene.html
Дата: 13.10.10 16:27
Оценка: 67 (5)
Здравствуйте, Пельмешко, Вы писали:

П>Евгений, а не поясните, блокирующее что конкретно?

П>При создание делегата какие-нибудь стабы методов генерируются и в глобальных синхронизованных таблицах хранятся или что-то подобное?

Я не знаю что блокируется конкретно. Однако в стектрейсах четко видно как управление уходит в EE и там видны блокировки.
Причем еще очень зависит на что создается делегат.

Дешевле всего (и это без блокировки) на обычный метод класса.
Дорого — на метод интерфейса, на метод структуры, на статический метод, и на метод класса с дженериками. Причем разница доходит до 50 раз.

Подобное поведение подтверждается как экспериментально, так и источниками из CLR Team
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Re[6]: Стоит ли увлекаться лямбдами?
От: Sinix  
Дата: 14.10.10 09:13
Оценка: 11 (2)
Здравствуйте, xvost, Вы писали:


_FR>>Я так понимаю, что вряд-ли "Создание объекта-замыкания" отличается от просто создания (и инициализации) некоторого пользовательского объекта с такими же полями?


X>правильно.


Не удержался и проверил. 200 млн вызовов, релиз, запуск по Ctrl-F5.
Единственный нюанс — анонимные методы действительно кэшируются (легко проверить в рефлекторе). В остальном — всё ожидаемо.
         Direct:       82,0 ms
           Data:    1 301,0 ms
    Cached Data:       81,0 ms
          IData:    1 692,0 ms
   Cached IData:      704,0 ms
         Lambda:      865,0 ms
  Cached lambda:      651,0 ms
       Delegate:    2 906,0 ms
Cached delegate:      627,0 ms


На практике, конечно же, эти копейки моментально сожрёт остальной код, стоит только усложнить реализацию Foo().
И да, для числомолотилок делегаты неэффективны

  код
  class Program
  {
    interface IData
    {
      int Foo();
    }

    class Data: IData
    {
      public int val;

      public int Foo()
      {
        return val;
      }
    }

    const int Count = 200 * 1000 * 1000;

    static int Foo()
    {
      return 0;
    }

    static void Main(string[] args)
    {
      Measure("Direct", () =>
      {
        for (int i = 0; i < Count; i++)
        {
          Foo();
        }
      });
      Measure("Data", () =>
      {
        for (int i = 0; i < Count; i++)
        {
          new Data()
          {
            val = i
          }.Foo();
        }
      });
      Measure("Cached Data", () =>
      {
        Data data = new Data()
        {
          val = 0
        };
        for (int i = 0; i < Count; i++)
        {
          data.Foo();
        }
      });
      Measure("IData", () =>
      {
        for (int i = 0; i < Count; i++)
        {
          ((IData)new Data()
          {
            val = i
          }).Foo();
        }
      });
      Measure("Cached IData", () =>
      {
        IData data = new Data()
        {
          val = 0
        };
        for (int i = 0; i < Count; i++)
        {
          data.Foo();
        }
      });
      Measure("Lambda", () =>
      {
        for (int i = 0; i < Count; i++)
        {
          Func<int> a = () => i;
          a();
        }
      });
      Measure("Cached lambda", () =>
      {
        int c = 5;
        Func<int> a = () => c;
        for (int i = 0; i < Count; i++)
        {
          a();
        }
      });
      Measure("Delegate", () =>
      {
        for (int i = 0; i < Count; i++)
        {
          Func<int> a = Foo;
          a();
        }
      });
      Measure("Cached delegate", () =>
      {
        Func<int> a = Foo;
        for (int i = 0; i < Count; i++)
        {
          a();
        }
      });
      Console.ReadKey();
    }

    static void Measure(string name, Action callback)
    {
      Stopwatch sw = Stopwatch.StartNew();
      callback();
      sw.Stop();
      Console.WriteLine("{0,15}: {1,10:#,##0.0###} ms", name, sw.ElapsedMilliseconds);
    }
  }

Re[7]: Стоит ли увлекаться лямбдами?
От: Sinix  
Дата: 14.10.10 09:46
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Евгений, а не поясните, блокирующее что конкретно?

П>При создание делегата какие-нибудь стабы методов генерируются и в глобальных синхронизованных таблицах хранятся или что-то подобное?
Кмк, это проблемы EE, а не делегатов.

П>p.s. А вот функциональные значения в F# лишены проблем производительности создания экземпляров, так как являются просто экземплярами обычных классов, да и вызываются чуть быстрее (самый обычный виртуальный вызов, так же как в Nemerle, если мне не изменяет память).

Не совсем так. В il действительно callvirt, но JITтится оно аналогично call. Иначе они по производительности совпадали бы с обычными делегатами/интерфейсами.
См http://www.rsdn.ru/forum/dotnet/3996529.1.aspx
Автор: _FRED_
Дата: 13.10.10
Re[7]: Стоит ли увлекаться лямбдами?
От: Пельмешко Россия blog
Дата: 14.10.10 10:15
Оценка: 6 (1)
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, xvost, Вы писали:



_FR>>>Я так понимаю, что вряд-ли "Создание объекта-замыкания" отличается от просто создания (и инициализации) некоторого пользовательского объекта с такими же полями?


X>>правильно.


S>Не удержался и проверил. 200 млн вызовов, релиз, запуск по Ctrl-F5.

S>Единственный нюанс — анонимные методы действительно кэшируются (легко проверить в рефлекторе). В остальном — всё ожидаемо.

Я нюанс у Вас заметил, вот этот код:
S>      Measure("Data", () =>
S>      {
S>        for (int i = 0; i < Count; i++)
S>        {
S>          new Data()
S>          {
S>            val = i
S>          }.Foo();
S>        }
S>      });

И вот этот:
S>      Measure("Lambda", () =>
S>      {
S>        for (int i = 0; i < Count; i++)
S>        {
S>          Func<int> a = () => i;
S>          a();
S>        }
S>      });

Должны быть логически равнозначны (раз хотим проверить скорость создания делегата), однако здесь компилятор C# успешно применяет кэширование экземпляра делегата, даже не смотря на замыкание! Замыкание происходит на переменную итерации, которая находится вне блока цикла, а значит все делегаты могут разделить между собой класс-замыкание, да и сам экземпляр делегата... Сделайте вот так:
for (int i = 0; i < Count; i++)
{
    int j = i;
    Func<int> a = () => j;
    a();
}
...и удивитесь как изменится результат

Да и кэширование у Вас энергичное, не равнозначное тому, что делает C#...
Re[8]: Стоит ли увлекаться лямбдами?
От: Sinix  
Дата: 14.10.10 10:19
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Должны быть логически равнозначны (раз хотим проверить скорость создания делегата), однако здесь компилятор C# успешно применяет кэширование экземпляра делегата, даже не смотря на замыкание!

Ну да, собственно это и хотел показать, где-то выше по дискуссии видел обратное утверждение
Re[8]: Стоит ли увлекаться лямбдами?
От: hardcase Пират http://nemerle.org
Дата: 14.10.10 10:24
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Не совсем так. В il действительно callvirt, но JITтится оно аналогично call. Иначе они по производительности совпадали бы с обычными делегатами/интерфейсами.

S>См http://www.rsdn.ru/forum/dotnet/3996529.1.aspx
Автор: _FRED_
Дата: 13.10.10


Инструкция callvirt нужна чтобы генерировать NullReferenceException при this равном null.
Если меня не подводит память, то C++/CLI использует call для вызова невиртуальных методов и потому их можно позвать для null.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[8]: Стоит ли увлекаться лямбдами?
От: Sinix  
Дата: 14.10.10 10:27
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Да и кэширование у Вас энергичное, не равнозначное тому, что делает C#...


Это вы про Cached lambda vs Cached delegate? Там же копейки.

Если про Cached data — то там как раз jit оптимизирует callvirt, как я вам уже писал
Re[8]: Стоит ли увлекаться лямбдами?
От: Пельмешко Россия blog
Дата: 14.10.10 10:33
Оценка:
Здравствуйте, Sinix, Вы писали:

П>>p.s. А вот функциональные значения в F# лишены проблем производительности создания экземпляров, так как являются просто экземплярами обычных классов, да и вызываются чуть быстрее (самый обычный виртуальный вызов, так же как в Nemerle, если мне не изменяет память).

S>Не совсем так. В il действительно callvirt, но JITтится оно аналогично call. Иначе они по производительности совпадали бы с обычными делегатами/интерфейсами.

Что значит "JITтится оно аналогично call"?
Почему для F# что-то "JITтится" иначе?

Для меня всегда было так (в порядке производительности):

  1. Вызов virtual-метода = косвенный вызов, самый быстрый из косвенных в CLR;
  2. Вызов метода через интерфейс = дважды косвенный вызов, чуть медленнее;
  3. Вызов делегата = косвенный вызов + некоторый оверхед [ + ещё оверхед из-за подстановки Target'а ]

В F# используются обычные вызовы виртуальных методов, через callvirt.
Re[9]: Стоит ли увлекаться лямбдами?
От: Sinix  
Дата: 14.10.10 10:34
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Инструкция callvirt нужна чтобы генерировать NullReferenceException при this равном null.

Не только. Ещё и чтоб не ломать код при внезапном

    class Data2: Data
    {
      public override int Foo() // Data.Foo - virtual
      {
        return 42;
      }
    }


H>Если меня не подводит память, то C++/CLI использует call для вызова невиртуальных методов и потому их можно позвать для null.

Да.

Чуть-чуть ссылок (вы-то уже наверняка читали, но кто-нить может заинтересоваться):
http://bartdesmet.net/blogs/bart/archive/2007/02/27/c-quiz-call-versus-callvirt.aspx
http://blogs.msdn.com/b/ericlippert/archive/2007/08/17/subtleties-of-c-il-codegen.aspx
http://stackoverflow.com/questions/845657/why-is-the-c-compiler-emitting-a-callvirt-instruction-for-a-gettype-method-cal
Re[9]: Стоит ли увлекаться лямбдами?
От: Sinix  
Дата: 14.10.10 10:49
Оценка: 71 (4)
Здравствуйте, Пельмешко, Вы писали:

П>Что значит "JITтится оно аналогично call"?

П>Почему для F# что-то "JITтится" иначе?
П>В F# используются обычные вызовы виртуальных методов, через callvirt.
Это фича рантайма, а не языка.
Посмотрите ссылки тут
Автор: Sinix
Дата: 14.10.10


П>Для меня всегда было так (в порядке производительности):

  1. Вызов virtual-метода = косвенный вызов, самый быстрый из косвенных в CLR;
  2. Вызов метода через интерфейс = дважды косвенный вызов, чуть медленнее;
  3. Вызов делегата = косвенный вызов + некоторый оверхед [ + ещё оверхед из-за подстановки Target'а ]

Ну, на самом деле оно не так, но имело некоторое отношение к реальности — для 1го фреймворка
http://msdn.microsoft.com/en-us/library/ms973852.aspx (см табличку)
Ещё пруфы
http://msdn.microsoft.com/en-us/library/ff647790.aspx
http://www.sturmnet.org/blog/2005/09/01/

Сейчас — примерно так:
http://tips.x-tensive.com/2008/10/method-call-performance.html
http://stackoverflow.com/questions/216008/c-virtual-function-invocation-is-even-faster-than-a-delegate-invocation
Ещё (из кэша гугля, оригинал сдох)
http://stackoverflow.com/questions/2082735/performance-of-calling-delegates-vs-methods
Re: Стоит ли увлекаться лямбдами?
От: Воронков Василий Россия  
Дата: 15.10.10 12:56
Оценка: +1
Здравствуйте, Neco, Вы писали:

Проблем в целом с таким подходом нет. С т.з. производительности все зависит от того, что пишите. Каких-то кардинальных проблем с производительностью нет.
Если судить о приведенном коде, то — буду неоригинален. Стоит прежде всего продумать форматирование. Убирать лишние скобочки, которые затрудняют чтение. И я бы на экономил на переносах строк — в идеале хочется, чтобы тело каждой лямбды было четко оформлено. Я обычно форматирую как-то так:

Fun(x =>
    {
        Fun2(y =>
            {

            });
    });


Да, ну и хорошо при этом на забывать о том, что лямбда "повторенная дважды" как-то уже теряет свою ценность Какие-то вещи можно оформлять и в виде старых добрых неанонимных функций. Заодно и клиентский код будет выглядеть чище.
Re[10]: Стоит ли увлекаться лямбдами?
От: Воронков Василий Россия  
Дата: 15.10.10 13:05
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Сейчас — примерно так:

S>http://tips.x-tensive.com/2008/10/method-call-performance.html
S>http://stackoverflow.com/questions/216008/c-virtual-function-invocation-is-even-faster-than-a-delegate-invocation
S>Ещё (из кэша гугля, оригинал сдох)
S>http://stackoverflow.com/questions/2082735/performance-of-calling-delegates-vs-methods

Гм, вызов через интерфейс стал медленее вызова через делегат? Интересно.
Re[11]: Стоит ли увлекаться лямбдами?
От: Sinix  
Дата: 15.10.10 13:18
Оценка: +1
Здравствуйте, Воронков Василий, Вы писали:

ВВ>Гм, вызов через интерфейс стал медленее вызова через делегат? Интересно.

Скорее, вызов делегата стал быстрее вызова ч/з интерфейс
Re[2]: Стоит ли увлекаться лямбдами?
От: Аноним  
Дата: 16.10.10 09:46
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>Здравствуйте, Neco, Вы писали:


N>>в данном случае, это как бы Presentation, но планирую использовать такой же подход на уровне контроллеров и вообще по жизни.

GZ>У лямбд есть существенный недостаток. Это неимение имени. Имя — это ясный абстрактный коментарий того, что данный код делает. Неимение имени существенно снижает читабельность а следовательно сопровождаемость кода. Поэтому для сложных конструкций, где с одного взгляда не поймешь что делегат делает,
именованные функции рулят.



имеете ввиду замена на стандартный foreach?
Re[4]: Стоит ли увлекаться лямбдами?
От: Аноним  
Дата: 16.10.10 09:54
Оценка:
Здравствуйте, xvost, Вы писали:

X>Все это во-первых весьма и весьма жрет ЦПУ (учитывая подводные и широко неизвестные факты в EE про создания лямбд над методом класса с дженериками)

X>А во-вторых объекты замыкания, если их много, весьма основательно мусорят, что приводит к mid-life crisis'у

а что такое EE?
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.