-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Fixes #2720: Use StackWalker on Java 9+ to create Locations #2723
Conversation
In terms of memory allocations, this reduces the overall memory allocations of creating a location by an order of magnitude in Java 9, and as compared to Java 8. The implementation is somewhat involved due to these desires: - Minimize the amount of work done if the Location is never used. This is done by not converting the StackFrame into a StackTraceElement, instead wrapping in an intermediate state. The StackTraceElement conversion will only occur (internally) if the .getFileName() method is called. - Ensure the implementation is still Serializable. This is ensured with a .writeReplace method. - Minimize the number of allocations, which is basically an exercise in lambda caching. - Ensuring the old mechanism still works on Java 8. Presently on Java 9+, on a stack depth of 1000 the old mechanism will allocate 40kB of RAM per call. The new one will allocate 1.5kB of RAM per call, which is a huge improvement. This is still sadly not the 'close-to-no-overhead' solution I was looking for. I therefore also added a system property that can be used to fully disable Location creation. I'm aware that this is likely not the right approach given Mockito has plugins and settings - mostly looking for guidance here given I'm not sure what would be idiomatic here.
9c7d62c
to
a5c573f
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.
The CI build is failing because of auto-formatting issues. You can run ./gradlew spotlessApply
to fix these automatically.
Overall a nice improvement! Mostly some minor comments, but overall looking solid already. Good work 😄
src/main/java/org/mockito/internal/debugging/Java9PlusLocationImpl.java
Outdated
Show resolved
Hide resolved
src/main/java/org/mockito/internal/debugging/Java9PlusLocationImpl.java
Outdated
Show resolved
Hide resolved
src/main/java/org/mockito/internal/debugging/LocationFactory.java
Outdated
Show resolved
Hide resolved
src/test/java/org/mockitousage/internal/debugging/LocationFactoryTest.java
Show resolved
Hide resolved
src/main/java/org/mockito/internal/debugging/Java9PlusLocationImpl.java
Outdated
Show resolved
Hide resolved
src/main/java/org/mockito/internal/debugging/Java9PlusLocationImpl.java
Outdated
Show resolved
Hide resolved
src/main/java/org/mockito/internal/debugging/Java9PlusLocationImpl.java
Outdated
Show resolved
Hide resolved
src/main/java/org/mockito/internal/debugging/LocationFactory.java
Outdated
Show resolved
Hide resolved
Fixes mockito#2723. Class.getMethods is an inefficient method call which is being called on each mock invocation. It ends up constructing new Method objects for each method on the class, and this can dominate the overall performance of Mockito mocks. This commit caches the result of the computation. Once concern is that this change uses some static state. I considered: - Instance state - based on where this type is constructed it seemed like it'd be a big imposition on code readability elsewhere. - Weakly referenced map. Mockito has a type for this, but the constructor of that type produces a Thread with which to clean up. Both of these seemed like overkill compared to the overhead expected in the real world (which should be on the order of a few kilobytes of RAM at most).
Fixes mockito#2723. Class.getMethods is an inefficient method call which is being called on each mock invocation. It ends up constructing new Method objects for each method on the class, and this can dominate the overall performance of Mockito mocks. This commit caches the result of the computation. Once concern is that this change uses some static state. I considered: - Instance state - based on where this type is constructed it seemed like it'd be a big imposition on code readability elsewhere. - Weakly referenced map. Mockito has a type for this, but the constructor of that type produces a Thread with which to clean up. Both of these seemed like overkill compared to the overhead expected in the real world (which should be on the order of a few kilobytes of RAM at most).
`Class.getMethods` is an inefficient method call which is being called on each mock invocation. It ends up constructing new `Method` objects for each method on the class, and this can dominate the overall performance of Mockito mocks. This commit caches the result of the computation. Once concern is that this change uses some static state. I considered: - Instance state - based on where this type is constructed it seemed like it'd be a big imposition on code readability elsewhere. - Weakly referenced map. Mockito has a type for this, but the constructor of that type produces a Thread with which to clean up. Both of these seemed like overkill compared to the overhead expected in the real world (which should be on the order of a few kilobytes of RAM at most). Fixes #2723
Latest commit is green but the status checks are red. Not sure why, but I assume a click of retry will do the trick. |
src/test/java/org/mockitointegration/ClassLoadabilityChecker.java
Outdated
Show resolved
Hide resolved
Codecov Report
@@ Coverage Diff @@
## main #2723 +/- ##
============================================
+ Coverage 86.05% 86.06% +0.01%
- Complexity 2790 2815 +25
============================================
Files 317 320 +3
Lines 8395 8527 +132
Branches 1048 1052 +4
============================================
+ Hits 7224 7339 +115
- Misses 896 912 +16
- Partials 275 276 +1
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
Sigh, you were right. My bad. Anyway, it looks like it'll go green now. |
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.
Last nits and then this is ready-to-go 🎉
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.
Thanks for the hard work and investigation into performance! Much appreciated 😄
Is there any specific reason to remove the system variable to disable location tracking altogether? My teams is still using Java 8 and this is a major problem for us as well. Locally testing without location reduces the test time from 10+ min to 5 min. (Yes we have a huge code base with large entity graphs :) ) |
I prefer to not ship a flag just for Java 8, which is EOL. Instead, upgrading to Java 9 and above should yield similar benefits. Shipping flags is expensive in the longterm and in this case, I don't think it is worth carrying that cost. |
It’s probably worth saying:
Before my changes, a simple mock call takes 3us.
After, it takes 2us.
If you remove all call site recording, it takes 200ns.
In my case I was considering making it so that it stopped recording call
sites on hotter paths (E.g. after the thousandth call to a mock or
whatever) but it wasn’t a big enough blocker for me.
…On Tue, 11 Oct 2022 at 08:53, Tim van der Lippe ***@***.***> wrote:
I prefer to not ship a flag just for Java 8, which is EOL. Instead,
upgrading to Java 9 and above should yield similar benefits. Shipping flags
is expensive in the longterm and in this case, I don't think it is worth
carrying that cost.
—
Reply to this email directly, view it on GitHub
<#2723 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAKMJMN3HJWG3NJPATVORTWCUMIBANCNFSM55V5N2YQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
@TimvdLippe That is true. Unfortunately we are still stuck with Java 8. |
@j-baker The solution you refer the flag a counter that stops recording after certain number of invocations? |
I was considering proposing making the location tracking code always stop
recording after enough invocations, the logic being that if you’ve done
thousands of them, knowing the precise location where a single call happens
from is probably less interesting.
There’s possibly also something where if mock invocation recording is
disabled, it might be reasonable to not record stub invocation locations.
…On Sun, 16 Oct 2022 at 07:03, Thinesh Ganeshalingam < ***@***.***> wrote:
@j-baker <https://github.com/j-baker> The solution you refer the flag a
counter that stops recording after certain invocations?
—
Reply to this email directly, view it on GitHub
<#2723 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAKMJP4WVCZRPOMCOVB6FTWDOLBNANCNFSM55V5N2YQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
Fixes #2720.
In terms of memory allocations, this reduces the overall memory
allocations of creating a Location by an order of magnitude in
Java 9, and as compared to Java 8.
The implementation is somewhat involved due to these desires:
is done by not converting the StackFrame into a StackTraceElement,
instead wrapping in an intermediate type. The StackTraceElement conversion
will only occur (internally) if the .getFileName() method is called, which it usually isn't.
a .writeReplace method.
lambda caching.
not like them since they don't exist in Android.
Presently on Java 9+, on a stack depth of 1000 the old mechanism will allocate
40kB of RAM per call. The new one will allocate 1.5kB of RAM per call,
which is a huge improvement. Even in a very basic JUnit test with no stack depth,
the difference is about 1.5kB of allocation vs 8kB.
This is still sadly not the 'close-to-no-overhead' solution I was
looking for. I therefore also added a system property that can be used
to fully disable Location creation. I'm aware that this is likely not
the right approach given Mockito has plugins and settings - mostly
looking for guidance here given I'm not sure what would be idiomatic
here.
Checklist
including project members to get a better picture of the change
commit is meaningful and help the people that will explore a change in 2 years
Fixes #<issue number>
in the description if relevantFixes #<issue number>
if relevant