-4

When one decides to use lazy initialization, he usually has to pay for it.

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(); } }
}

Basically, he has to check every time if his backing field has null/nil value. What if he could escape from this practice? When you successfully initialize the field, you can rid of this check, right?

Unfortunately, majority of production languages do not allow you to modify their functions in run-time, especially add or remove single instructions from the function body though it would be helpful if used wisely. However, in C#, you can use delegates (initially I discovered them and afterwards realized why native languages having function pointers for) and events mechanism to imitate such behavior with consequent lack of performance, because null-checks just move onto lower level, but do not disappear completely. Some languages, e.g. LISP and Prolog, allow you to modify their code easily, but they are hardly can be treated as production languages.

In native languages like Delphi and C/C++ it seems better to write two functions, safe and rapid, call them by pointer and switch this pointer to rapid version after initialization. You can even allow compiler or IDE to generate code to do this without additional headache. But as @hvd mentioned, this can even decrease speed, because CPU will not know that those functions are almost the same, thus will not prefetch them into it's cache.

Yes, I'm performance maniac seeking for performance without explicit problem, just to feed my curiosity. What common approaches are exist to develop such functionality?

Danatela
  • 349
  • 8
  • 28
  • 2
    "and switch to rapid after initialization"... - using the same conditional statement, as in the getter. What do you want to save? A few CPU cycles? – Dennis Sep 29 '15 at 06:51
  • 1
    Calling a function is expensive, at least in comparison to reading a variable. Do you know what a function pointer is in C and C++? Or a procedural variable in Delphi? How can the IDE be relevant here? Why do you mention it? Performance questions usually require a little more context than you have provided. – David Heffernan Sep 29 '15 at 06:55
  • I can´t see any problem on doing the null-check within your property. You may have more dramatic performance-problems then this single simple expression. – MakePeaceGreatAgain Sep 29 '15 at 06:55
  • @Dennis I can use pointer to function and switch it instead of checking right version each time. – Danatela Sep 29 '15 at 07:03
  • 2
    @Danatela Have you measured the impact of that? I ask because I would expect loading a word from memory and comparing it to zero to be slightly cheaper than loading a word from memory and setting the instruction pointer to that value, because the latter means the processor cannot know in advance what instructions will be executed. (I would also expect the difference to be so small that it would be very hard to measure.) –  Sep 29 '15 at 07:09
  • @DavidHeffernan I know... I was brilliant student. I just meant that safe and rapid versions can be generated automatically. – Danatela Sep 29 '15 at 07:10
  • @hvd that's why I think that it's better to modify function than generate two different versions of it. – Danatela Sep 29 '15 at 07:11
  • In addition to what @hwd said: when taking care for performance ALLWAYS use a performace-profiler such as DotTrace in order to see what exactly caused the problem. Quite often it´s not what you´d expect it to be, at least as far as I am concerned. – MakePeaceGreatAgain Sep 29 '15 at 07:11
  • 2
    @Danatela: in C# you can use delegates in the same manner, as the function pointers in native languages. But I doubt, that there will be any performance gain. – Dennis Sep 29 '15 at 07:15
  • 4
    Indirection seldom improves performance. I doubt that a function call is faster than a null check. Why don't you measure? Make some real world code that you wish to optimise. – David Heffernan Sep 29 '15 at 07:16
  • @DavidHeffernan I'd say that I can't rid of the function call. And the question was made from pure curiosity though it may be useful anyway. I have no real problem here, sorry. That's why I don't measure. – Danatela Sep 29 '15 at 07:32
  • @Danatela you can get rid of the function call. You can inline the null check + variable read. That will be fastest. – David Heffernan Sep 29 '15 at 18:28
  • @DavidHeffernan Thanks. Actual reason of putting initialization into another function was dictated by the question statement. I'm using inlining when I need, but here I think it will prevent the desired effect. Don't cut the branch where you sit, we say. Self-modifying code should have some restrictions due to W^X paradigm. Also, I read related Wikipedia article and found clues I want. It seems CPU has to have special capability to allow such modifications though is not explicitly impossible to do. Anyway, thanks for participation and suggestions. – Danatela Sep 30 '15 at 03:26
  • Self modifying code is possible. As I said though, null check will still be faster. – David Heffernan Sep 30 '15 at 05:15

2 Answers2

2

Actually the laziness toolkit framework is not always that important, when you compare it's overhead to the actual computation.

There are many approaches. You can use Lazy, a self modifying lambda setup, a boolean or whatever suits your workflow best.

Lazy evaluation toolkit's overhead is only important to consider when you have some repeated computation.

My code example with a micro benchmark explores the comparative overhead of lazy computation in context of an accompanying more expensive operation in a loop.

You can see that laziness toolkit's overhead is neglectible even when used along with a relatively chip payload operation.

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
}

Output:

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
George Polevoy
  • 7,450
  • 3
  • 36
  • 61
  • Nice work! Though I decided that I don't like compromises :) – Danatela Sep 29 '15 at 10:00
  • For now, I will accept. The Wikipedia article explained that modern CPUs and memory architecture has no capability to allow truly self-modifying code. – Danatela Sep 30 '15 at 03:28
1

FWIW with the help of Spring4D this can also be done in Delphi:

var
  field: Lazy<VeryExpensiveField>;
begin
  field :=
    function: VeryExpensiveField
    begin
      Result := VeryExpensiveField.Create;
    end;
Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102