Feature #11317
Implementing multi-threaded parsing
0%
Related issues
History
#1 Updated by Octavian Adrian Gavril 6 months ago
- Related to Feature #3211: implement multi-threaded pattern engine and rework the ConversionDriver to leverage it added
#3 Updated by Alexandru Lungu 19 days ago
- Assignee set to Octavian Adrian Gavril
While testing the latest trunk with changes from #3211, I saw several times threads blocking on public static synchronized int lookupTokenType(String tokenName) from ProgressParser. This is quite important as it is called by BinaryAstCodec.readNode. When I did a thread dump for a conversion with 16 threads, most of them were loading ASTs with BinaryAstCodec. As loading means looking up token types, the lookupTokenType made the threads constantly wait for the monitor at each token read.
Why would lookupTokenType be syncrhonized. I understand that tokenLookup is lazily computed, but it is computed only once, as it is a static collection. After that, all threads need to read from that map without contention. Currently they block each other when reading from tokenLookup.
Can we compute lookupTokenType only once at the bootstrap time and eliminate the synchronized when looking up token type? It is the only synchronization inside ProgressParser AFAIK.
I would expect to be a quick fix for a large gain.
#5 Updated by Octavian Adrian Gavril 19 days ago
Created task branch 11317a.
#6 Updated by Octavian Adrian Gavril 19 days ago
- % Done changed from 0 to 100
- Status changed from New to WIP
I've committed the change in 11317a/16735.
#7 Updated by Octavian Adrian Gavril 19 days ago
- Status changed from WIP to Review
- reviewer Alexandru Lungu added
Alex, please review. I have tested it with Hotel_GUI so far.
#8 Updated by Alexandru Lungu 19 days ago
- Status changed from Review to Internal Test
Review of 11317a:
- I am OK with the changes.
Please run a large customer application to check if there is any visible performance improvement and regression test at the same time. IMHO, it is a (very) safe change, so one large application test should be enough.
#9 Updated by Octavian Adrian Gavril 19 days ago
The large customer application completed successfully. I got 3h13m with 11317a and 2h54m with trunk/16731. There are no differences between the generated outputs. I ran both using 16 threads and a 24G conversion heap. Here are my machine's specifications: Show
#10 Updated by Octavian Adrian Gavril 19 days ago
The time difference was unexpected, so I checked for potential causes. I found that the conversion with 11317a ran with the debug agent enabled, which likely explains the variance. I'll rerun the conversion to confirm.
#11 Updated by Octavian Adrian Gavril 15 days ago
I ran both conversions again. The one with 11317a took 2h56m, and the one with trunk took 2h52m. The results are more expected now. There's still no noticeable performance improvement, but the execution times are virtually the same.
#12 Updated by Alexandru Lungu 8 days ago
- Status changed from Internal Test to Merge Pending
Please merge 11317a to trunk now.
#13 Updated by Octavian Adrian Gavril 8 days ago
- Status changed from Merge Pending to WIP
- % Done changed from 100 to 0
Branch 11317a was merged into trunk as rev. 16753 and archived.
Moved back to WIP and 0% Done for the rest of the work that needs to be done.
#14 Updated by Alexandru Lungu 8 days ago
- File locks.png added
- File locks2.png added
Octavian, I am still running trunk/16749, but I found a similar problem that might not be assessed by 11317a:

