Bug #9756
Performance improvements for CONTAINS udf related methods.
100%
History
#1 Updated by Stefanel Pezamosca over 1 year ago
- File start.p
added - Status changed from New to WIP
- % Done changed from 0 to 100
Working at #7020 I also made some performance improvements for CONTAINS udf related methods. LogicalExpressionConverter.Lexer, Contains and FqlToSqlConverter. I decided to extract these optimizations in another branch so they can reach trunk faster.
So, I created 9756a from trunk revision 15764 and committed the changes in revision 15765. There are also some javadoc fixes.
I attached the code that I used to test this.
100000 iterations each.
Without optimisations -> 4600ms - 4800ms With LEC.Lexer optimisations -> 4500ms - 4600ms With LEC.Lexer + Contains optimisations -> 3600ms - 3800msSo ~20% improvement.
Biggest bottleneck is FQL parsing (FQLParser). Adding a cache for already parsed fql ASTs shows great performance improvement for this specific scenario.
Above optimisations + FQL AST caching -> 2300ms - 2400msSo another ~35% improvement.
There is a total of ~50% improvement for this scenario with the 9756a changes.
#3 Updated by Stefanel Pezamosca over 1 year ago
- Assignee set to Stefanel Pezamosca
#4 Updated by Stefanel Pezamosca over 1 year ago
- reviewer Alexandru Lungu, Ovidiu Maxiniuc added
#5 Updated by Ovidiu Maxiniuc over 1 year ago
Review of 9756a/15766.
I agree that these are two directions we can improve performance. The ideas are good. But I think there are a few issues to discuss:- in
FqlToSqlConverter, thefqlAstCacheis static, and the key is just thefql. This is not correct. The current solution will work only on simple cases. Think of two queries on DIFFERENT databases (maybe different buffers of same database?) which apparently request apparently the same data from SQL, but the table structure are different. The solution is to create akeyforfqlAstCachewhich contains the DMO classes as well. Their paths contain enough information to distinct tables from different databases, even if they have the same name; - also in
FqlToSqlConverter.fqlAstCache, I think there are 90% cases when the AST tree does not require to beduplicate()-d when added to cache or extracted. That is because theFQLAst rootis usually only processed locally, intoSQL()and does not 'leak'. For cases when the instance is created andpreprocess()-ed, we can add an annotation to mark 'mutable' trees in case they are affected byprocessStatement(). I think this happens only for word-indexed instances in case of SELECT statements. This is just an assumption and need to be double checked; - related to the new, rather large, boolean arrays in
ContainsandLogicalExpressionConverter. I think we can replace these occurrences withBitSet. These are pretty fast data structures and use the memory optimally (1 bit perboolean), in a dynamic manner (allocating memory only for set places); - in
Functions.getDelimiterPattern()andLogicalExpressionConverter$Lexer(), the compiled patterns are simplified. Also in the latter caseskipPatternsisstaticand NOT synchronised! At least not evidently. I looked up the call stack a few levels and I couldn’t spot any.
#6 Updated by Stefanel Pezamosca over 1 year ago
Ovidiu Maxiniuc wrote:
Review of 9756a/15766.
I agree that these are two directions we can improve performance. The ideas are good. But I think there are a few issues to discuss:
- in
FqlToSqlConverter, thefqlAstCacheis static, and the key is just thefql. This is not correct. The current solution will work only on simple cases. Think of two queries on DIFFERENT databases (maybe different buffers of same database?) which apparently request apparently the same data from SQL, but the table structure are different. The solution is to create akeyforfqlAstCachewhich contains the DMO classes as well. Their paths contain enough information to distinct tables from different databases, even if they have the same name;
The cache I made only saves the ast given by FQLParser, and has only the String fql as argument. So, without any other processing.
#7 Updated by Stefanel Pezamosca over 1 year ago
Ovidiu Maxiniuc wrote:
- also in
FqlToSqlConverter.fqlAstCache, I think there are 90% cases when the AST tree does not require to beduplicate()-d when added to cache or extracted. That is because theFQLAst rootis usually only processed locally, intoSQL()and does not 'leak'. For cases when the instance is created andpreprocess()-ed, we can add an annotation to mark 'mutable' trees in case they are affected byprocessStatement(). I think this happens only for word-indexed instances in case of SELECT statements. This is just an assumption and need to be double checked;
Hmm, I think that the cache only needs to be made for queries with CONTAINS because for these queryWasRewritten would be true and because of this the cache used in Query.createSqlQuery is not used.
#8 Updated by Stefanel Pezamosca over 1 year ago
Ovidiu Maxiniuc wrote:
- related to the new, rather large, boolean arrays in
ContainsandLogicalExpressionConverter. I think we can replace these occurrences withBitSet. These are pretty fast data structures and use the memory optimally (1 bit perboolean), in a dynamic manner (allocating memory only for set places);
boolean[] is fastest with some memory compromise indeed. I will also test with BitSet.
- in
Functions.getDelimiterPattern()andLogicalExpressionConverter$Lexer(), the compiled patterns are simplified. Also in the latter caseskipPatternsisstaticand NOT synchronised! At least not evidently. I looked up the call stack a few levels and I couldn’t spot any.
Oh, right skipPatterns should have been a ConcurrentHashMap.
Do you have anything else to add ?
#9 Updated by Ovidiu Maxiniuc over 1 year ago
Stefanel Pezamosca wrote:
The cache I made only saves the ast given by FQLParser, and has only the String fql as argument. So, without any other processing.
That's right. I've been deceived when comparing the revisions. Scratch the first point. And seconds, as well. Unless we extend the cache to processed trees.
boolean[] is fastest with some memory compromise indeed. I will also test with BitSet.
That's true. But BitSet is optimised for accesses, so I think the trade is worth it.
- in
Functions.getDelimiterPattern()andLogicalExpressionConverter$Lexer(), the compiled patterns are simplified. Also in the latter caseskipPatternsisstaticand NOT synchronised! At least not evidently. I looked up the call stack a few levels and I couldn’t spot any.Oh, right
skipPatternsshould have been a ConcurrentHashMap.
Correct. I was thinking of external synchronisation. Switching to ConcurrentHashMap should do it.
Do you have anything else to add ?
Not at this time :).
#10 Updated by Stefanel Pezamosca over 1 year ago
I changed boolean[] to BitSet and used ConcurrentHashMap for skipPatterns in 9756a revision 15767.
#11 Updated by Ovidiu Maxiniuc over 1 year ago
Cool. I have looked at the code. The changes are minimal. They are OK in my opinion. However, we can assume the first 128 characters (base Latin set) have high probabilities to be processed so we can pre-allocate them by using new BitSet(128).
At the same time, please redo the original 100000-iterations test from the 1st note and post the results.
#12 Updated by Stefanel Pezamosca over 1 year ago
- reviewer deleted (
Alexandru Lungu)
Ovidiu Maxiniuc wrote:
Cool. I have looked at the code. The changes are minimal. They are OK in my opinion. However, we can assume the first 128 characters (base Latin set) have high probabilities to be processed so we can pre-allocate them by using
new BitSet(128).At the same time, please redo the original 100000-iterations test from the 1st note and post the results.
Done. New results with updated baseline. After multiple executions, the values observed were approximately within the following range:
Without 9756a: -> 5200ms - 5600ms With 9756a: -> 2600ms - 2800ms
#13 Updated by Stefanel Pezamosca over 1 year ago
- Status changed from Review to Internal Test
I will continue testing the functionality with different parameters for contains.
#14 Updated by Stefanel Pezamosca over 1 year ago
Oh, I discovered an issue that is also in trunk. One example of problematic statement contains(words, -) I will fix this in 9756a.
#15 Updated by Stefanel Pezamosca over 1 year ago
I fixed this in 9756a revision 15769.
Other tests passed. If there are no other objections I think 9756a is safe for trunk.
#16 Updated by Stefanel Pezamosca over 1 year ago
- Status changed from Internal Test to Review
I made some other minor changes in revision 15769.
#17 Updated by Ovidiu Maxiniuc over 1 year ago
- Status changed from Review to Internal Test
O am OK with the changes in r15769.
#18 Updated by Stefanel Pezamosca over 1 year ago
I have continued testing various CONTAINS testcases and there were no other issues.
This can be merged.
#19 Updated by Alexandru Lungu over 1 year ago
- Status changed from Internal Test to Merge Pending
Please merge 9756a to trunk after 9676a.
#20 Updated by Stefanel Pezamosca over 1 year ago
Branch 9756a was merged into trunk as rev. 15784 and archived.
#21 Updated by Stefanel Pezamosca over 1 year ago
- Status changed from Merge Pending to Test