Re[5]: Стоит ли увлекаться лямбдами?
От: Пельмешко Россия blog
Дата: 16.10.10 10:45
Оценка:
Здравствуйте, Аноним, Вы писали:

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


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

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

А>а что такое EE?


Execution Engine
Re[3]: Стоит ли увлекаться лямбдами?
От: GlebZ Россия  
Дата: 17.10.10 18:31
Оценка:
Здравствуйте, Аноним, Вы писали:

А>имеете ввиду замена на стандартный foreach?

Они не имеют никакого отношения друг-другу.
Re[4]: Стоит ли увлекаться лямбдами?
От: VladD2 Российская Империя www.nemerle.org
Дата: 21.10.10 17:37
Оценка:
Здравствуйте, Neco, Вы писали:

N>Кстати, раз уж я спрашиваю про идею, то пожалуй скажу для чего такая конструкция нужна (чтобы можно было исходя из требования что-то предлагать, а не из кода). По сути нужно иметь возможность повторно использовать элементы дизайна. В моём случае это Asp.Net MVC, хотя от оригинала уже мало что осталось. Проблема зародилась в том, что при объединении нескольких mvc-контролов в один более сложный (например, кнопка с текстом — т.е. по сути гиперлинк с картинкой и текстом внутри), предлагалось либо не париться и дублировать, либо писать своё расширение, код которого в общем-то, будет состоять из бубнов с TagBuilder'ом. И сделать такой код повторно используемым я не понял как. Постепенно пришёл к тому, что всякое расширение любого html элемента, который может в себе что-то содержать, должно принимать параметр-лямбду, типа subHtml. Если сообщать вместо лямбды string, то визуально нарушается ход событий (т.е. сначала готовим начинку, а потом её используем). Таким образом такая конструкция и случилась.


То что ты описал — это попытка добиться ленивого (отложенного) выполнения методом "закат солнца вручную".

В принципе в шарпе другого выхода почти нет. Для тех же целей можно использовтаь интерфейсы или классы, но без замыканий все это бесполезно.

А вообще, у вас явно получается некий ДСЛ. Только реализуете вы его доступными средствами языка. От того и столько "шума".

ЗЫ

Кстати, что касается DSL-ей в контексте работы с ASP.NET, то очень интересно глянуть на Nemerle on rails. Там, средствами Nemerle, реализовано срезу несколько DSL-ей, начиная от DSL-реструктуризации БД, и кончая передачей данных из контроллеров в представления. Кроме того там используется альтернативный движок рендеренга который позволяет избавиться от проблемы с которыми вы боритесь.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Стоит ли увлекаться лямбдами?
От: VladD2 Российская Империя www.nemerle.org
Дата: 21.10.10 18:11
Оценка:
Здравствуйте, xvost, Вы писали:

X>Да. Создание делегата — во много раз более дорогое удовольствие чем создание простого объекта


Ты уж людей так сильно не пугай. Овершэд от вызова лямбд не такой уж большой чтобы об этом вообще задумываться в 99% случаев. К тому же если это множественный вызов (работа со списками, например), то создание лямбды происходит один раз, а не при каждом вызове.

Так что в очень критических местах лямбдам конечно не место. Но по жизни таких мест не много. Некоторые даже комбинаторные парсеры на базе шарповских лямбды умудряются делать. И ничего — работает (хотя я бы так делать никогда не стал).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: Стоит ли увлекаться лямбдами?
От: VladD2 Российская Империя www.nemerle.org
Дата: 21.10.10 18:16
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

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


Обычные классы с виртуальными методами. Они шустрее интерфейсов.
Именно так реализованы функциональные типы в Nemerle и (наверно) в F#.

Раньше это давало очень большой выигрыш (в 3-4 раза), но в 3.5 фрэймворке вызов лямбды был оптимизирован и на сегодня (плюс процессоры Core 2 и выше) разницу заметить сложно.

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


