Когда кто-то решает использовать ленивую инициализацию, он обычно должен за это платить.
class Loafer
{
private VeryExpensiveField field;
private VeryExpensiveField LazyInitField()
{
field = new VeryExpensiveField();
// I wanna here remove null check from accessor, but how?
return field;
}
property Field { get { return field ?? LazyInitField(); } }
}
По сути, он должен каждый раз проверять, имеет ли его вспомогательное поле значение null / nil. Что если он сможет избежать этой практики? Когда вы успешно инициализируете поле, вы можете избавиться от этой проверки, верно?
К сожалению, большинство рабочих языков не позволяют вам изменять их функции во время выполнения, особенно добавлять или удалять отдельные инструкции из тела функции, хотя было бы полезно, если бы их использовали разумно. Однако в C # вы можете использовать делегаты (сначала я их обнаружил, а потом понял, почему на родных языках есть указатели на функции) и События механизм для имитации такого поведения с последующим отсутствием производительности, поскольку нулевые проверки просто переходят на более низкий уровень, но не исчезают полностью. Некоторые языки, например LISP и Prolog позволяют легко изменять их код, но их вряд ли можно рассматривать как рабочие языки.
В родных языках, таких как Delphi и C / C ++, кажется, что лучше написать две функции, безопасную и быструю, вызывать их по указателю и переключать этот указатель на быструю версию после инициализации. Вы даже можете позволить компилятору или IDE генерировать код, чтобы сделать это без дополнительной головной боли. Но, как упомянул @hvd, это может даже снизить скорость, поскольку процессор не будет знать, что эти функции почти одинаковы, и поэтому не будет предварительно загружать их в свой кеш.
Да, я — маньяк, ищущий производительность без явных проблем, просто чтобы прокормить свое любопытство. Какие общие подходы существуют для разработки такой функциональности?
На самом деле инфраструктура инструментария лени не всегда так важна, когда вы сравниваете ее с фактическими вычислениями.
Есть много подходов.
Ты можешь использовать ленивый, самоизменяющаяся лямбда-установка, логическое значение или что-либо еще, что подходит вашему рабочему процессу.
Накладные расходы на инструмент для отложенной оценки важны только при повторных вычислениях.
Мой пример кода с микро-бенчмарком исследует сравнительные издержки ленивых вычислений в контексте сопровождающей более дорогой операции в цикле.
Вы можете видеть, что накладные расходы на ленивый инструментарий можно пренебречь даже при использовании вместе с относительно полезной операцией загрузки чипа.
void Main()
{
// If the payload is small, laziness toolkit is not neglectible
RunBenchmarks(i => i % 2 == 0, "Smaller payload");
// Even this small string manupulation neglects overhead of laziness toolkit
RunBenchmarks(i => i.ToString().Contains("5"), "Larger payload");
}
void RunBenchmarks(Func<int, bool> payload, string what)
{
Console.WriteLine(what);
var items = Enumerable.Range(0, 10000000).ToList();
Func<Func<int, bool>> createPredicateWithBoolean = () =>
{
bool computed = false;
return i => (computed || (computed = Compute())) && payload(i);
};
items.Count(createPredicateWithBoolean());
var sw = Stopwatch.StartNew();
Console.WriteLine(items.Count(createPredicateWithBoolean()));
sw.Stop();
Console.WriteLine("Elapsed using boolean: {0}", sw.ElapsedMilliseconds);
Func<Func<int, bool>> createPredicate = () =>
{
Func<int, bool> current = i =>
{
var computed2 = Compute();
current = j => computed2;
return computed2;
};
return i => current(i) && payload(i);
};
items.Count(createPredicate());
sw = Stopwatch.StartNew();
Console.WriteLine(items.Count(createPredicate()));
sw.Stop();
Console.WriteLine("Elapsed using smart predicate: {0}", sw.ElapsedMilliseconds);
Console.WriteLine();
}
bool Compute()
{
return true; // not important for the exploration
}
Выход:
Smaller payload
5000000
Elapsed using boolean: 161
5000000
Elapsed using smart predicate: 182
Larger payload
5217031
Elapsed using boolean: 1980
5217031
Elapsed using smart predicate: 1994
FWIW с помощью Spring4D это также можно сделать в Delphi:
var
field: Lazy<VeryExpensiveField>;
begin
field :=
function: VeryExpensiveField
begin
Result := VeryExpensiveField.Create;
end;