Здравствуйте, xvost, Вы писали:
X>К тому же (при определенных условиях) блокирующее, что немедленно сказывается на многопоточных приложениях
Евгений, а не поясните, блокирующее что конкретно?
При создание делегата какие-нибудь стабы методов генерируются и в глобальных синхронизованных таблицах хранятся или что-то подобное?
p.s. А вот функциональные значения в F# лишены проблем производительности создания экземпляров, так как являются просто экземплярами обычных классов, да и вызываются чуть быстрее (самый обычный виртуальный вызов, так же как в Nemerle, если мне не изменяет память). Вот только они не бывают никаких других сигнатур кроме как 'a -> 'b, поэтому придётся либо время тратить и нагружать GC, складывая аргументы в тупль, либо использовать каррированную форму, но тогда все вызовы становятся медленнее на один downcast (надо бы сравнить с вызовом делегата)
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, _FRED_, Вы писали:
_FR>>То есть, да: использование API, основанного на вызове методов через делегаты (linq/Rx и многое уже что появилось) уже чревато (из-за дороговизны создания делегата) но использование этого API посредстром анонимных методов или лямбд с точки зрения производительности всё-тки не хуже использования этого же API посредством именованных методов?
JR>В NET альтернатива делегату, по-моему, только интерфейс, то есть выбор не особо велик, а созданный делегат, в том числе и на базе анонима, можно "кэшировать". Кстати, кто-нибудь интересовался, при использовании лямбд кэширование выполняется?
Да, выполняется конечно, но только если лямбда имеет CLI-представление в виде статического метода, то есть если ни замыкается на локальные переменные, ни на this.
Много подобных кэшируется:
.Select(person => person.Name)
Интерфейсный вызов тоже не айс, в дотнете он дважды косвенный (где-то здесь обсуждалось давно), самая быстрая альтернатива — обычный виртуальный вызов экземплярного метода.
Здравствуйте, Jolly Roger, Вы писали:
JR>В NET альтернатива делегату, по-моему, только интерфейс, то есть выбор не особо велик, а созданный делегат, в том числе и на базе анонима, можно "кэшировать".
Иногда безсмысленно: если анонимная функция использует замыкания, то кешированиеособо не спасёт.
JR>Кстати, кто-нибудь интересовался, при использовании лямбд кэширование выполняется?
Тогда, когда анонимную функцию можно представить в виде статического метода, делегат на неё кэшируется. Других оптимизаций по кешированию анонимных функций я не видел.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>Кстати, получается, если в примере топикстартера, например, мы заменим все лямбды на обычные, именованные методы или именованные методы во вспомагательных классах, то (поскольку и создание вспомагательных классов и создание делегатов останется) прироста производительности всё-таки не получится?
Если среди лямбд есть статические (у топикстартера таких нет), то получите проигрыш.
Если, конечно, сами не реализуете кэширование создание экземпляров делегатов.
Здравствуйте, Пельмешко, Вы писали:
П>Евгений, а не поясните, блокирующее что конкретно? П>При создание делегата какие-нибудь стабы методов генерируются и в глобальных синхронизованных таблицах хранятся или что-то подобное?
Я не знаю что блокируется конкретно. Однако в стектрейсах четко видно как управление уходит в EE и там видны блокировки.
Причем еще очень зависит на что создается делегат.
Дешевле всего (и это без блокировки) на обычный метод класса.
Дорого — на метод интерфейса, на метод структуры, на статический метод, и на метод класса с дженериками. Причем разница доходит до 50 раз.
Подобное поведение подтверждается как экспериментально, так и источниками из CLR Team
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, 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);
}
}
Здравствуйте, Пельмешко, Вы писали:
П>Евгений, а не поясните, блокирующее что конкретно? П>При создание делегата какие-нибудь стабы методов генерируются и в глобальных синхронизованных таблицах хранятся или что-то подобное?
Кмк, это проблемы EE, а не делегатов.
П>p.s. А вот функциональные значения в F# лишены проблем производительности создания экземпляров, так как являются просто экземплярами обычных классов, да и вызываются чуть быстрее (самый обычный виртуальный вызов, так же как в Nemerle, если мне не изменяет память).
Не совсем так. В il действительно callvirt, но JITтится оно аналогично call. Иначе они по производительности совпадали бы с обычными делегатами/интерфейсами.
См http://www.rsdn.ru/forum/dotnet/3996529.1.aspx
Здравствуйте, 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#...
Здравствуйте, Пельмешко, Вы писали:
П>Должны быть логически равнозначны (раз хотим проверить скорость создания делегата), однако здесь компилятор C# успешно применяет кэширование экземпляра делегата, даже не смотря на замыкание!
Ну да, собственно это и хотел показать, где-то выше по дискуссии видел обратное утверждение
Здравствуйте, Sinix, Вы писали:
S>Не совсем так. В il действительно callvirt, но JITтится оно аналогично call. Иначе они по производительности совпадали бы с обычными делегатами/интерфейсами. S>См http://www.rsdn.ru/forum/dotnet/3996529.1.aspx
Инструкция callvirt нужна чтобы генерировать NullReferenceException при this равном null.
Если меня не подводит память, то C++/CLI использует call для вызова невиртуальных методов и потому их можно позвать для null.
Здравствуйте, Sinix, Вы писали:
П>>p.s. А вот функциональные значения в F# лишены проблем производительности создания экземпляров, так как являются просто экземплярами обычных классов, да и вызываются чуть быстрее (самый обычный виртуальный вызов, так же как в Nemerle, если мне не изменяет память). S>Не совсем так. В il действительно callvirt, но JITтится оно аналогично call. Иначе они по производительности совпадали бы с обычными делегатами/интерфейсами.
Что значит "JITтится оно аналогично call"?
Почему для F# что-то "JITтится" иначе?
Для меня всегда было так (в порядке производительности):
Вызов virtual-метода = косвенный вызов, самый быстрый из косвенных в CLR;
Вызов метода через интерфейс = дважды косвенный вызов, чуть медленнее;
Вызов делегата = косвенный вызов + некоторый оверхед [ + ещё оверхед из-за подстановки Target'а ]
В F# используются обычные вызовы виртуальных методов, через callvirt.
Здравствуйте, hardcase, Вы писали:
H>Инструкция callvirt нужна чтобы генерировать NullReferenceException при this равном null.
Не только. Ещё и чтоб не ломать код при внезапном
class Data2: Data
{
public override int Foo() // Data.Foo - virtual
{
return 42;
}
}
H>Если меня не подводит память, то C++/CLI использует call для вызова невиртуальных методов и потому их можно позвать для null.
Да.
Здравствуйте, Пельмешко, Вы писали:
П>Что значит "JITтится оно аналогично call"? П>Почему для F# что-то "JITтится" иначе? П>В F# используются обычные вызовы виртуальных методов, через callvirt.
Это фича рантайма, а не языка.
Посмотрите ссылки тут
Проблем в целом с таким подходом нет. С т.з. производительности все зависит от того, что пишите. Каких-то кардинальных проблем с производительностью нет.
Если судить о приведенном коде, то — буду неоригинален. Стоит прежде всего продумать форматирование. Убирать лишние скобочки, которые затрудняют чтение. И я бы на экономил на переносах строк — в идеале хочется, чтобы тело каждой лямбды было четко оформлено. Я обычно форматирую как-то так:
Fun(x =>
{
Fun2(y =>
{
});
});
Да, ну и хорошо при этом на забывать о том, что лямбда "повторенная дважды" как-то уже теряет свою ценность Какие-то вещи можно оформлять и в виде старых добрых неанонимных функций. Заодно и клиентский код будет выглядеть чище.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Гм, вызов через интерфейс стал медленее вызова через делегат? Интересно.
Скорее, вызов делегата стал быстрее вызова ч/з интерфейс
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'у