Re: Lazy, cached supplier: most performant mutex mechanism?
Remi Forax via Concurrency-interest <[email protected]> Sat, 1 Aug 2020 14:24:17 +0200 (CEST)
| Newsgroups | gmane.comp.java.jsr.166-concurrency |
|---|---|
| Message-ID | <[email protected]> |
> De: "concurrency-interest" <[email protected]> > Cc: "[email protected]" > <[email protected]> > Envoyé: Vendredi 31 Juillet 2020 20:56:10 > Objet: Re: [concurrency-interest] Lazy, cached supplier: most performant mutex > mechanism? > Thanks everyone, I'll check these things out. > Seems like the user-space dual of a condy instruction, except run max once. I > was hoping to try an approach that wasn't based on synchronized, though > synchronized saves you the allocation of a lock. There is already a proposal for a JLS dual of condy [ https://bugs.openjdk.java.net/browse/JDK-8209964 | https://bugs.openjdk.java.net/browse/JDK-8209964 ] but as you said, condy semantics allows the bootstrap method to be run more than once. > Ron Pressler had another interesting suggestion to try: > Read val; if it's null, CAS fn to null. > If you win, allocate a lock and do an ordered set (with a VarHandle) to the lock > field, lock it, invoke fn, do an ordered write (with a VarHandle) to val, and > unlock, then do another ordered write to null out the lock. > If you lose the CAS, spin with an ordered read on the lock field and the val. > This will be a short spin, because all you're waiting for is the allocation of > the lock. If you see a non-null val, you're done. If you see a non-null lock, > lock on it, and then a normal read from val should be non-null. > This requires a bit of care in terms of memory ordering, and will probably > require testing on non-Intel platforms, as those have weaker memory ordering > than Intel, and many concurrency bugs don't manifest on x86. But the code will > still be small, and it's an important method, so a relatively elaborate > mechanism there might be worth it. > </suggestion> Here is a version that has the nice property to not have a volatile read in the fast-path (and the not so nice property of being very slow if the code is not JITed by c2) This is how yo use it, here ONCE.get() should always return 0 private static int COUNTER = 0 ; private static final Once<Integer> ONCE = Once. of (() -> COUNTER ++) ; and the trick is to use a MutableCallSite import java.lang.invoke.MethodHandle ; import java.lang.invoke.MethodHandles ; import java.lang.invoke.MethodType ; import java.lang.invoke.MutableCallSite ; import java.lang.reflect.UndeclaredThrowableException ; import java.util.Objects ; import java.util.concurrent.locks.ReentrantLock ; import java.util.function.Supplier ; public interface Once< T > extends Supplier< T > { @SuppressWarnings ( "unchecked" ) static < T > Once< T > of (Supplier<? extends T > supplier) { var mh = new OnceCallSite(supplier).dynamicInvoker() ; return () -> { try { return ( T ) mh .invokeExact() ; } catch (RuntimeException | Error e) { throw e ; } catch (Throwable t) { throw new UndeclaredThrowableException(t) ; } } ; } class OnceCallSite extends MutableCallSite { private static final MethodHandle SLOW_PATH ; static { var lookup = MethodHandles. lookup () ; try { SLOW_PATH = lookup.findVirtual(OnceCallSite. class, "slowPath" , MethodType. methodType (Object. class )) ; } catch (NoSuchMethodException | IllegalAccessException e) { throw new AssertionError(e) ; } } private Object value ; private final Supplier<?> supplier ; private final ReentrantLock lock = new ReentrantLock() ; private OnceCallSite (Supplier<?> supplier) { super (MethodType. methodType (Object. class )) ; this . supplier = supplier ; setTarget( SLOW_PATH .bindTo( this )) ; } private Object slowPath () { lock .lock() ; try { Object value = this . value ; if (value != null ) { return value ; } value = Objects. requireNonNull ( supplier .get()) ; this . value = value ; setTarget(MethodHandles. constant (Object. class, value)) ; return value ; } finally { lock .unlock() ; } } } } > On Fri, Jul 31, 2020 at 2:33 PM Viktor Klang < [ mailto:[email protected] | > [email protected] ] > wrote: >> We spent quite some time on this, you can read all about it here: >> [ https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html | >> https://docs.scala-lang.org/sips/improved-lazy-val-initialization.html ] >> On Fri, 31 Jul 2020 at 20:02, Benjamin Manes via Concurrency-interest < [ >> mailto:[email protected] | [email protected] >> ] > wrote: >>> I believe double-checked locking, such as Martin's version in Guava [ >>> https://github.com/google/guava/blob/master/guava/src/com/google/common/base/Suppliers.java#L124-L137 >>> | Suppliers#memoize ] , is the best approach on the JVM for instance-level >>> memoization. >>> On Fri, Jul 31, 2020 at 10:42 AM Ghadi Shayban via Concurrency-interest < [ >>> mailto:[email protected] | [email protected] >>> ] > wrote: >>>> This seems like it would be a common stdlib ask, but what is the most performant >>>> way to protect the code inside a supplier from being concurrently realized more >>>> than once? Contention would be rare, and the losing threads need to wait on the >>>> value being computed by the winning thread. >>>> The most straightforward thing to do is a synchronized block, but this currently >>>> pins a carrier thread in Project Loom. >>>> The Supplier needs to keep track of: >>>> 1) the thunk, if unrealized >>>> 2) a value, if realized >>>> Golang's sync.Once does this [1] (CAS, fallback to mutex. Doesn't remember >>>> value) >>>> Clojure's lazy sequences use synchronized [2] >>>> Is there a better way to approach this on the JVM? >>>> Thanks! >>>> [1] [ https://golang.org/src/sync/once.go | https://golang.org/src/sync/once.go >>>> ] >>>> [2] [ >>>> https://github.com/clojure/clojure/blob/30a36cbe0ef936e57ddba238b7fa6d58ee1cbdce/src/jvm/clojure/lang/LazySeq.java#L39-L46 >>>> | >>>> https://github.com/clojure/clojure/blob/30a36cbe0ef936e57ddba238b7fa6d58ee1cbdce/src/jvm/clojure/lang/LazySeq.java#L39-L46 >>>> ] >>>> _______________________________________________ >>>> Concurrency-interest mailing list >>>> [ mailto:[email protected] | [email protected] >>>> ] >>>> [ http://cs.oswego.edu/mailman/listinfo/concurrency-interest | >>>> http://cs.oswego.edu/mailman/listinfo/concurrency-interest ] >>> _______________________________________________ >>> Concurrency-interest mailing list >>> [ mailto:[email protected] | [email protected] >>> ] >>> [ http://cs.oswego.edu/mailman/listinfo/concurrency-interest | >>> http://cs.oswego.edu/mailman/listinfo/concurrency-interest ] >> -- >> Cheers, >> √ > _______________________________________________ > Concurrency-interest mailing list > [email protected] > http://cs.oswego.edu/mailman/listinfo/concurrency-interest _______________________________________________ Concurrency-interest mailing list [email protected] http://cs.oswego.edu/mailman/listinfo/concurrency-interest