I am not entirely sure what was the phase the is being run when this was captured. On a thread dump, I see:
"Conversion-Thread-2" #36 prio=5 os_prio=0 cpu=74657,68ms elapsed=169,18s tid=0x00007542d5f87e30 nid=0xe4a2 waiting on condition [0x00007542363f2000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.20/Native Method)
- parking to wait for <0x000000042080fec8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.20/LockSupport.java:211)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@17.0.20/AbstractQueuedSynchronizer.java:715)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@17.0.20/AbstractQueuedSynchronizer.java:938)
at java.util.concurrent.locks.ReentrantLock$Sync.lock(java.base@17.0.20/ReentrantLock.java:153)
at java.util.concurrent.locks.ReentrantLock.lock(java.base@17.0.20/ReentrantLock.java:322)
at com.goldencode.ast.XmlFilePlugin.loadTree(XmlFilePlugin.java:287)
at com.goldencode.ast.AstManager.loadTree(AstManager.java:373)
at com.goldencode.p2j.pattern.AstSymbolResolver.loadTree(AstSymbolResolver.java:362)
at com.goldencode.p2j.pattern.AstSymbolResolver.getAst(AstSymbolResolver.java:417)
at com.goldencode.p2j.pattern.CommonAstSupport$Library.getAst(CommonAstSupport.java:1653)
at com.goldencode.p2j.pattern.CommonAstSupport$Library.getAst(CommonAstSupport.java:1635)
at com.goldencode.expr.CE904.execute(Unknown Source)
at com.goldencode.expr.Expression.execute(Expression.java:410)
at com.goldencode.p2j.pattern.Rule.apply(Rule.java:501)
at com.goldencode.p2j.pattern.Rule.executeActions(Rule.java:792)
at com.goldencode.p2j.pattern.Rule.coreProcessing(Rule.java:758)
at com.goldencode.p2j.pattern.Rule.apply(Rule.java:538)
at com.goldencode.p2j.pattern.RuleContainer.apply(RuleContainer.java:665)
at com.goldencode.p2j.pattern.AstWalker.walk(AstWalker.java:262)
at com.goldencode.p2j.pattern.AstWalker.walk(AstWalker.java:210)
at com.goldencode.p2j.pattern.PatternEngine.apply(PatternEngine.java:1834)
at com.goldencode.p2j.pattern.PatternEngine.processAst(PatternEngine.java:1718)
at com.goldencode.p2j.pattern.AstProcessorJob.run(AstProcessorJob.java:255)
at com.goldencode.p2j.pattern.AstProcessorThread.run(AstProcessorThread.java:140)
Locked ownable synchronizers:
- None
"Conversion-Thread-3" #37 prio=5 os_prio=0 cpu=75887,58ms elapsed=169,18s tid=0x00007542d5f07cb0 nid=0xe4a3 waiting on condition [0x00007542888f4000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.20/Native Method)
- parking to wait for <0x000000042080fec8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.20/LockSupport.java:211)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@17.0.20/AbstractQueuedSynchronizer.java:715)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@17.0.20/AbstractQueuedSynchronizer.java:938)
at java.util.concurrent.locks.ReentrantLock$Sync.lock(java.base@17.0.20/ReentrantLock.java:153)
at java.util.concurrent.locks.ReentrantLock.lock(java.base@17.0.20/ReentrantLock.java:322)
at com.goldencode.ast.XmlFilePlugin.loadTree(XmlFilePlugin.java:287)
at com.goldencode.ast.AstManager.loadTree(AstManager.java:373)
at com.goldencode.p2j.pattern.AstSymbolResolver.loadTree(AstSymbolResolver.java:362)
at com.goldencode.p2j.pattern.AstSymbolResolver.getAst(AstSymbolResolver.java:417)
at com.goldencode.p2j.pattern.CommonAstSupport$Library.getAst(CommonAstSupport.java:1653)
at com.goldencode.p2j.pattern.CommonAstSupport$Library.getAst(CommonAstSupport.java:1635)
at com.goldencode.expr.CE904.execute(Unknown Source)
at com.goldencode.expr.Expression.execute(Expression.java:410)
at com.goldencode.p2j.pattern.Rule.apply(Rule.java:501)
at com.goldencode.p2j.pattern.Rule.executeActions(Rule.java:792)
at com.goldencode.p2j.pattern.Rule.coreProcessing(Rule.java:758)
at com.goldencode.p2j.pattern.Rule.apply(Rule.java:538)
at com.goldencode.p2j.pattern.RuleContainer.apply(RuleContainer.java:665)
at com.goldencode.p2j.pattern.AstWalker.walk(AstWalker.java:262)
at com.goldencode.p2j.pattern.AstWalker.walk(AstWalker.java:210)
at com.goldencode.p2j.pattern.PatternEngine.apply(PatternEngine.java:1834)
at com.goldencode.p2j.pattern.PatternEngine.processAst(PatternEngine.java:1718)
at com.goldencode.p2j.pattern.AstProcessorJob.run(AstProcessorJob.java:255)
at com.goldencode.p2j.pattern.AstProcessorThread.run(AstProcessorThread.java:140)
Locked ownable synchronizers:
- None
So, again, there seems to be a contention on AST loading. The locks are delivered by:
ReentrantLock lock = LOCK_CACHE.computeIfAbsent(artifact.getRelativePath(), k -> new ReentrantLock());
- It is weird why all threads want the same artifact almost at the same time. Maybe they require schema p2o (that are only very few).
- The whole situation takes ~4 minutes in a conversion of 20 minutes. After these 4 minutes, another phase kicks in and it works fast again.
However, after around 10 minutes in which all threads are working in parallel, I face this issue again:

