3

Say that a method has an optional Action or Func<T, T> argument that will usually be the default value of null. This usually-null lambda expression will be called many times.

In this situation, is it better to:

  1. Null-check the lambda expression before each use, or...

    a. Call nullable Action's using action?.Invoke().

    b. Call nullable transform Func<T, T>'s as transform == null ? value : transform(value).

  2. ...null-check the expression once when the method is called, then replace it with a transparent variant?

    a. Replace null Action's with new Action(() => { }).

    b. Replace null Func<T, T>'s with new Func<T, T>((T value) => { return value; }).

For this question, please assume:

  • This is deeply nested math code, so its performance is relevant.

  • The code's readability is not meaningfully affected.

I ask this question because I frequently have methods that allow for optional lambda expressions, which are incredibly useful, but I'm never sure if I should null-check them on declaration or if I should just null-check them on each potential usage.

I imagine that, if the lambda expression will only be executed once, then it's best to just null-check at that point.

But, I'm curious about when the method constructs an object that will call the lambda expression many times. Assuming that the argument will usually be null, then it seems like this question reduces to a simpler question: is it faster to null-check a lambda or execute one that doesn't do anything?

Nat
  • 1,085
  • 2
  • 18
  • 35
  • [Race your horses](https://ericlippert.com/2012/12/17/performance-rant/) – René Vogt Feb 16 '17 at 06:23
  • @RenéVogt Hah yeah, that's a good point, and why I point out that the performance is relevant in this case (as people often ask about performance when it doesn't even matter). But, as EricLippert notes in that blog, it's difficult to get meaningful benchmarks from a garbage-collected, JIT-compiled runtime. So, I'm hoping for some insight into the general nature of executing a transparent lambda (can the JIT optimize it out, under dead-code optimization logic?) versus null-checking (which is a branching operation, that might mess with a CPU's branch predictor). [...] – Nat Feb 16 '17 at 06:27
  • @RenéVogt [...] And since it's a complicated trade-off that depends on underlying generalizations (like how the JIT-er's working and what affects the CPU's branch predictor), I was hoping that someone with knowledge of those issues could comment on it. While I suspect that it could have a meaningful impact if one method is implemented throughout an entire project versus the other, I'm not sure if I'd get meaningful results by just looping over one versus the other in a quick test method with a timer attached. – Nat Feb 16 '17 at 06:28
  • @RenéVogt I also figure that this is such a common problem that others would be interested in it, too. I mean, nullable lambda arguments probably appear in a large portion of .NET projects, right? – Nat Feb 16 '17 at 06:32
  • I understand your question and that you probably also want to know _why_ which is faster. There was no offence intended in posting that link. I just like that article and think it's always a good read when talking about performance. – René Vogt Feb 16 '17 at 06:35
  • if the performance is **that** important, then you can just make a copy of the methods without the optional parameters – Slai Feb 16 '17 at 06:48
  • @Slai Agreed, and that's often the solution in simpler cases. Still, it requires 2^n variants for when there's n arguments, which quickly bloats the code and makes it hard to modify/maintain. Plus I think that code bloat hurts performance too. – Nat Feb 16 '17 at 06:54
  • I don't think it was necessary that you delete your comments. I changed my answer because you convinced me. – Szabolcs Feb 23 '17 at 20:49

2 Answers2

1

If you check this answer you will see some performance benchmarks for a very similar delegate problem, although in this case it has to do with raising events:

The cost for raising an event with an empty delegate is roughly twice that for raising it with a null check first.

I'm imagining that performance implications would be similar for your case.

Community
  • 1
  • 1
John Wu
  • 50,556
  • 8
  • 44
  • 80
0

3 . using an empty method should be a bit faster than creating a new empty lambda each time:

static void _() { }

static void b(Action a = null)
{
    if (a == null) a = _;
}

4 . to avoid the null check

static void _() { }

static void b() { b(_); }

static void b(Action a)
{

}
Slai
  • 22,144
  • 5
  • 45
  • 53
  • Does .NET actually create a new object for a new lambda each time? I know that, behind the scenes, a lambda expression is treated as a new class definition (called a "DisplayClass" or something like that), where the local context is essentially a parameter that the "DisplayClass" is constructed with and stores as a field. But, when nothing's captured, then the DisplayClass should basically be static (as you've demonstrated). It'd seem like a poor implementation if .NET's actually creating new instances of an entirely static class each time. – Nat Feb 16 '17 at 07:07
  • @Nat I would recommend comparing the generated IL with http://ilspy.net as I am not expert on the internals but I don't think the compiler can optimize `new Action(() => { })` as well as `static void _() { }` – Slai Feb 16 '17 at 07:16