-
Notifications
You must be signed in to change notification settings - Fork 306
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve ArchCondition evaluation #876
At the moment evaluating `ArchConditions` is quite memory inefficient. E.g. for a rule like `noClasses().should().accessField(Foo.class, "bar")` we create a `ConditionEvent` for every single field access and qualify it with "allowed" or "violation". Thus, this is independent of some access actually being a violation but done for every access. Furthermore, all such events of all evaluated objects will be kept in memory until the test failure is reported. This will keep in the former case events for all field accesses of all classes in memory until the final failure (or success) is reported. The background for this behavior is the complex evaluation logic of nested and inverted conditions. E.g. `noClasses()` will invert the events and thus make the events that were "allowed" before to be violations. To make matters worse we have combinations like `accessField(..).orShould()...` and aggregations like `noClasses().should().haveAny...` where `haveAny...` will be satisfied if there is any occurrence that supports the condition, but by negating it via `noClasses()` we effectively change the evaluation to "all occurrences must NOT satisfy the condition". So it is quite challenging to determine at which point the information about "allowed" events is finally obsolete, since we can arbitrarily aggregate and invert. I tried to come up with a lazy solution, where we would use some `Supplier/Consumer` approach, but failed to come up with one that a) makes up a good public API for users, b) works for all those cases of aggregation including customized behavior by users and c) really saves substantial amounts of memory. So in the end I gave up on that and focused on optimizing the current approach. This PR will address two points: a) improve the memory footprint of `ArchCondition` evaluation and b) improve the public API to make future optimizations easier and the code more maintainable (now is a very good opportunity since we are about to release a new major version, so we can break the public API a little bit). ## Performance Aspect As for point a) the main optimization here is to throw out intermediate copying of events, and once we know we are done with event evaluation (i.e. we are now top-level about to create the result) to dump all references to now obsolete satisfied events. This had the following positive effect when testing `noClasses().should().accessField(System.class, "out")` on a big code base on my machine: ### Before #### Heap allocation  #### Performance Evaluating the rule could be done 89 times in 60 seconds. ### After #### Heap allocation  #### Performance Evaluating the rule could be done 135 times in 60 seconds. ### Result So altogether we see a smoother memory allocation with less GCs and a performance improvement of about 50% when evaluating such conditions. Thus, I consider the change worthwile. ## API Aspect Before the public API included a final class `ConditionEvents`, this is now an interface to allow different implementations to be used internally. Also the API of this interface has been stripped of all non-essential methods, like `getAllowed()` which would return the allowed events and force us to always keep a reference to satisfied events as long as we hold `ConditionEvents`. I tried to strip the API down to really just the essentials, e.g. `containViolation()` or `getViolating()` and remove all methods that don't make sense in all contexts.
- Loading branch information
Showing
34 changed files
with
803 additions
and
713 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.