On thread dumps I see the same stack traces.
I was wondering why we would need to synchronize the loading of these ASTs? From the looks of it, the process is immutable so it doesn't need synchronization. The same locks are shared indeed with saveTree and persist, but I guess we can use a ReadWriteReentrantLock instead to allow concurrent reads.
As a side idea, I wonder if we can keep a static cache of ASTs to be reused directly from memory. So, instead of flushing to disk and reading again, to have a small cache in-memory to keep the most used ASTs (schema ASTs I suppose are one of them, but super-classes used anywhere can fit as well). We need to avoid thrashing such cache with one-time asts, so maybe we can also persist a "hit count" and if the hit count exceeds a threshold to also save the ASTs in a memory registry.
#15 Updated by Octavian Adrian Gavril 8 days ago
I'll start experimenting based on your ideas.
#16 Updated by Alexandru Lungu 8 days ago
As a side idea, I wonder if we can keep a static cache of ASTs to be reused directly from memory. So, instead of flushing to disk and reading again, to have a small cache in-memory to keep the most used ASTs (schema ASTs I suppose are one of them, but super-classes used anywhere can fit as well). We need to avoid thrashing such cache with one-time asts, so maybe we can also persist a "hit count" and if the hit count exceeds a threshold to also save the ASTs in a memory registry.
Don't force the cache now. It may be a small gain. The ReadWriteReentrantLock may be major.
#17 Updated by Alexandru Lungu 7 days ago
Some context on a similar project as the one above.
- Parser: 8m32s (I know this is not multi-threaded)
- Post-parse fix-ups: 47s (can't tell why this is so slow)
- Early annotations: 2m09s -> this was the phase from which I extracted the first image above
- Gap analysis: 15s
- Schema fix-ups: 2s
- Schema annotations: 6s
- Preprocess P2O: 5s
- P2O Generation: 13s
- P2O post-processing: 2s
- Generate DMO: 10s
- Generate Java DMO: 11s
- Unreachable Code 10s
- Code Conversion Anno Prep: 17s
- Code Conversion Annotations: 10m53s -> this was the phase from which I extracted the second image above
- Frame generator: 13s
- Business Logic Base Structure: 28s
- Core Code Conversion: 2m2s -> this is all green, no locking. It is pure TRPL work (most threads are in
RuleContainer.apply) - Generate Java: 19s
I may have missed some phases, but they were most probably < 10s.
#18 Updated by Octavian Adrian Gavril 6 days ago
I have tested the changes with ReentrantReadWriteLock, but it's really hard to see any noticeable improvement. I think performance strongly depends on the project and how it is structured, as we might be dealing with more dependencies between artifacts.
Alex, could you provide the Utilizing N concurrent thread(s) to process M file(s); K CPU(s), H MiB heap log message if it is still available?
#19 Updated by Alexandru Lungu 6 days ago
Utilizing 16 concurrent thread(s) to process 6800 file(s); 16 CPU, 16384 MiB heap.
This is what I have.
#20 Updated by Alexandru Lungu 6 days ago
I have tested the changes with ReentrantReadWriteLock, but it's really hard to see any noticeable improvement. I think performance strongly depends on the project and how it is structured, as we might be dealing with more dependencies between artifacts.
Feel free to commit the changes, I can test them and let you know if I see an improvement on it.
#21 Updated by Octavian Adrian Gavril 6 days ago
Alexandru Lungu wrote:
Feel free to commit the changes, I can test them and let you know if I see an improvement on it.
Created task branch 11317b.
I've committed the changes in 11317b/16762.