-
Notifications
You must be signed in to change notification settings - Fork 37
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
QCheck: improve int shrinker fast path #173
Conversation
I see the point of trying The speed improvements on the example are mostly because of how simple the examples are and because in most cases, trying However, maybe this is a sign that we might need a somewhat more complex shrinking strategy, i.e. instead of shrinking one counter-example into a sequence, and then restarting from the first counter-example in the sequence, we might want to add some things such as:
|
There are many things to say here. I've tried to summarize some of them starting here: #153 (comment) Overall, those observations convinced me that trying 0 first is actually the fast path. For a list of length n if the element values do not matter, repeated halving will spend O(n log n) reduction steps, Now imagine functions of ints, where it will have to shrink both key and value of each irrelevant key-value entry repeatedly.
I completely agree that trying Here are some numbers (with wc counting steps - *.expected_old denotes the "old expected outputs, *.expected are the new):
As we can see, it is only the int_* tests that got worse. (also for the nat_ but I forgot to save that one). If 209080 shrink attempts are hard to relate to, consider the file sizes:
12M for fun_last_foldleftright_qcheck for only 994 shrink steps!
I actually suggested something like a 1-element cache (context awareness) in the QCheck2 shrinker for eliminating Overall, this is a trade-off between practical bang-for-buck and clever algorithms where QCheck2's choice wins.
I've tried to argue for why trying
In any case we have a situation where:
|
I don't disagree with your points, and I agree that in absolute, trying This is not a problem that is reflected by the testsuite because the tests in it are too simple. Most (if not all), the list tests you mention basically do not care about the individual elements and are mainly tests of the length of the list. Additionally, the property tested is almost always trivial (or rather extremely quick) to compute. That explains why in your experiements to add a cache, the performance was worse: basically, the cache check was more costly that the property to check, which is something that I don't think is very usual in real-life qcheck uses. All of that is not to say that these tests are bad; in the context of a testsuite, it's good to have simple tests such as these, however, these are not a good benchmark to use when comparing shrinking strategies. |
Spending half the attempts repeatedly trying
I am happy that this effort calls for a performance test suite for QCheck. I think it would be a very nice addition (hint, hint). Up until now we have
As such, we have used these tests to benchmark our shrinkers in the past. Constructively and concretely: What would be a good benchmark to compare shrinking strategies in your opinion? |
Closing as this is subsumed by #235 |
Here is an example of what #172 opens up for:
A simple change (inspired by QCheck2) that tries
0
first on eachint
shrinker invocation has drastic improvements:fun_last_foldleftright_qcheck.expected
's line count (read: 10x less shrink attempts!)fun_first_foldleftright_qcheck
is halved - and even finds a smaller counterexamplelist_shorter_100_qcheck
andlist_size_shorter_100_qcheck
are reduced to a quarterlist_shorter_10_qcheck
is reduced to a thirdYou can also see the shrink counts in test/core/QCheck_expect_test.expected all improve - except for a the
int
-ones that increment by one (the user-reported shrink-steps only count the successfull ones).A quick timing from
test/core
ofimproves (before):
to (after):
This is a tradeoff, naturally:
int_zero_qcheck
andint_smaller_209609
spend more shrinking steps to try0
repeatedly (like QCheck2's int shrinker)Given the above, I think this tradeoff is worth it though.