В C# выполняется.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: Стоит ли увлекаться лямбдами?
От: VladD2 Российская Империя www.nemerle.org
Дата: 21.10.10 18:18
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Интерфейсный вызов тоже не айс, в дотнете он дважды косвенный (где-то здесь обсуждалось давно), самая быстрая альтернатива — обычный виртуальный вызов экземплярного метода.


Эта информация устарела (к дотнет 3.5 х86).

Последние тесты показывают, что разницу в скорости виртуального вызова и лямбды заметить практически невозможно. Так что что-то там в МС все же подшаманили.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Стоит ли увлекаться лямбдами?
От: VladD2 Российская Империя www.nemerle.org
Дата: 21.10.10 18:20
Оценка:
Здравствуйте, Пельмешко, Вы писали:

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


Эта информация устарела. Я рядом об этом писал уже. Разницы в скорости почти нет.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[10]: Стоит ли увлекаться лямбдами?
От: VladD2 Российская Империя www.nemerle.org
Дата: 21.10.10 19:23
Оценка: 30 (2)
Здравствуйте, Sinix, Вы писали:

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

S>

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

S>Ну, на самом деле оно не так, но имело некоторое отношение к реальности — для 1го фреймворка


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

S>http://stackoverflow.com/questions/216008/c-virtual-function-invocation-is-even-faster-than-a-delegate-invocation

Воспроизвел тест из вышеприведенной ссылке на C# и Nemerle (оба теста под управлением 3.5-го фрэймворка).
Результаты...
Nemerle-тест:
Virtual call took: 661
Delegate call took: 782
Functional type call took: 642

Virtual call took: 642
Delegate call took: 787
Functional type call took: 640

C#-тест:
651
852
638
852

Первый вызов виртуальный, второй делегата.

Я уж не знаю какого черта у немерла скорость вызова делегата оказалась чуть шустрее шарповской (думаю просто расклад (с) Поркчик Ржевский).

Но главное что можно вывести из этих тестов, что на современном железе (а у меня Core 2 2.8 Ghz) и относительно современном фрэймворке (я проводил измерения на 3.5-ом) разница между скоростью вызова делегата и виртуальным взовом не столь существенна (как была ранее).

И чем более существенны вычисления идущие рядом, тем меньше влияние она оказывает на скорость самого приложения.

Кроме того я произвел смелые научные эксперементы...
Так я сделал метод с предикатом обобщенным:
public DoSomething[T](predicator : Predicate[T], word : T) : int

Но это никак не сказалось на скорости работы вычислениях.
Возможно на скорости создания замыкания это и сказывается, но никак не на скорости вызова.

Более интересным оказался другой эксперимент... Я сделал обобщенным объект Foo и использовал его параметр типов в методе методах использующих делегат и функциональный тип. Результат получился просто феерическим:
Virtual call took: 1065
Delegate call took: 781
Functional type call took: 640

Virtual call took: 1065
Delegate call took: 781
Functional type call took: 640

Замедлился виртуальный вызов, но вызовы выполняемые через делегат и функциональный объект выполнялись за то же время, что и раньше! И это при том, что параметр типов не используется явно внутри виртуального вызова, а в других методах используется.

Немерловый тест:
using System;
using System.Console;
using System.Diagnostics;

class Foo
{
  public virtual IsTokenChar(word : string) : bool 
  {
    String.IsNullOrEmpty(word);
  }

  // this is a template method
  public DoSomething(word : string) : int
  {
    unchecked
    {
      mutable trueCount = 0;
      
      for (mutable i = 0; i < Repeat; ++i)
        when (IsTokenChar(word))
          ++trueCount;
      
      trueCount;
    }
  }

  public DoSomething(predicator : Predicate[string], word : string) : int
  {
    unchecked
    {
      mutable trueCount = 0;
      
      for (mutable i = 0; i < Repeat; ++i)
        when (predicator(word))
          ++trueCount;
          
      trueCount;
    }
  }

