Skip to content

Do not unnecessarily sort guard conjuncts [blocks: #3486] #1998

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

Merged
merged 1 commit into from
May 19, 2021

Conversation

tautschnig
Copy link
Collaborator

They are constructed in a consistent order anyway.

if(it1!=op1.end() && *it1==*it2)
it1=op1.erase(it1);
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this works? For example, a & b - b would not erase the b, since it1 now has no way of moving except when erasing an element.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relatedly, for vectors this has worst-case quadratic cost. How about making a utility inplace-set-difference that provides a good place to benchmark this sort of repeated single-element erase vs. std::set_difference followed by swap?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this would not work with a & b - b, but as the commit message says: we never construct those. My proposal for a full solution is #310: use BDDs. I'm open to all other ideas. But right now it's just computational overhead...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case I suggest renaming this remove_prefix_conjuncts, since its behaviour is much more specific than a generic operator-=, and add a doxy comment specifying that's all it can do. Also consider using std::mismatch?

Copy link
Contributor

@allredj allredj left a 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: 6bd4556).
Build URL: https://travis-ci.com/diffblue/test-gen/builds/104021437

@tautschnig
Copy link
Collaborator Author

For the record: on ReachSafety-ECA this increases the number of symex steps per second from 2961.83 to 3580.92 - a speedup of approximately 20%. This is all due to saving approximately 2500 seconds in sort_operands (by not calling it).

Similar savings may be possible when using BDDs, in particular with CUDD. Benchmarking thereof started just now.

@tautschnig
Copy link
Collaborator Author

Adding data on BDDs (using CUDD): the operator|= is now, as hoped, basically free (0.5 seconds instead of 440 seconds with develop). Somewhat surprisingly, however, the cost of merge_ireps is now a lot higher. Consequently we only achieve 2608.81 steps per second.

@romainbrenguier
Copy link
Contributor

@tautschnig

Adding data on BDDs (using CUDD): the operator|= is now, as hoped, basically free (0.5 seconds instead of 440 seconds with develop). Somewhat surprisingly, however, the cost of merge_ireps is now a lot higher. Consequently we only achieve 2608.81 steps per second.

The guards sometimes become much bigger with BDDs than with exprt. On my benchmarks I also noticed that while the global execution was 30% faster, the execution with --show-vcc --verbosity 0 was slower with BDDs on average. I also noticed merge_ireps taking a long time, in particular in the example I had to disable (grep for "bdd-expected-timeout").

@tautschnig
Copy link
Collaborator Author

The above data was all based on profiling information, which I prefer not to exclusively rely on. The optimised, competition-setting runs yield (more == better):

develop: 116 points
this PR: 119 points
BDDs/CUDD: 91 points

This of course is only a single (and somewhat peculiar: basically no pointers, but otherwise heavily stressing goto-symex) category.

@tautschnig
Copy link
Collaborator Author

I also noticed merge_ireps taking a long time, in particular in the example I had to disable (grep for "bdd-expected-timeout").

I'll try to do some benchmarking with HASH_CODE enabled at a later time to see whether that helps.

@romainbrenguier
Copy link
Contributor

@tautschnig

I'll try to do some benchmarking with HASH_CODE enabled at a later time to see whether that helps.

Oh yes I always use HASH_CODE. For the merging of ireps it seems redundant for BDDs since all nodes that are logically equivalent are already "merged". So maybe the merge and the conversion from BDD to exprt should be done together. I will try some things.

@romainbrenguier
Copy link
Contributor

@tautschnig

Adding data on BDDs (using CUDD): the operator|= is now, as hoped, basically free (0.5 seconds instead of 440 seconds with develop). Somewhat surprisingly, however, the cost of merge_ireps is now a lot higher. Consequently we only achieve 2608.81 steps per second.

I'm not sure I understand what these steps represent. Could you explain? If BDDs lead to fewer steps being executed for analyzing the same program, you could have fewer steps per second but still be faster?

@tautschnig
Copy link
Collaborator Author

If BDDs lead to fewer steps being executed for analyzing the same program, you could have fewer steps per second but still be faster?

