删除惰性初始化后的null检查

Remove null check after lazy initialization

本文关键字:null 检查 初始化 删除      更新时间:2023-10-16

当决定使用延迟初始化时,通常必须为此付出代价。

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所提到的,这甚至会降低速度,因为CPU不会知道这些函数几乎是相同的,因此不会将它们预取到它的缓存中。

是的,我是一个追求没有明确问题的性能的性能狂,只是为了满足我的好奇心。开发这种功能的常见方法是什么?

实际上,当您将其开销与实际计算进行比较时,惰性工具包框架并不总是那么重要。

有很多方法。你可以使用Lazy,一个自我修改的lambda设置,一个布尔值或任何最适合你的工作流程。

延迟求值工具箱的开销只有在需要重复计算时才需要考虑。

我的带有微基准测试的代码示例探讨了在循环中伴随更昂贵的操作的上下文中延迟计算的比较开销。

您可以看到,即使与相对芯片负载操作一起使用,惰性工具包的开销也是可以忽略不计的。

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

在Spring4D的帮助下,这也可以在Delphi中完成:

var
  field: Lazy<VeryExpensiveField>;
begin
  field :=
    function: VeryExpensiveField
    begin
      Result := VeryExpensiveField.Create;
    end;