Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, 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);
}
}
Здравствуйте, Воронков Василий, Вы писали:
ВВ>Гм, вызов через интерфейс стал медленее вызова через делегат? Интересно.
На самом деле — это гон. На современных процах и фрэймворках они выполняются примерно за одно время. Разница ничтожна, да и зависит от фазы луны.
Не удивлюсь, что дело в процессорах. Все же в обоих случаях мы имеем очень косвенный вызов, что можно эффективно разрулить аппаратно.
Если это так, то на лицо поддержка Интелом управляемых языков . И это еще цветочки. Говорят, что Интел решил свой ФЯ залудить. Да не простой, а для числодробления.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.