Open peterder72 opened 1 year ago
There was another issue a few years ago about caching expression trees to improve performance; IIRC, that was one of the reasons that led to the ExpressionCompiler
extension point. You can set ExpressionCompiler.Instance
to an instance that caches already compiled expression trees, and Moq will then use that.
IIRC, the reason why the cache wasn't added directly to the default implementation was that there was some uncertainty about how to unambiguously recognize already compiled expression trees and map them to a cached delegate (i.e. how to prevent false positives in the cache lookup).
I suppose if the caching logic is reliable and not overly complex, then that previous decision could be revisited. What does your caching implementation look like?
@stakx I haven't implemented any caching mechanisms myself, I just fixed the way expressions are compiled for It.Is, see here. This already fixes the biggest performance drop that causes times of 1m for verification
@peterder72, I see. My apologies for the misunderstanding. Looks like a reasonable change to make. :+1:
The only thing I'm left wondering is whether compiling the expression tree once, ahead-of-time instead of once per invocation of the matcher could possibly affect how & when captured variables get evaluated. I don't think so, but it may be worth verifying.
@stakx I was also thinking about the same thing, but I can't see anything that can become an issue here, since nothing is captured in the scope of It.Is, all references come from the caller. If you can think of how it could go wrong so that I can put it under test, let me know, I'll also give it a think for now
@peterder72, I was mostly thinking about things like:
var obj = ...;
... It.Is(x => x == obj) ...
// ^^^
// when / how often does this captured variable get evaluated?
But on second thought, even that shouldn't be a problem, since compilation in all probability does not perform any kind of partial evaluation.
I can't think of any other reasons why the change shouldn't be made. Looks good to me.
(Be advised that I am not committing anything to this repo for the time being, due to the recent SponsorLink disaster, so I likely won't be the one merging your PR if you decide to submit one.)
Due to lack of recent activity, this issue has been labeled as 'stale'. It will be closed if no further activity occurs within 30 more days. Any new comment will remove the label.
If you submit a PR for this, I'd gladly merge it.
Ah, completely forgot about this one, submitted #1512. It's that same fork I used back when I created this issue, but no conflicts it seems
Mock verification is very slow when used with expressions and large amount of invocations. Repro:
On inspection, most of the time is spent compiling the exact same provided match expression, when predicate is executed for each invocation during verification:
This results in huge performance drop on large number of invocations and unnecessary allocations, while not serving any purpose. From what I can see, this issue has not been discovered previously. I was able to trace it back to this commit, but it might've been still present in a different form.
The solution here would be to only execute the compilation once, and then pass the compled delegate into the match factory method:
I have implemented a fix on my fork, benchmark results for pre, post, and IsAny (for comparison) is as follows:
Benchmark code:
Since I already have a fix on my fork, I'll be happy to submit a PR for this issue myself