  public DoSomethingHof(predicator : string -> bool, word : string) : int
  {
    unchecked
    {
      mutable trueCount = 0;
      
      for (mutable i = 0; i < Repeat; ++i)
        when (predicator(word))
          ++trueCount;
          
      trueCount;
    }
  }

  private Repeat = 200000000;
}

module Program
{
  Main() : void
  {
    def f = Foo();

    repeat (2)
    {
      def sw = Stopwatch.StartNew();
      _ = f.DoSomething(null);
      sw.Stop();
      WriteLine($"Virtual call took: $(sw.ElapsedMilliseconds)");


      def sw = Stopwatch.StartNew();
      _ = f.DoSomething(str => String.IsNullOrEmpty(str), null);
      sw.Stop();
      WriteLine($"Delegate call took: $(sw.ElapsedMilliseconds)");

      def sw = Stopwatch.StartNew();
      _ = f.DoSomethingHof(str => String.IsNullOrEmpty(str), null);
      sw.Stop();
      WriteLine($"Functional type call took: $(sw.ElapsedMilliseconds)");

      WriteLine();
    }
  }
}
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[11]: Стоит ли увлекаться лямбдами?
От: VladD2 Российская Империя www.nemerle.org
Дата: 21.10.10 19:36
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Более интересным оказался другой эксперимент... Я сделал обобщенным объект Foo и использовал его параметр типов в методе методах использующих делегат и функциональный тип. Результат получился просто феерическим:

VD>
VD>Virtual call took: 1065
VD>Delegate call took: 781
VD>Functional type call took: 640

VD>Virtual call took: 1065
VD>Delegate call took: 781
VD>Functional type call took: 640
VD>

VD>Замедлился виртуальный вызов, но вызовы выполняемые через делегат и функциональный объект выполнялись за то же время, что и раньше! И это при том, что параметр типов не используется явно внутри виртуального вызова, а в других методах используется.

Проверил данный тест на 4-ом фрэймворке. В нем замедления от использования виртуальных функций в обобщенном классе не наблюдается. А результаты такие:
640
925

Первое — виртуальный вызов (без разницы в обобщенном классе или нет), а второй — это делегат.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[11]: Стоит ли увлекаться лямбдами?
От: Sinix  
Дата: 22.10.10 00:28
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Воспроизвел тест из вышеприведенной ссылке на C# и Nemerle (оба теста под управлением 3.5-го фрэймворка).

Да, примерно тот же расклад вышел у меня
Автор: Sinix
Дата: 14.10.10
.

Насколько понял, ув. Пельмешко интересовала именно производительность в вакууме — иначе зачем заводить речь о косвенных вызовах?

VD>Но главное что можно вывести из этих тестов...

Согласен и поддерживаю

VD>Более интересным оказался другой эксперимент... Я сделал обобщенным объект Foo и использовал его параметр типов в методе методах использующих делегат и функциональный тип. Результат получился просто феерическим:

Забавно. А у нас — от так (.NET 4):
                   Direct:       84,0 ms
                Direct<T>:       84,0 ms
------
                     Data:    1 304,0 ms
              Cached Data:       80,0 ms
          Data-GenericFoo:    1 273,0 ms
   Cached Data-GenericFoo:       79,0 ms
           GenericData<T>:    1 290,0 ms
    Cached GenericData<T>:       78,0 ms
------
                    IData:    1 864,0 ms
             Cached IData:      717,0 ms
         IData-GenericFoo:    3 855,0 ms
  Cached IData-GenericFoo:    2 780,0 ms
                 IData<T>:    1 415,0 ms
          Cached IData<T>:      637,0 ms
------
                   Lambda:      884,0 ms
            Cached lambda:      635,0 ms
                 Delegate:    2 812,0 ms
          Cached delegate:      722,0 ms
              Delegate<T>:    2 808,0 ms
       Cached delegate<T>:    1 115,0 ms
Done...


  код
using System;
using System.Diagnostics;

class Program
{
  interface IData
  {
    int Foo();
    T GenericFoo<T>() where T: class;
  }
  interface IData<T>
  {
    T Foo();
  }

