Fix #15009: ErrorLogger::readCode: cache code so we don't have to re-read files - #8837
Fix #15009: ErrorLogger::readCode: cache code so we don't have to re-read files #8837ludviggunne wants to merge 14 commits into
Conversation
|
Will add benchmarks and more tests. |
|
I believe this approach still leads to multiple file-opens. the warnings often jump up and down in the file. i.e. when there are more than one location, more than 1 type of warning, ... Spontanously I would cache the whole files. But only cache a few files. |
If there are multiple errors the file is kept open, but it does scan the file each time.
I considered that but I worried it would take up a lot of memory for large files. |
f9eef12 to
2709de3
Compare
2709de3 to
45e8797
Compare
9cb45d8 to
c53c369
Compare
|
Here are some benchmarks from a single file that generates a lot of MISRA warnings. I've compared the main branch and this PR, once without any suppressions and once with all MISRA warnings suppressed. The execution time is more or less the same when suppressions are used, likely due to c976cac. DetailsLinuxWindows |
4192248 to
915667d
Compare
915667d to
f67d7ba
Compare
danmar
left a comment
There was a problem hiding this comment.
I would like one more test in other_test.py that targets this optimisation. I suggest that the testcode contains 2 functions with different warnings so that the line numbers of the reported warnings are going up and down.
Use strace to check how many times the file is reopened. Ideally it is not reopened every time the line number goes up.
It's guaranteed not to reopen if there is only one file, even if the line number goes up and down. But I could add a test with multiple files. |
I agree that is how it works now. But I want a test that catches regressions. I suggest a test with only 1 file. |
|
how does this work when 2 threads reports a warning in a header at the same time? Do they reuse the same cache? |
3a277a3 to
b49b527
Compare
b49b527 to
7598d90
Compare
Did you try this out? When I was implementing the suppression cache I also did a "primitive" implementation of a file cache. I was also slightly worried about the cache taking up a lot of memory at that time, so I had a limit on the number of files that could fit in the cache (1000 I think). |
Did you also do a benchmark with multiple files generating warnings to benchmark the code that removes entries from the cache? |
Here is a comparison where the files are cached as vectors of lines, using the same example project as above (single file, > 1MB). IMHO the difference is negligible. This doesn't account for the overhead of resetting the file pointer though since the line numbers are strictly increasing, I could try to generate a test file that would expose this.
Currently running this same benchmark for a project with multiple files and cross references, but it's taking a while 😅 DetailsLinuxWindows |
Here are benchmarks for a single file that generates 1000 errors, each referencing an earlier line. Still a very small difference. DetailsLinuxWindows |
|
Good to know that they perform the same in that specific scenario. It will be interesting how the two solutions perform in the bigger benchmark that you're doing. Have you thought about the potential drawbacks (if any) about having file streams open for a long time or the maximum number of file streams a process can have open at the same time? I found the proof-of-concept cache that I was playing around with in case it adds any value to this pull request. Details
|
This edit introduces the following changes:
Add a
sourceLineCallbackparameter toErrorMessage::toString, which is used for expanding the{code}part of the error template. IfsourceLineCallbackisnullptr,{code}is expanded to an empty string and no read is performed. This happens in the case of suppressed errors or xml output for example, and replaces the use of thenoCodeparameter from c976cac. The default value for this parameter isErrorMessage::directSourceLineCallback, which does what the functionreadCodedid originally, i.e. just opens and reads the file with no caching.Add a
mSourceCachemember toErrorLogger. This keeps file streams open for reuse to avoid repeatedly opening the same file for each error. The cache has multiple entries since an error path may refer to multiple files. To limit the number of streams open at the same time, an entry is evicted when the size meetsErrorLogger::mSourceCacheSize. To avoid evicting the primary source file (where the error originates), the entries are sorted by priority, where the primary source file has priority 0 and the files referred to in the error path have decreasing priority starting with -1.ErrorLogger::sourceFileCallbackdoes a lookup in the cache before constructing the error context and is passed toErrorMessage::toString. The priority for all entries is decremented on each lookup so that entries that haven't been used for a while are evicted.