延迟初始化后删除空检查

Remove null check after lazy initialization

当一个人决定使用惰性初始化时,他通常不得不为此付出代价。

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#中,你可以使用delegates(最初我发现了它们,后来意识到为什么本地语言具有函数指针)和events机制来模仿这种行为会导致性能下降,因为空检查只是移到较低级别,但不会完全消失。一些语言,例如LISP和Prolog,让你很容易修改它们的代码,但它们很难被视为生产语言。

在像Delphi和C/C++这样的本地语言中,似乎最好写两个函数,安全和快速,通过指针调用它们并在初始化后将此指针切换为快速版本。您甚至可以让编译器或 IDE 生成代码来执行此操作,而不会让人头疼。但是正如@hvd 提到的,这甚至会降低速度,因为 CPU 不知道这些功能几乎相同,因此不会将它们预取到它的缓存中。

是的,我是一个性能狂,寻求没有明显问题的性能,只是为了满足我的好奇心。开发此类功能有哪些常见方法?

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

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

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

方法有很多种。 您可以使用 Lazy、自修改 lambda 设置、布尔值或最适合您的工作流程的任何内容。

惰性评估工具包的开销仅在您进行一些重复计算时才需要考虑。

我的带有微基准测试的代码示例探索了在循环中伴随的更昂贵操作的上下文中惰性计算的相对开销。

你可以看到,laziness toolkit 的开销可以忽略不计,即使与相对芯片有效负载操作一起使用也是如此。

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