Skip to content

Add a more aggressive fast-path for Invoke(object[])#372

Open
WAcry wants to merge 8 commits intodynamicexpresso:masterfrom
WAcry:david/perf2
Open

Add a more aggressive fast-path for Invoke(object[])#372
WAcry wants to merge 8 commits intodynamicexpresso:masterfrom
WAcry:david/perf2

Conversation

@WAcry
Copy link

@WAcry WAcry commented Jan 31, 2026

Stack

#372 currently contains all commits from #371 plus one extra commit.

What & why

Invoke(object[]) is a common hot path. Even after #371, the fallback path still pays for:

  • building usedArgs (when declared != used), and/or
  • DynamicInvoke overhead.

This PR introduces an additional fast-path that, when applicable, bypasses both costs.

Changes

  • Add a more aggressive Invoke(object[]) fast-path:
    • In Lambda’s internal InvocationContext, compile once a Func<object[], object> “fast invoker”.
    • When the declared argument array passed to Invoke(object[]) meets the type assignability / nullable-compatibility constraints, invoke via the fast invoker:
      • Avoids DynamicInvoke
      • Avoids constructing usedArgs
    • Otherwise, fall back to the safe path:
      • build usedArgs + DynamicInvoke

Benchmark results

Environment:

  • BenchmarkDotNet v0.14.0
  • Windows 11 (10.0.26200.7705)
  • 11th Gen Intel Core i7-11800H (16 logical / 8 physical)
  • .NET 8.0.23 (x64 RyuJIT)
  • .NET SDK 10.0.102

Raw numbers

Method master Mean (ms) PR #371 Mean (ms) PR #372 Mean (ms) master Alloc (MB) PR #371 Alloc (MB) PR #372 Alloc (MB)
Invoke cached lambda (object[]) 221.70 10.04 2.867 260.93 9.16 2.29
Invoke cached lambda (IEnumerable<Parameter>) 175.80 96.20 97.169 191.50 37.38 37.38
Eval (IEnumerable<Parameter>) 25,503.70 16,346.04 16,821.122 17,172.52 16,800.18 16,781.66

Derived comparisons

Method master → #371 speedup master → #372 speedup #371#372 speedup master → #371 alloc reduction master → #372 alloc reduction #371#372 alloc reduction
Invoke cached lambda (object[]) 22.08x 77.31x 3.50x 28.49x 113.94x 4.00x
Invoke cached lambda (IEnumerable<Parameter>) 1.83x 1.81x 0.99x 5.12x 5.12x 1.00x
Eval (IEnumerable<Parameter>) 1.56x 1.52x 0.97x 1.02x 1.02x 1.00x

Notes:

  • PR #372 delivers an additional big win for Invoke(object[]):
    • vs PR #371: ~3.5x faster, ~4x fewer allocations
    • vs master: ~77x faster, ~114x fewer allocations
      (in this benchmark scenario)
  • Invoke(IEnumerable<Parameter>) / Eval(...) are effectively unchanged from #371 — this fast-path is intentionally targeted at declared-order object[].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant