-
Notifications
You must be signed in to change notification settings - Fork 277
Adding smallest_unused_suffix cache #3296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding smallest_unused_suffix cache #3296
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to see an analysis as to why it is even happening that there are this many symbols with the same prefix. And then data (how much faster is it, on which benchmarks). And commits that actually provide a rationale for the change happening.
I dare saying this because ff25b2c.
@@ -787,8 +788,11 @@ bool java_bytecode_languaget::typecheck( | |||
break; | |||
case LAZY_METHODS_MODE_EAGER: | |||
{ | |||
symbol_table_buildert suffix_aware_table = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suffix_aware_table
-> symbol_table_builder
@@ -787,8 +788,11 @@ bool java_bytecode_languaget::typecheck( | |||
break; | |||
case LAZY_METHODS_MODE_EAGER: | |||
{ | |||
symbol_table_buildert suffix_aware_table = | |||
symbol_table_buildert::build(symbol_table); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
::build
-> ::wrap
journalling_symbol_tablet journalling_symbol_table = | ||
journalling_symbol_tablet::wrap(symbol_table); | ||
journalling_symbol_tablet::wrap(suffix_aware_table); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To check: does the journalling table forward next_suffix
to its wrapped table?
src/goto-cc/compile.cpp
Outdated
@@ -692,37 +693,40 @@ void compilet::add_compiler_specific_defines(configt &config) const | |||
|
|||
void compilet::convert_symbols(goto_functionst &dest) | |||
{ | |||
symbol_table_buildert suffix_aware_table = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suffix_aware_table -> symbol_table_builder
@@ -789,7 +789,7 @@ bool java_bytecode_languaget::typecheck( | |||
case LAZY_METHODS_MODE_EAGER: | |||
{ | |||
symbol_table_buildert suffix_aware_table = | |||
symbol_table_buildert::build(symbol_table); | |||
symbol_table_buildert::build(symbol_table); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally squash this formatting commit into the appropriate precessor commits
In brief your previous fix changed the complexity of generating N symbols with the same prefix in a symbol table of size M from O(NM) to O(N^2), which is definitely an improvement, but this patch bumps it to O(N). I believe the typical case where this really bites us is e.g. 1000 |
Why do we ever have
? |
That is: can we really not do any better than calling all of them |
They're temps generated at a Possible solutions:
|
Also @JohnDumbell recommend separating a commit that just turns |
+1 to that, and possibly even do so in a separate PR: this is quite likely the syntactically most intrusive part of this PR, and at the same time actually just the right thing to do irrespective of this PR. |
@smowton, in response to your earlier elaboration: this PR, and thus item 4 from your list, may be absolutely the right thing to do. But I want to make as sure as by any means possible that we are not fixing symptoms, but actually treat the root cause. And having symbols named |
I'll apply the recommendations / git changes now and then get to work on an actual benchmark. In the particular case where I saw this (and then thought doing something about it might be useful long-term) the actual suffix was in the 4000's. |
@tautschnig that sounds good -- better names wherever possible, and a fresh-symbol routine that can be relied upon not to explode when there are a few thousand name clashes. |
@JohnDumbell 4000+ name clashes seem scary - wouldn't that imply that the function as at least 4000 instructions? Of course that's possible, but it seems worth taking another look. Taking action to fix a problem is good, but I want to be really sure we are fixing the right problem. |
At a guess the pathalogical cases will be mechanically-generated code |
d976534
to
b90ae0b
Compare
@smowton I've also seen such pathological code in certain embedded systems.... sigh. |
b90ae0b
to
4f793eb
Compare
4f793eb
to
842c199
Compare
One keeps the old functionality, while the other just allows us to tell it on what number to start the suffix search.
842c199
to
f48ca75
Compare
5f9eb36
to
7f5890d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
Passed Diffblue compatibility checks (cbmc commit: 7f5890d).
Build URL: https://travis-ci.com/diffblue/test-gen/builds/92299022
@tautschnig Sorry it took a little while, but we did some tests on a c method with 10,000 lines of I tried recreating the test on some less extreme Java examples and only got around a 5% reduction in run-time due to bytecode per method limitation and me not knowing the absolute best way to generate as many temporaries as possible, but I also didn't recognise any noticeable slowdown either. I know it provides similar benefits to the c benchmark on the library I originally found the issue in (Groovy), but trying to measure them precisely is a bit harder because something else is going on to cause that many temporaries to be needed in the first place. If you need anything more precise just say. (Also thanks to @smowton on this) |
(the reason for that particular formulation btw: ternary conditionals get a temporary allocated if either of their branches may have side-effects) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we create names with function_id::tmp::<hash of expr/symbol names in expr>::tmpX
to make clashes less likely?
@peterschrammel don't see any reason why not, beyond the hashing/name creation overall might be slower than just accepting any clashes and doing one additional find using the wrapper? (also more unique names = more instances in the map with smaller counts against them) I guess the differences would be minor either way though, and you'd definitely benefit if you didn't have the wrapper. I think I'd like to do it either way though, better to do it in a different PR? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good except for the comments below. Also it would be nice to copy the report on the experiment into one of the commit messages as it's somewhat cumbersome to go from the git history to GitHub to then find the data.
src/util/symbol_table_builder.h
Outdated
@@ -0,0 +1,113 @@ | |||
// Copyright 2016-2018 Diffblue Limited. All Rights Reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is different from any other file headers in use. The "All Rights Reserved." scares me.
src/util/symbol_table_builder.h
Outdated
#ifndef CPROVER_UTIL_SYMBOL_TABLE_BUILDER_H | ||
#define CPROVER_UTIL_SYMBOL_TABLE_BUILDER_H | ||
|
||
#include "symbol_table.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this header required here?
src/util/symbol_table_builder.h
Outdated
// Check if we have an entry for this particular suffix, if not, | ||
// create baseline. | ||
auto suffix_iter = next_free_suffix_for_prefix | ||
.insert(std::pair<std::string, std::size_t>(prefix, 0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to use {prefix, 0}
instead of std::pair<....>(prefix, 0)
@@ -100,6 +100,11 @@ class journalling_symbol_tablet : public symbol_table_baset | |||
return result; | |||
} | |||
|
|||
std::size_t next_unused_suffix(const std::string &prefix) const override |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment about the commit message: I don't understand "Due to the inclusion of another method we just need to make sure any calls are routed to the wrapped table and not the parent." Which method is this "another" method that's causing trouble?
@@ -23,6 +23,7 @@ Author: Daniel Kroening, kroening@kroening.com | |||
#include <json/json_parser.h> | |||
|
|||
#include <goto-programs/class_hierarchy.h> | |||
#include <util/symbol_table_builder.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Place this include with the other ones from util
above.
7f5890d
to
ca71045
Compare
This wrapper is purely to allow the symbol table to keep track of the suffixes that it has currently seen. This improves speed in a situation where a large amount of instances with the same prefix are requested.
Due to the inclusion of an additional overridable method on symbol_table_baset we just need to make sure any calls to this wrapper are routed to the wrapped table and not to the base instance.
Tests were done on a c method with 10,000 lines of x += argc ? f() : g(); to generate a large amount of temp symbols needing to be created, and with this change applied it took 1:11 (m:s) to finish generating the GOTO before it got stuck on symex, and without it the generation ran longer than 30 minutes. In attempting to recreate the issue in Java it only got around a 5% reduction in run-time due to bytecode per method limitation and me not knowing the absolute best way to generate as many temporaries as possible, but I also didn't recognise any noticeable slowdown either. In the Java instance where this issue was first recognized it was building a similar number of temporaries as the C program, but directly benchmarking that is problematic because there is likely other issues causing it to require this number of temporaries in the first place. But it would cause similar speed increases in any language that requires a large amount of symbols to be created that have a clashing prefix.
ca71045
to
6425eb1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
Passed Diffblue compatibility checks (cbmc commit: 6425eb1).
Build URL: https://travis-ci.com/diffblue/test-gen/builds/93065007
This just speeds up symbol name generation in situations where there are large amounts of symbols already existing with the same prefix. A similar approach to the journalling_symbol_tablet in how it works - you just wrap any current symbol table, and as long as that instance lasts it'll store what the next available index is so usually you'll only need to do a minimal amount of finds to get the next free suffix.
It doesn't go overboard with trying to work out if there are any holes in the symbol table that we could re-use because it's essential that each suffix + prefix combo be predictable, so only clears the cache when you clear the whole symbol table.