While doing some CPU sampling in our production environment, we encountered some strange results. It seems like that, during the evaluation of NiFi expressions, the modification of a HashSet is the most expensive operation in this process.
This feels pretty unrealistic considering all the other processing related to evaluating NiFi expressions.
After reviewing some code and some profiling it just looks like this HashSet modification is performed way more often than required. Especially that it is done at each evaluation.
This profiling output was produced with the following unit test:
The key question is: Why are the Evaluator Objects (and all the stuff related to it) built twice:
- Once in ExpressionCompiler.compile()
- Once again in CompiledExpression.evaluate()
In other words: Every call to CompiledExpression.evaluate() leads to a new ExpressionCompiler being created and expensive calls being made. Why not just reuse Evaluator objects created beforehand that are stored in the CompiledExpression?
Is there a specific design decision behind that? It looks like there is room for performance improvement, especially for heavily used processors.
On our live system, where we perform expensive tasks like language detection, mail parsing and such, this situation causes the most amount of CPU eaten by the expression language evaluation.
Thank you very much for looking into this.