Yes, this is certainly possible, hence me also posting the scores achieved in that category. What I'm looking at in the profiles is:

  1. The number of calls to goto_symext::symex_with_state (that's basically the total number of runs of CBMC).
  2. The number of calls to symex_bmct::symex_step (that's what I consider the total number of symbolic execution steps).
  3. The time spent in symex_bmct::symex_step together with all of the functions that it calls.

For develop and ReachSafety-ECA those numbers were:

  1. 4305 calls
  2. 48005643 calls
  3. 16208.08 seconds

I do agree and emphasise that this is by no means a complete picture. For me this mainly is about having reproducible data that I can key an eye on over time.

@tautschnig
Copy link
Collaborator Author

I'll try to do some benchmarking with HASH_CODE enabled at a later time to see whether that helps.

That's now done, scatter plot comparing current develop (y axis) vs BDDs+HASH_CODE (x axis) is shown below. The scores are now almost the same (155 with BDDs, 157 without BDDs).

Screen Shot 2019-03-25 at 02 13 51

Copy link
Contributor

@allredj allredj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️
This PR failed Diffblue compatibility checks (cbmc commit: f4a99f0).
Build URL: https://travis-ci.com/diffblue/test-gen/builds/105613658
Status will be re-evaluated on next push.
Common spurious failures:

  • the cbmc commit has disappeared in the mean time (e.g. in a force-push)

  • the author is not in the list of contributors (e.g. first-time contributors).

  • the compatibility was already broken by an earlier merge.

@martin-cs
Copy link
Collaborator

There is something significant in that graph to do with the number of time-outs. There is a vertical "smear" near the X axis time-out which suggests that there is a serious scalability difference there so I am surprised the numbers are so similar.

Copy link
Collaborator

@martin-cs martin-cs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the patch implements what is described in the PR. Whether we want it is a different question. I can see that this is performance critical and I don't think the current behaviour is optimal, even for this design. The "common prefix" comments give me pause for concern because that doesn't describe what I think is going on. Could we simply add (and document) some invariants on the guard expressions always being in (some) sorted order? It feels like that should be do-able, would give the performance benefits of this patch with some extra certainty of what is going on.

( I agree that BDDs are actually the way of solving this in full generality but I do wonder whether what we have is sufficiently restricted that we can solve it with a less heavy-weight solution. Regardless, I think pinning down what is going on here in terms of order with help the BDD case as well. )

@tautschnig
Copy link
Collaborator Author

With latest develop (and this PR rebased on top of it) profiling suggest that we save approximately 30% in guard_exprt::operator-= (reducing time spent in there from 1000s to 700s). For the overall result, however, there is no notable difference on ReachSafety-ECA.

@martin-cs
Copy link
Collaborator

martin-cs commented Mar 25, 2019 via email

@tautschnig tautschnig changed the title [SV-COMP'18 9/19] Do not unnecessarily sort guard conjuncts [SV-COMP'18 9/19] Do not unnecessarily sort guard conjuncts [blocks: #3486] Mar 30, 2019
@tautschnig tautschnig changed the title [SV-COMP'18 9/19] Do not unnecessarily sort guard conjuncts [blocks: #3486] Do not unnecessarily sort guard conjuncts [blocks: #3486] Mar 30, 2019
@tautschnig
Copy link
Collaborator Author

Do we generate the same results? I can believe that we can save time
in guard_exprt but generate more complex guards and thus loose time
over-all.

As far as I can tell we generate exactly the same results.

Guards are (no longer) generic conjunctions. They are constructed in a
consistent order. If a more generic set-up is required, use BDD-backed
guards.
@codecov
Copy link

codecov bot commented May 16, 2021

Codecov Report

Merging #1998 (eabd67c) into develop (f564088) will decrease coverage by 0.08%.
The diff coverage is 82.38%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1998      +/-   ##
===========================================
- Coverage    75.53%   75.45%   -0.09%     
===========================================
  Files         1447     1447              
  Lines       158116   158087      -29     
===========================================
- Hits        119431   119277     -154     
- Misses       38685    38810     +125     
Impacted Files Coverage Δ
src/analyses/guard_expr.cpp 97.82% <ø> (-0.14%) ⬇️
src/goto-instrument/accelerate/accelerate.cpp 34.89% <0.00%> (ø)
src/goto-instrument/concurrency.cpp 0.00% <0.00%> (ø)
src/goto-instrument/nondet_volatile.cpp 86.55% <ø> (-0.15%) ⬇️
src/goto-instrument/replace_calls.cpp 89.55% <0.00%> (+1.31%) ⬆️
src/goto-programs/goto_convert_class.h 87.30% <ø> (ø)
src/goto-programs/goto_convert.cpp 91.69% <33.33%> (-0.24%) ⬇️
src/util/simplify_expr_struct.cpp 74.38% <50.00%> (+0.78%) ⬆️
src/goto-instrument/goto_program2code.cpp 69.20% <60.00%> (+0.06%) ⬆️
src/util/simplify_expr.cpp 78.14% <65.00%> (-6.93%) ⬇️
... and 75 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0ea7f13...eabd67c. Read the comment docs.

@tautschnig
Copy link
Collaborator Author

The following two flame graphs demonstrate what made me look into this (observe how goto_symext::merge_gotos seems to be (transitively) spending a lot of time in sort_and_join) and that merge_gotos now no longer is the most expensive part of execute_next_instruction.

Screenshot 2021-05-18 at 17 28 54

Screenshot 2021-05-18 at 17 29 08

That being said, the overall impact is fairly small (total CPU time saved is a few seconds), and this benchmark category of SV-COMP (Reachsafety-Recursive) is the only one where this was notable at all.

I do maintain that this sorting is unnecessary given the way guards are constructed by goto-symex (AFAIK the only user), but I don't think the performance data alone provide a great deal of support for this cleanup.

Copy link
Collaborator

@martin-cs martin-cs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the code is redundant and removing it 1. causes no harm, 2. improves things a bit then... why not?

@tautschnig tautschnig merged commit 60abd1e into diffblue:develop May 19, 2021
@tautschnig tautschnig deleted the no-sort-guards branch May 19, 2021 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants