В последнее время всё чаще строю многоэтажные конструкции типа:
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, но планирую использовать такой же подход на уровне контроллеров и вообще по жизни.
Так вопрос, чем это может грозить? Потерей производительности, например?
Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
Здравствуйте, Neco, Вы писали:
N>в данном случае, это как бы Presentation, но планирую использовать такой же подход на уровне контроллеров и вообще по жизни. N>Так вопрос, чем это может грозить? Потерей производительности, например? N>Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
Я бы беспокоился больше по поводу читабельности. А она, по-моему, в приведенном примере хромает на обе ноги.
Здравствуйте, Lloyd, Вы писали:
L>Я бы беспокоился больше по поводу читабельности. А она, по-моему, в приведенном примере хромает на обе ноги.
Хех-хех, да это есть ))
Но пока идей, как это улучшить, нет. Может у Вас есть? Буду признателен.
Здравствуйте, Neco, Вы писали:
N>Здравствуйте, Lloyd, Вы писали:
L>>Я бы беспокоился больше по поводу читабельности. А она, по-моему, в приведенном примере хромает на обе ноги. N>Хех-хех, да это есть )) N>Но пока идей, как это улучшить, нет. Может у Вас есть? Буду признателен.
Кстати, раз уж я спрашиваю про идею, то пожалуй скажу для чего такая конструкция нужна (чтобы можно было исходя из требования что-то предлагать, а не из кода). По сути нужно иметь возможность повторно использовать элементы дизайна. В моём случае это Asp.Net MVC, хотя от оригинала уже мало что осталось. Проблема зародилась в том, что при объединении нескольких mvc-контролов в один более сложный (например, кнопка с текстом — т.е. по сути гиперлинк с картинкой и текстом внутри), предлагалось либо не париться и дублировать, либо писать своё расширение, код которого в общем-то, будет состоять из бубнов с TagBuilder'ом. И сделать такой код повторно используемым я не понял как. Постепенно пришёл к тому, что всякое расширение любого html элемента, который может в себе что-то содержать, должно принимать параметр-лямбду, типа subHtml. Если сообщать вместо лямбды string, то визуально нарушается ход событий (т.е. сначала готовим начинку, а потом её используем). Таким образом такая конструкция и случилась.
Здравствуйте, MozgC, Вы писали:
MC>- Не использовать многоэтажные лямбда-конструкции?
Это логично, но они неспроста появились — т.е. они решают вполне конкретную задачу.
В общем, я Lloyd'у написал подробное объяснение.
Здравствуйте, Neco, Вы писали:
L>>Я бы беспокоился больше по поводу читабельности. А она, по-моему, в приведенном примере хромает на обе ноги. 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.
Здравствуйте, Neco, Вы писали:
N>в данном случае, это как бы Presentation, но планирую использовать такой же подход на уровне контроллеров и вообще по жизни.
У лямбд есть существенный недостаток. Это неимение имени. Имя — это ясный абстрактный коментарий того, что данный код делает. Неимение имени существенно снижает читабельность а следовательно сопровождаемость кода. Поэтому для сложных конструкций, где с одного взгляда не поймешь что делегат делает, именованные функции рулят.
N>Так вопрос, чем это может грозить? Потерей производительности, например? N>Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
Не должно, ежели это не битовыжимательство.
Здравствуйте, Neco, Вы писали:
N>Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
По поводу лямбд стоит беспокоиться в Performance-critical местах, поскольку они ОЧЕНЬ небесплатны по производительности
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, xvost, Вы писали:
N>>Слышал что-то, что бинарники бУхнут от таких конструкций (из-за количества анонимных классов), но меня это мало беспокоит. Или должно беспокоить, как считаете?
X>По поводу лямбд стоит беспокоиться в Performance-critical местах, поскольку они ОЧЕНЬ небесплатны по производительности
По сравнению с вызовом именованного метода?
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>>>По поводу лямбд стоит беспокоиться в 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!"
Здравствуйте, 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.
Здравствуйте, xvost, Вы писали:
X>>> 2б) Создание делегата над методом класса замыкания _FR>>Делегат тоже создаётся заметно медленнее чем обычный пользовательский объект?
Кстати, получается, если в примере топикстартера, например, мы заменим все лямбды на обычные, именованные методы или именованные методы во вспомагательных классах, то (поскольку и создание вспомагательных классов и создание делегатов останется) прироста производительности всё-таки не получится?
То есть, да: использование API, основанного на вызове методов через делегаты (linq/Rx и многое уже что появилось) уже чревато (из-за дороговизны создания делегата) но использование этого API посредстром анонимных методов или лямбд с точки зрения производительности всё-тки не хуже использования этого же API посредством именованных методов?
Надеюсь, не слишком завернул
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, _FRED_, Вы писали:
_FR>но использование этого API посредстром анонимных методов или лямбд с точки зрения производительности всё-тки не хуже использования этого же API посредством именованных методов?
В общем, да. Разницу в микроскоп будет не заметить.
И все приведенные мною выше слова относятся действительно к performance-critical коду, который вызывается десятки тысяч раз в секунду. Там использование лямбд, linq и прочей современной радости _строго_ противопоказано.
Во всех остальных случаях, как правило, удобство чтения, восприятия и модификации кода перевешивает перфоманс-деградацию
С уважением, Евгений
JetBrains, Inc. "Develop with pleasure!"
Здравствуйте, _FRED_, Вы писали:
_FR>То есть, да: использование API, основанного на вызове методов через делегаты (linq/Rx и многое уже что появилось) уже чревато (из-за дороговизны создания делегата) но использование этого API посредстром анонимных методов или лямбд с точки зрения производительности всё-тки не хуже использования этого же API посредством именованных методов?
В NET альтернатива делегату, по-моему, только интерфейс, то есть выбор не особо велик, а созданный делегат, в том числе и на базе анонима, можно "кэшировать". Кстати, кто-нибудь интересовался, при использовании лямбд кэширование выполняется?
Здравствуйте, 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'у
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, 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, если мне не изменяет память).
Эта информация устарела. Я рядом об этом писал уже. Разницы в скорости почти нет.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, 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();
}
}
}
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, 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);
}
}
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Гм, вызов через интерфейс стал медленее вызова через делегат? Интересно.
На самом деле — это гон. На современных процах и фрэймворках они выполняются примерно за одно время. Разница ничтожна, да и зависит от фазы луны.
Не удивлюсь, что дело в процессорах. Все же в обоих случаях мы имеем очень косвенный вызов, что можно эффективно разрулить аппаратно.
Если это так, то на лицо поддержка Интелом управляемых языков . И это еще цветочки. Говорят, что Интел решил свой ФЯ залудить. Да не простой, а для числодробления.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.