  class Data: IData
  {
    public int val;

    public int Foo()
    {
      return val;
    }

    public T GenericFoo<T>() where T: class
    {
      return null;
    }
  }

  class GenericData<T>: IData<T>
  {
    public T val;

    public T Foo()
    {
      return val;
    }
  }

  const int Count = 200 * 1000 * 1000;

  static int Foo()
  {
    return 0;
  }
  static T Foo<T>() where T: class
  {
    return null;
  }

  static void Main(string[] args)
  {
    Measure("Direct", () =>
    {
      for (int i = 0; i < Count; i++)
      {
        Foo();
      }
    });
    Measure("Direct<T>", () =>
    {
      for (int i = 0; i < Count; i++)
      {
        Foo<object>();
      }
    });

    Console.WriteLine("------");
    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("Data-GenericFoo", () =>
    {
      for (int i = 0; i < Count; i++)
      {
        new Data()
        {
          val = i
        }.GenericFoo<object>();
      }
    });
    Measure("Cached Data-GenericFoo", () =>
    {
      Data data = new Data()
      {
        val = 0
      };
      for (int i = 0; i < Count; i++)
      {
        data.GenericFoo<object>();
      }
    });
    Measure("GenericData<T>", () =>
    {
      for (int i = 0; i < Count; i++)
      {
        new GenericData<int>()
        {
          val = i
        }.Foo();
      }
    });
    Measure("Cached GenericData<T>", () =>
    {
      GenericData<int> data = new GenericData<int>()
      {
        val = 0
      };
      for (int i = 0; i < Count; i++)
      {
        data.Foo();
      }
    });

    Console.WriteLine("------");
    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("IData-GenericFoo", () =>
    {
      for (int i = 0; i < Count; i++)
      {
        ((IData)new Data()
        {
          val = i
        }).GenericFoo<object>();
      }
    });
    Measure("Cached IData-GenericFoo", () =>
    {
      IData data = new Data()
      {
        val = 0
      };
      for (int i = 0; i < Count; i++)
      {
        data.GenericFoo<object>();
      }
    });
    Measure("IData<T>", () =>
    {
      for (int i = 0; i < Count; i++)
      {
        ((IData<int>)new GenericData<int>()
        {
          val = i
        }).Foo();
      }
    });
    Measure("Cached IData<T>", () =>
    {
      IData<int> data = new GenericData<int>()
      {
        val = 0
      };
      for (int i = 0; i < Count; i++)
      {
        data.Foo();
      }
    });

    Console.WriteLine("------");
    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();
      }
    });
    Measure("Delegate<T>", () =>
    {
      for (int i = 0; i < Count; i++)
      {
        Func<object> a = Foo<object>;
        a();
      }
    });
    Measure("Cached delegate<T>", () =>
    {
      Func<object> a = Foo<object>;
      for (int i = 0; i < Count; i++)
      {
        a();
      }
    });
    Console.Write("Done...");
    Console.ReadKey();
  }

  static void Measure(string name, Action callback)
  {
    Stopwatch sw = Stopwatch.StartNew();
    callback();
    sw.Stop();
    Console.WriteLine("{0,25}: {1,10:#,##0.0###} ms", name, sw.ElapsedMilliseconds);
  }
}
Re[11]: Стоит ли увлекаться лямбдами?
От: VladD2 Российская Империя www.nemerle.org
Дата: 22.10.10 14:09
Оценка:
Здравствуйте, Воронков Василий, Вы писали:

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


На самом деле — это гон. На современных процах и фрэймворках они выполняются примерно за одно время. Разница ничтожна, да и зависит от фазы луны.

Не удивлюсь, что дело в процессорах. Все же в обоих случаях мы имеем очень косвенный вызов, что можно эффективно разрулить аппаратно.

Если это так, то на лицо поддержка Интелом управляемых языков . И это еще цветочки. Говорят, что Интел решил свой ФЯ залудить. Да не простой, а для числодробления.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.