nreco / lambdaparser

Runtime parser for string expressions (formulas, method calls). Builds dynamic LINQ expression tree and compiles it to lambda delegate.
http://www.nrecosite.com/
MIT License
307 stars 55 forks source link

use ConcurrentDictionary to replace lock in expression cache #51

Closed WAcry closed 2 months ago

WAcry commented 2 months ago

PR Description

Hello, thank you very much for your work! It has been very helpful for my project. In this PR, I suggest replacing the expression cache's Dictionary (with lock) with ConcurrentDictionary.

While the performance impact of a lock is usually acceptable, when Eval() is heavily used to evaluate the same expressions (which is precisely when and why we need caching), it puts more pressure on threads and the CPU, making it more time-consuming compared to the ConcurrentDictionary. ConcurrentDictionary has been heavily optimized for multi-thread scenarios, especially when there are many reads and fewer writes. Its read performance is almost identical to a Dictionary without any lock.

Considering that this library targets net45, netstandard1.3, and netstandard2.0, all of which support ConcurrentDictionary, this change should be feasible. If, for some reason, there is a need to support older versions in the future, we can use #if and #elif compilation directives.

Additionally, I have updated the README. When my colleagues and I first used this library last year and this year, we made the same mistake, instantiated a new instance each time we called it instead of making it a singleton. This wasted its caching capabilities. Also, if a user frequently evaluates many unique expressions, it might put pressure on memory in default. Therefore, I believe it is necessary to include this parameter in the README to help new users utilize this excellent work more effectively.

VitaliyMF commented 2 months ago

@WAcry replacing lock with ConcurrentDictionary makes a lot of sense. Shipped in 1.1.2

Thank you for your contribution!