Здравствуйте, Neco, Вы писали:
N>в данном случае, это как бы Presentation, но планирую использовать такой же подход на уровне контроллеров и вообще по жизни. N>Так вопрос, чем это может грозить? Потерей производительности, например? N>Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
Я бы беспокоился больше по поводу читабельности. А она, по-моему, в приведенном примере хромает на обе ноги.
Здравствуйте, Пельмешко, Вы писали:
П>Евгений, а не поясните, блокирующее что конкретно? П>При создание делегата какие-нибудь стабы методов генерируются и в глобальных синхронизованных таблицах хранятся или что-то подобное?
Я не знаю что блокируется конкретно. Однако в стектрейсах четко видно как управление уходит в EE и там видны блокировки.
Причем еще очень зависит на что создается делегат.
Дешевле всего (и это без блокировки) на обычный метод класса.
Дорого — на метод интерфейса, на метод структуры, на статический метод, и на метод класса с дженериками. Причем разница доходит до 50 раз.
Подобное поведение подтверждается как экспериментально, так и источниками из CLR Team
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, _FRED_, Вы писали:
_FR>но использование этого API посредстром анонимных методов или лямбд с точки зрения производительности всё-тки не хуже использования этого же API посредством именованных методов?
В общем, да. Разницу в микроскоп будет не заметить.
И все приведенные мною выше слова относятся действительно к performance-critical коду, который вызывается десятки тысяч раз в секунду. Там использование лямбд, linq и прочей современной радости _строго_ противопоказано.
Во всех остальных случаях, как правило, удобство чтения, восприятия и модификации кода перевешивает перфоманс-деградацию
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, Пельмешко, Вы писали:
П>Что значит "JITтится оно аналогично call"? П>Почему для F# что-то "JITтится" иначе? П>В F# используются обычные вызовы виртуальных методов, через callvirt.
Это фича рантайма, а не языка.
Посмотрите ссылки тут
Здравствуйте, Neco, Вы писали:
N>в данном случае, это как бы Presentation, но планирую использовать такой же подход на уровне контроллеров и вообще по жизни.
У лямбд есть существенный недостаток. Это неимение имени. Имя — это ясный абстрактный коментарий того, что данный код делает. Неимение имени существенно снижает читабельность а следовательно сопровождаемость кода. Поэтому для сложных конструкций, где с одного взгляда не поймешь что делегат делает, именованные функции рулят.
N>Так вопрос, чем это может грозить? Потерей производительности, например? N>Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
Не должно, ежели это не битовыжимательство.
Здравствуйте, Neco, Вы писали:
N>В последнее время всё чаще строю многоэтажные конструкции типа:
ИМХО, ничего страшного нет.
N> xml.Div(new { @class = "editor-area" }, () => {
N> if (!HideBackButton()) {
N> xml.PTag(() => {
// сделать разве что "поуже", не так широко,
// перенеся длинные строки на несколько строк.
N> xml.ImageActionRef(Page.Content.ImgUrl(IMG_PREVIOUS_BUTTON),
"To the List", ActionNameForBackButton(),
"Back to the List", ParentListRoutes());
N> });
N> }
// убрал бы скобки вокруг одного параметра лямбды
N> //xml.FieldSet((fieldset) => {
xml.FieldSet(fieldset => {
N> fieldset.Legend(LegendText());
N> if (!string.IsNullOrEmpty(model.ErrorMessage)) {
N> xml.Div(new { @class = "error-message" },
// А тут убрал бы не обязательные скобки.
() => xml.WriteString(model.ErrorMessage););
N> }
N> if (model.Exception != null) {
N> xml.Div(new { @class = "error-exception" },
// И тут.
() => xml.PrintException(model.Exception););
N> }
N> xml.ComplexFieldHidden(
GetLambdaFroSerializedObject(m => m.SerializedOriginalModel),
model.SerializedOriginalModel);
N> if (fields != null) {
N> foreach (var one_field in fields) {
N> one_field.Render(xml);
N> }
N> } else if (customFields != null) {
N> customFields(xml);
N> } else {
N> throw new InvalidOperationException("Do not have any field for render!");
N> }
// Этот абзац сильвы выбивается из общего стиля:
// скобки не так расставлены, "this." …
N> if (!HideSubmitButton()) {
N> xml.SubmitButton("Submit");
N> }
N> });
N> });
Так же (но это уже не совсем к читабельности, хотя может и к ней) смущает "new { @class = "editor-area" }" — набор атрибутов не на столько большой, что бы сложно было бы описать один единственный раз его и пользщоваться далее уже типизированными аналогами.
N>помеченные жирным конструкции могут вкладываться друг в друга весьма глубоко, если не в одном методе, то в их последовательном вызове. Вовсю используются замыкания. N>в данном случае, это как бы Presentation, но планирую использовать такой же подход на уровне контроллеров и вообще по жизни. N>Так вопрос, чем это может грозить? Потерей производительности, например?
Ну это вряд ли.
N>Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
Я бы не беспокоился.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, xvost, Вы писали:
X>>>По поводу лямбд стоит беспокоиться в Performance-critical местах, поскольку они ОЧЕНЬ небесплатны по производительности _FR>>По сравнению с вызовом именованного метода?
X>Во-первых да. X>Во-вторых там не только вызов, а еще X> 2а) Создание объекта-замыкания
Я так понимаю, что вряд-ли "Создание объекта-замыкания" отличается от просто создания (и инициализации) некоторого пользовательского объекта с такими же полями?
Конечно, считая относительно медленным создание любого объекта, можно придти к тому, что лямбды тормозят. Надеюсь, такие подсчёты не станут обычными для большинства прогоаммистов
Или создание именно "объекта-замыкания" обычно медленнее, чем создание пользовательского объекта?
X> 2б) Создание делегата над методом класса замыкания
Делегат тоже создаётся заметно медленнее чем обычный пользовательский объект?
X>Все это во-первых весьма и весьма жрет ЦПУ (учитывая подводные и широко неизвестные факты в EE про создания лямбд над методом класса с дженериками)
Разве EE знает, с объектом лямбды она дело имеет просто с пользовательским объектом/методом и не знает, что когда-то обыло лямбдой? Мне казалось, что "лямбда" — лишь понятия компилятора, а EE уже дела нет, что это за объект. Это не так? Другими словами, чем отличается поведение ЕЕ "про создания лямбд над методом класса с дженериками" от "про создания делегатов над методом класса с дженериками"?
X>А во-вторых объекты замыкания, если их много, весьма основательно мусорят, что приводит к mid-life crisis'у
А что можно назвать "mid-life crisis"-ом применительно к данному вопросу?
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>Я так понимаю, что вряд-ли "Создание объекта-замыкания" отличается от просто создания (и инициализации) некоторого пользовательского объекта с такими же полями?
правильно.
X>> 2б) Создание делегата над методом класса замыкания _FR>Делегат тоже создаётся заметно медленнее чем обычный пользовательский объект?
Да. Создание делегата — во много раз более дорогое удовольствие чем создание простого объекта
_FR>Разве EE знает, с объектом лямбды она дело имеет просто с пользовательским объектом/методом и не знает, что когда-то обыло лямбдой? Мне казалось, что "лямбда" — лишь понятия компилятора, а EE уже дела нет, что это за объект. Это не так? Другими словами, чем отличается поведение ЕЕ "про создания лямбд над методом класса с дженериками" от "про создания делегатов над методом класса с дженериками"?
Еще раз. Никаких лямбд в EE нет. Там есть только делегаты. А их создание — очень и очень дорогое удовольствие. К тому же (при определенных условиях) блокирующее, что немедленно сказывается на многопоточных приложениях
X>>А во-вторых объекты замыкания, если их много, весьма основательно мусорят, что приводит к mid-life crisis'у _FR>А что можно назвать "mid-life crisis"-ом применительно к данному вопросу?
Пользовательская логика. Ее объекты передут в Gen1 тогда как могли бы умереть в Gen0
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, Sinix, Вы писали:
П>>Для меня всегда было так (в порядке производительности): S> S>Вызов virtual-метода = косвенный вызов, самый быстрый из косвенных в CLR; S>Вызов метода через интерфейс = дважды косвенный вызов, чуть медленнее; S>Вызов делегата = косвенный вызов + некоторый оверхед [ + ещё оверхед из-за подстановки 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-тест:
Я уж не знаю какого черта у немерла скорость вызова делегата оказалась чуть шустрее шарповской (думаю просто расклад (с) Поркчик Ржевский).
Но главное что можно вывести из этих тестов, что на современном железе (а у меня Core 2 2.8 Ghz) и относительно современном фрэймворке (я проводил измерения на 3.5-ом) разница между скоростью вызова делегата и виртуальным взовом не столь существенна (как была ранее).
И чем более существенны вычисления идущие рядом, тем меньше влияние она оказывает на скорость самого приложения.
Кроме того я произвел смелые научные эксперементы...
Так я сделал метод с предикатом обобщенным:
public DoSomething[T](predicator : Predicate[T], word : T) : int
Но это никак не сказалось на скорости работы вычислениях.
Возможно на скорости создания замыкания это и сказывается, но никак не на скорости вызова.
Более интересным оказался другой эксперимент... Я сделал обобщенным объект Foo и использовал его параметр типов в методе методах использующих делегат и функциональный тип. Результат получился просто феерическим:
Замедлился виртуальный вызов, но вызовы выполняемые через делегат и функциональный объект выполнялись за то же время, что и раньше! И это при том, что параметр типов не используется явно внутри виртуального вызова, а в других методах используется.
Немерловый тест:
using System;
using System.Console;
using System.Diagnostics;
class Foo
{
public virtual IsTokenChar(word : string) : bool
{
String.IsNullOrEmpty(word);
}
// this is a template methodpublic 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();
}
}
}
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, _FRED_, Вы писали:
_FR>>То есть, да: использование API, основанного на вызове методов через делегаты (linq/Rx и многое уже что появилось) уже чревато (из-за дороговизны создания делегата) но использование этого API посредстром анонимных методов или лямбд с точки зрения производительности всё-тки не хуже использования этого же API посредством именованных методов?
JR>В NET альтернатива делегату, по-моему, только интерфейс, то есть выбор не особо велик, а созданный делегат, в том числе и на базе анонима, можно "кэшировать". Кстати, кто-нибудь интересовался, при использовании лямбд кэширование выполняется?
Да, выполняется конечно, но только если лямбда имеет CLI-представление в виде статического метода, то есть если ни замыкается на локальные переменные, ни на this.
Много подобных кэшируется:
.Select(person => person.Name)
Интерфейсный вызов тоже не айс, в дотнете он дважды косвенный (где-то здесь обсуждалось давно), самая быстрая альтернатива — обычный виртуальный вызов экземплярного метода.
Здравствуйте, 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);
}
}
Здравствуйте, Jolly Roger, Вы писали:
JR>В NET альтернатива делегату, по-моему, только интерфейс, то есть выбор не особо велик, а созданный делегат, в том числе и на базе анонима, можно "кэшировать".
Иногда безсмысленно: если анонимная функция использует замыкания, то кешированиеособо не спасёт.
JR>Кстати, кто-нибудь интересовался, при использовании лямбд кэширование выполняется?
Тогда, когда анонимную функцию можно представить в виде статического метода, делегат на неё кэшируется. Других оптимизаций по кешированию анонимных функций я не видел.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
X>>По поводу лямбд стоит беспокоиться в Performance-critical местах, поскольку они ОЧЕНЬ небесплатны по производительности _FR>По сравнению с вызовом именованного метода?
Во-первых да.
Во-вторых там не только вызов, а еще
2а) Создание объекта-замыкания
2б) Создание делегата над методом класса замыкания
Все это во-первых весьма и весьма жрет ЦПУ (учитывая подводные и широко неизвестные факты в EE про создания лямбд над методом класса с дженериками)
А во-вторых объекты замыкания, если их много, весьма основательно мусорят, что приводит к mid-life crisis'у
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, xvost, Вы писали:
X>>> 2б) Создание делегата над методом класса замыкания _FR>>Делегат тоже создаётся заметно медленнее чем обычный пользовательский объект?
Кстати, получается, если в примере топикстартера, например, мы заменим все лямбды на обычные, именованные методы или именованные методы во вспомагательных классах, то (поскольку и создание вспомагательных классов и создание делегатов останется) прироста производительности всё-таки не получится?
То есть, да: использование API, основанного на вызове методов через делегаты (linq/Rx и многое уже что появилось) уже чревато (из-за дороговизны создания делегата) но использование этого API посредстром анонимных методов или лямбд с точки зрения производительности всё-тки не хуже использования этого же API посредством именованных методов?
Надеюсь, не слишком завернул
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, 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#...
Здравствуйте, Neco, Вы писали:
N>Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
По поводу лямбд стоит беспокоиться в Performance-critical местах, поскольку они ОЧЕНЬ небесплатны по производительности
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, Sinix, Вы писали:
S>Не совсем так. В il действительно callvirt, но JITтится оно аналогично call. Иначе они по производительности совпадали бы с обычными делегатами/интерфейсами. S>См http://www.rsdn.ru/forum/dotnet/3996529.1.aspx
Инструкция callvirt нужна чтобы генерировать NullReferenceException при this равном null.
Если меня не подводит память, то C++/CLI использует call для вызова невиртуальных методов и потому их можно позвать для null.
Проблем в целом с таким подходом нет. С т.з. производительности все зависит от того, что пишите. Каких-то кардинальных проблем с производительностью нет.
Если судить о приведенном коде, то — буду неоригинален. Стоит прежде всего продумать форматирование. Убирать лишние скобочки, которые затрудняют чтение. И я бы на экономил на переносах строк — в идеале хочется, чтобы тело каждой лямбды было четко оформлено. Я обычно форматирую как-то так:
Fun(x =>
{
Fun2(y =>
{
});
});
Да, ну и хорошо при этом на забывать о том, что лямбда "повторенная дважды" как-то уже теряет свою ценность Какие-то вещи можно оформлять и в виде старых добрых неанонимных функций. Заодно и клиентский код будет выглядеть чище.
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Гм, вызов через интерфейс стал медленее вызова через делегат? Интересно.
Скорее, вызов делегата стал быстрее вызова ч/з интерфейс
В последнее время всё чаще строю многоэтажные конструкции типа:
xml.Div(new { @class = "editor-area" }, () => {
if (!HideBackButton()) {
xml.PTag(() => {
xml.ImageActionRef(Page.Content.ImgUrl(IMG_PREVIOUS_BUTTON), "To the List", ActionNameForBackButton(), "Back to the List", ParentListRoutes());
});
}
xml.FieldSet((fieldset) => {
fieldset.Legend(LegendText());
if (!string.IsNullOrEmpty(model.ErrorMessage)) {
xml.Div(new { @class = "error-message" }, () => {
xml.WriteString(model.ErrorMessage);
});
}
if (model.Exception != null) {
xml.Div(new { @class = "error-exception" }, () => {
xml.PrintException(model.Exception);
});
}
xml.ComplexFieldHidden(GetLambdaFroSerializedObject(m => m.SerializedOriginalModel), model.SerializedOriginalModel);
if (fields != null) {
foreach (var one_field in fields) {
one_field.Render(xml);
}
} else if (customFields != null) {
customFields(xml);
} else {
throw new InvalidOperationException("Do not have any field for render!");
}
if (! this.HideSubmitButton())
{
xml.SubmitButton("Submit");
}
});
});
помеченные жирным конструкции могут вкладываться друг в друга весьма глубоко, если не в одном методе, то в их последовательном вызове. Вовсю используются замыкания.
в данном случае, это как бы Presentation, но планирую использовать такой же подход на уровне контроллеров и вообще по жизни.
Так вопрос, чем это может грозить? Потерей производительности, например?
Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
Здравствуйте, Lloyd, Вы писали:
L>Я бы беспокоился больше по поводу читабельности. А она, по-моему, в приведенном примере хромает на обе ноги.
Хех-хех, да это есть ))
Но пока идей, как это улучшить, нет. Может у Вас есть? Буду признателен.
Здравствуйте, Neco, Вы писали:
N>Здравствуйте, Lloyd, Вы писали:
L>>Я бы беспокоился больше по поводу читабельности. А она, по-моему, в приведенном примере хромает на обе ноги. N>Хех-хех, да это есть )) N>Но пока идей, как это улучшить, нет. Может у Вас есть? Буду признателен.
Кстати, раз уж я спрашиваю про идею, то пожалуй скажу для чего такая конструкция нужна (чтобы можно было исходя из требования что-то предлагать, а не из кода). По сути нужно иметь возможность повторно использовать элементы дизайна. В моём случае это Asp.Net MVC, хотя от оригинала уже мало что осталось. Проблема зародилась в том, что при объединении нескольких mvc-контролов в один более сложный (например, кнопка с текстом — т.е. по сути гиперлинк с картинкой и текстом внутри), предлагалось либо не париться и дублировать, либо писать своё расширение, код которого в общем-то, будет состоять из бубнов с TagBuilder'ом. И сделать такой код повторно используемым я не понял как. Постепенно пришёл к тому, что всякое расширение любого html элемента, который может в себе что-то содержать, должно принимать параметр-лямбду, типа subHtml. Если сообщать вместо лямбды string, то визуально нарушается ход событий (т.е. сначала готовим начинку, а потом её используем). Таким образом такая конструкция и случилась.
Здравствуйте, MozgC, Вы писали:
MC>- Не использовать многоэтажные лямбда-конструкции?
Это логично, но они неспроста появились — т.е. они решают вполне конкретную задачу.
В общем, я Lloyd'у написал подробное объяснение.
Здравствуйте, Neco, Вы писали:
L>>Я бы беспокоился больше по поводу читабельности. А она, по-моему, в приведенном примере хромает на обе ноги. N>Хех-хех, да это есть )) N>Но пока идей, как это улучшить, нет. Может у Вас есть? Буду признателен.
Здравствуйте, xvost, Вы писали:
N>>Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
X>По поводу лямбд стоит беспокоиться в Performance-critical местах, поскольку они ОЧЕНЬ небесплатны по производительности
По сравнению с вызовом именованного метода?
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, xvost, Вы писали:
X>>> 2б) Создание делегата над методом класса замыкания _FR>>Делегат тоже создаётся заметно медленнее чем обычный пользовательский объект?
X>Да. Создание делегата — во много раз более дорогое удовольствие чем создание простого объекта
ОК, это объясняет назойливое кэширование, когда это возможно, делегатов.
_FR>>Разве EE знает, с объектом лямбды она дело имеет просто с пользовательским объектом/методом и не знает, что когда-то обыло лямбдой? Мне казалось, что "лямбда" — лишь понятия компилятора, а EE уже дела нет, что это за объект. Это не так? Другими словами, чем отличается поведение ЕЕ "про создания лямбд над методом класса с дженериками" от "про создания делегатов над методом класса с дженериками"?
X>Еще раз. Никаких лямбд в EE нет. Там есть только делегаты. А их создание — очень и очень дорогое удовольствие.
ОК, я всего лишь пытался понять ваши же слова:
…(учитывая подводные и широко неизвестные факты в EE про создания лямбд над методом класса с дженериками)
X>К тому же (при определенных условиях) блокирующее, что немедленно сказывается на многопоточных приложениях
Спасибо, будет что посмотреть.
X>>>А во-вторых объекты замыкания, если их много, весьма основательно мусорят, что приводит к mid-life crisis'у _FR>>А что можно назвать "mid-life crisis"-ом применительно к данному вопросу? X>Пользовательская логика. Ее объекты передут в Gen1 тогда как могли бы умереть в Gen0
О как интересно: и термин здоровский, и не бросается так сразу в глаза.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>То есть, да: использование API, основанного на вызове методов через делегаты (linq/Rx и многое уже что появилось) уже чревато (из-за дороговизны создания делегата) но использование этого API посредстром анонимных методов или лямбд с точки зрения производительности всё-тки не хуже использования этого же API посредством именованных методов?
В NET альтернатива делегату, по-моему, только интерфейс, то есть выбор не особо велик, а созданный делегат, в том числе и на базе анонима, можно "кэшировать". Кстати, кто-нибудь интересовался, при использовании лямбд кэширование выполняется?
Здравствуйте, xvost, Вы писали:
X>К тому же (при определенных условиях) блокирующее, что немедленно сказывается на многопоточных приложениях
Евгений, а не поясните, блокирующее что конкретно?
При создание делегата какие-нибудь стабы методов генерируются и в глобальных синхронизованных таблицах хранятся или что-то подобное?
p.s. А вот функциональные значения в F# лишены проблем производительности создания экземпляров, так как являются просто экземплярами обычных классов, да и вызываются чуть быстрее (самый обычный виртуальный вызов, так же как в Nemerle, если мне не изменяет память). Вот только они не бывают никаких других сигнатур кроме как 'a -> 'b, поэтому придётся либо время тратить и нагружать GC, складывая аргументы в тупль, либо использовать каррированную форму, но тогда все вызовы становятся медленнее на один downcast (надо бы сравнить с вызовом делегата)
Здравствуйте, _FRED_, Вы писали:
_FR>Кстати, получается, если в примере топикстартера, например, мы заменим все лямбды на обычные, именованные методы или именованные методы во вспомагательных классах, то (поскольку и создание вспомагательных классов и создание делегатов останется) прироста производительности всё-таки не получится?
Если среди лямбд есть статические (у топикстартера таких нет), то получите проигрыш.
Если, конечно, сами не реализуете кэширование создание экземпляров делегатов.
Здравствуйте, Пельмешко, Вы писали:
П>Евгений, а не поясните, блокирующее что конкретно? П>При создание делегата какие-нибудь стабы методов генерируются и в глобальных синхронизованных таблицах хранятся или что-то подобное?
Кмк, это проблемы EE, а не делегатов.
П>p.s. А вот функциональные значения в F# лишены проблем производительности создания экземпляров, так как являются просто экземплярами обычных классов, да и вызываются чуть быстрее (самый обычный виртуальный вызов, так же как в Nemerle, если мне не изменяет память).
Не совсем так. В il действительно callvirt, но JITтится оно аналогично call. Иначе они по производительности совпадали бы с обычными делегатами/интерфейсами.
См http://www.rsdn.ru/forum/dotnet/3996529.1.aspx
Здравствуйте, Пельмешко, Вы писали:
П>Должны быть логически равнозначны (раз хотим проверить скорость создания делегата), однако здесь компилятор C# успешно применяет кэширование экземпляра делегата, даже не смотря на замыкание!
Ну да, собственно это и хотел показать, где-то выше по дискуссии видел обратное утверждение
Здравствуйте, 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.
Да.
Гм, вызов через интерфейс стал медленее вызова через делегат? Интересно.
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'у
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, xvost, Вы писали:
X>>Все это во-первых весьма и весьма жрет ЦПУ (учитывая подводные и широко неизвестные факты в EE про создания лямбд над методом класса с дженериками) X>>А во-вторых объекты замыкания, если их много, весьма основательно мусорят, что приводит к mid-life crisis'у
А>а что такое EE?
Здравствуйте, Neco, Вы писали:
N>Кстати, раз уж я спрашиваю про идею, то пожалуй скажу для чего такая конструкция нужна (чтобы можно было исходя из требования что-то предлагать, а не из кода). По сути нужно иметь возможность повторно использовать элементы дизайна. В моём случае это Asp.Net MVC, хотя от оригинала уже мало что осталось. Проблема зародилась в том, что при объединении нескольких mvc-контролов в один более сложный (например, кнопка с текстом — т.е. по сути гиперлинк с картинкой и текстом внутри), предлагалось либо не париться и дублировать, либо писать своё расширение, код которого в общем-то, будет состоять из бубнов с TagBuilder'ом. И сделать такой код повторно используемым я не понял как. Постепенно пришёл к тому, что всякое расширение любого html элемента, который может в себе что-то содержать, должно принимать параметр-лямбду, типа subHtml. Если сообщать вместо лямбды string, то визуально нарушается ход событий (т.е. сначала готовим начинку, а потом её используем). Таким образом такая конструкция и случилась.
То что ты описал — это попытка добиться ленивого (отложенного) выполнения методом "закат солнца вручную".
В принципе в шарпе другого выхода почти нет. Для тех же целей можно использовтаь интерфейсы или классы, но без замыканий все это бесполезно.
А вообще, у вас явно получается некий ДСЛ. Только реализуете вы его доступными средствами языка. От того и столько "шума".
ЗЫ
Кстати, что касается DSL-ей в контексте работы с ASP.NET, то очень интересно глянуть на Nemerle on rails. Там, средствами Nemerle, реализовано срезу несколько DSL-ей, начиная от DSL-реструктуризации БД, и кончая передачей данных из контроллеров в представления. Кроме того там используется альтернативный движок рендеренга который позволяет избавиться от проблемы с которыми вы боритесь.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, xvost, Вы писали:
X>Да. Создание делегата — во много раз более дорогое удовольствие чем создание простого объекта
Ты уж людей так сильно не пугай. Овершэд от вызова лямбд не такой уж большой чтобы об этом вообще задумываться в 99% случаев. К тому же если это множественный вызов (работа со списками, например), то создание лямбды происходит один раз, а не при каждом вызове.
Так что в очень критических местах лямбдам конечно не место. Но по жизни таких мест не много. Некоторые даже комбинаторные парсеры на базе шарповских лямбды умудряются делать. И ничего — работает (хотя я бы так делать никогда не стал).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Jolly Roger, Вы писали:
JR>В NET альтернатива делегату, по-моему, только интерфейс, то есть выбор не особо велик, а созданный делегат, в том числе и на базе анонима, можно "кэшировать".
Обычные классы с виртуальными методами. Они шустрее интерфейсов.
Именно так реализованы функциональные типы в Nemerle и (наверно) в F#.
Раньше это давало очень большой выигрыш (в 3-4 раза), но в 3.5 фрэймворке вызов лямбды был оптимизирован и на сегодня (плюс процессоры Core 2 и выше) разницу заметить сложно.
JR>Кстати, кто-нибудь интересовался, при использовании лямбд кэширование выполняется?
В C# выполняется.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Пельмешко, Вы писали:
П>Интерфейсный вызов тоже не айс, в дотнете он дважды косвенный (где-то здесь обсуждалось давно), самая быстрая альтернатива — обычный виртуальный вызов экземплярного метода.
Эта информация устарела (к дотнет 3.5 х86).
Последние тесты показывают, что разницу в скорости виртуального вызова и лямбды заметить практически невозможно. Так что что-то там в МС все же подшаманили.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Пельмешко, Вы писали:
П>p.s. А вот функциональные значения в F# лишены проблем производительности создания экземпляров, так как являются просто экземплярами обычных классов, да и вызываются чуть быстрее (самый обычный виртуальный вызов, так же как в Nemerle, если мне не изменяет память).
Эта информация устарела. Я рядом об этом писал уже. Разницы в скорости почти нет.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Более интересным оказался другой эксперимент... Я сделал обобщенным объект Foo и использовал его параметр типов в методе методах использующих делегат и функциональный тип. Результат получился просто феерическим: VD>
VD>Замедлился виртуальный вызов, но вызовы выполняемые через делегат и функциональный объект выполнялись за то же время, что и раньше! И это при том, что параметр типов не используется явно внутри виртуального вызова, а в других методах используется.
Проверил данный тест на 4-ом фрэймворке. В нем замедления от использования виртуальных функций в обобщенном классе не наблюдается. А результаты такие:
640
925
Первое — виртуальный вызов (без разницы в обобщенном классе или нет), а второй — это делегат.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали: VD>Воспроизвел тест из вышеприведенной ссылке на C# и Nemerle (оба теста под управлением 3.5-го фрэймворка).
Да, примерно тот же расклад вышел у меня
Насколько понял, ув. Пельмешко интересовала именно производительность в вакууме — иначе зачем заводить речь о косвенных вызовах? 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);
}
}
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Гм, вызов через интерфейс стал медленее вызова через делегат? Интересно.
На самом деле — это гон. На современных процах и фрэймворках они выполняются примерно за одно время. Разница ничтожна, да и зависит от фазы луны.
Не удивлюсь, что дело в процессорах. Все же в обоих случаях мы имеем очень косвенный вызов, что можно эффективно разрулить аппаратно.
Если это так, то на лицо поддержка Интелом управляемых языков . И это еще цветочки. Говорят, что Интел решил свой ФЯ залудить. Да не простой, а для числодробления.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.