Skip to content
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

Potential performance optimisation around hierarchy-traversing matchers #74

Closed
lwasyl opened this issue Apr 12, 2023 · 3 comments
Closed
Assignees

Comments

@lwasyl
Copy link

lwasyl commented Apr 12, 2023

Tl;dr: It's possible that Kakao could be more performant by reordering matchers to apply faster matchers first.


Since Kakao wraps Espresso, Espresso's view finding mechanism is used for actions and assertions. This mechanism will always iterate through entire view hierarchy looking for all views matching the requested predicate (source).

Most matchers should be pretty fast, however some of them will also iterate some part of view hierarchy as part of their check. For example HasDescendantMatcher or IsDescendantOfAMatcher will both go up or down the view hierarchy looking for views that match the requested descendant/ancestor. This means complexity of those matchers is not constant, contrary to simple matchers like WithIdMatcher. Moreover, when these matchers are nested, the overall performance decreases dramatically — for nested isDescendantOfA matchers, Espresso will go through every view in hierarchy, first matcher will go up in hierarchy and execute the nested matcher, which will also go up the hierarchy — I believe the complexity is non-linear in that case.

This is all unavoidable to an extent, but if those slow matchers are used within composite matchers like allOf() or anyOf(), the developer can easily improve Espresso performance by simply putting the expensive, view-traversing matchers later on the list. That way Espresso will short-circuit as soon as it can, potentially not invoking the expensive matcher at all.

If I read the code correctly, Kakao will sometimes put the slow matcher first on the list that is effectively transformed into an allOf() with same ordering, for example

val vb = ViewBuilder().apply {
isDescendantOfA { withMatcher(parentMatcher as Matcher<View>) }
builder(this)
}
or
edit = KEditText {
isDescendantOfA(function)
isAssignableFrom(EditText::class.java)
}
. Simply switching those lines would improve the performance essentially for free, with no obvious downsides. The faster matchers will run first and short-circuit the check if they fail, and the slower matcher will run a couple of times instead of for every single view.

This might look like a small change, but especially with nested matchers the performance impact can be significant —  in one project with ~270 UI tests, simply putting isDescendantOfA and hasDescendant matchers last in allOf calls cut total test run time in half (the project doesn't use Kakao).

@lwasyl lwasyl changed the title Potential performance problem with hierarchy-traversing matchers Potential performance optimisation around hierarchy-traversing matchers Apr 12, 2023
@Vacxe Vacxe self-assigned this Jul 3, 2023
@Vacxe
Copy link
Member

Vacxe commented Jul 4, 2023

Will be available in next release. Thanks.

@Vacxe Vacxe closed this as completed Jul 4, 2023
@lwasyl
Copy link
Author

lwasyl commented Jul 4, 2023

Thanks! If you have any measurements/numbers before and after the changes, I'd be curious to hear if you saw any improvements 🙂

@Vacxe
Copy link
Member

Vacxe commented Jul 6, 2023

@lwasyl we have 1k+ UI tests in prod. Ill tell ya about performance boost changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants