-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Synchronized map poor performance #1211
Comments
This is a valid request. I see two options: a) drop disposeAll |
There is a backport project jsr166e of the |
Another possible idea: maintain multiple maps. The number of maps can be determined from |
does not help. The WeakKey part is important here. In my naive test performance regressed around 50%. Hot methods could use |
The class name is the same but JDK8 made significant map performance improvements using trees instead of linked lists. But as you say, we'd have to modify the code to handle weak references. The triple-combination of Weak references, concurrency, and Java 6 don't leave many options. |
My measurement happend on JDK 11, so the worst case is not even hit. |
@matthiasblaesing what was your replacement exactly? Why do you need a weak key when you already have a weak reference as value? You could have a WeakReference (or without disposeAll maybe PhantomReference) subclass, directly held by Memory, and keyed in a concurrent map by Pointer or Long. You then use the reference queue to remove the reference from the map, and ideally (longer term) replace finalization the same way. And you still have explicit memory management as well (which really is the better way to handle). It's the change we made in gst1-java-core in https://github.com/gstreamer-java/gst1-java-core/blob/master/src/org/freedesktop/gstreamer/glib/NativeObject.java although the requirements may be somewhat more complex there due to native ref counting - pointer is in a handle class that is held by both weak reference and main object. EDIT : incidentally, it's loosely based on Cleaner approach, obviously, but Java 8 and I'm not sure Cleaner would do everything required in Memory anyway - https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ref/Cleaner.html |
What if you simply drop the |
Le,
Originally there were some application requirements to ensure all memory
was explicitly disposed before exit (since the per-memory finalizer wasn't
guaranteed to run).
T.
…On Fri, Jun 19, 2020 at 10:08 AM Le Karasique ***@***.***> wrote:
What if you simply remove the disposeAll (and therefore - the map)?
Is it used anywhere except the JNA finalizer? Looks like the Memory
objects finalizer can do the job by itself.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#1211 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFYZLIWX24KJMQPS4BKKE3RXNWNFANCNFSM4N7PK7IQ>
.
|
So I measured and here are the results: This adds four new memory implementations: matthiasblaesing@40a3cf7 The benchmark is here: https://github.com/matthiasblaesing/jnamemorybenchmark The repository contains the JNA binary, if you want to modify, replace
and is run with:
The implementations:
The results:
Numbers are also in the Benchmark Repository: |
Thanks for sharing code and benchmark. Interesting results. Wonder whether it shows quite a different scenario to the issue reporter's, or whether the map lock really isn't the bottleneck it appears. I wonder what Memory2 using Does the benchmark reflect real-world memory pressures enough? Just wondering if GC is clearing the values often enough to show a real-world idea of contention? Will try and find some time to play with this over next few weeks - I'm more interested if using references as values in the map as to the benefits (or not) in removing finalization completely. |
Have a look at
The allocated memory is 1 Byte so GC pressure will start before native memory becomes an issue. The objects become eligible for GC directly after they are allocated and consumed by the blackhole. The idea is, that we are interested in the memory allocation hotpath. I admit, that the dispose method might be another bottleneck.
From my POV removing finalization can't be done without changing the API. This was my first try (after the measurements I would implement it different today): https://github.com/matthiasblaesing/jna/commits/remove_finalizer It changes the meaning of the |
Well, that has no map at all. Not suggesting that usage tracking isn't going to add overhead. I meant a proper comparison of usage tracking and map locking with the same data
That's what I meant - isn't there very little GC pressure full stop, so the ratio of calls to dispose (and potentially size of the maps) may be quite different to typical usage?
How does it change the meaning of dispose()? I can see it slightly changes API if people have overridden finalize() rather than dispose(), although if they've done that they should probably expect it to break! 😄 When doing this myself I implemented AutoCloseable by delegating to dispose() - that seems a more useful terminology for deallocating memory to me. Can they not implement custom deallocating by overriding dispose? Anyway, that's a possibly wider concern than this issue and better discussed on the mailing list? Interested in all this because it relates to work I'm doing at the moment. |
I think there is no need to maintain a map, a list could be used here. The allocatedMemory.remove(this) call is only needed if disposeAll is called and could be moved to the loop in disposeAll. If the dispose method is call from the finializer the Memory instance is not reachable through the allocatedMemory map, the only thing happening here is the dead entries of the map are removed. A call to allocatedMemory.size() will remove these entries with less overhead. A proof of concept implemetation using a linkedlist can be found here, i need to double check everything is still working before running the benchmarks: https://github.com/joerg1985/jna/blob/memory-map/src/com/sun/jna/Memory6.java
Explicit calling dispose method will not stop the finializer from calling dispose again, this will again call allocatedMemory.remove(this) and will increase the number of calls to the synchronized map. |
Which is why one of the available options is removing |
It does if you get rid of finalize(), which was the context of that comment!
Again, until you want to get rid of finalize(). Which I would prioritise over arguing over list vs map. |
My intention was not to disrupt the "finalizer or not in future versions" discussion and i messed up the context of the comments, sorry for this. The only thing i would like to contribute is the idea of a faster implementation of the current behaviour.
I hope i did not break anything. |
My numbers are not as good as yours, but still Memory6 looks like a good improvement, that might be worth merging. Would you be willing to create a PR with the suggested patch? I looked at a diff between Memory and Memory6 and notice many unrelated changes, that made it difficult to see the real changes and a cleaner patch would make review easier. |
I will create a new branch without these unrelated changes, try to find better names for the internal class/methods and create some unittests. The different numbers might be related to the Java version, my default runtime is 11. I did not consider this running the benchmark, before opening the PR i will benchmark Java 8, 11 and 14. |
Closing, as improved implementation was merged with 59b052a |
We have issues with the performance because of this: https://github.com/java-native-access/jna/blob/master/src/com/sun/jna/Memory.java#L57-L58
Lock waiting can get up to 99% of the time under a heavy load. SynchronizedMap works very slow in a multi-threaded environment. I suggest replacing it with any parallel map implementation.
The text was updated successfully, but these errors were encountered: