RFR 8017540: Improve multi-threaded contention behavior of BigInteger.toString() radix conversion cache

Dmitry Nadezhin dmitry.nadezhin at gmail.com
Tue Jun 25 19:36:54 UTC 2013


What about such code ?

BigInteger getRadixConversionCache(int radix, int exponent) {
  BigInteger retVal = null;
  BigInteger[][] pc = powerCache; // volatile read
  BigInteger[] cacheLine = pc[radix];
  int oldSize = cacheLine.length;
  if (exponent >= oldSize) {
      cacheLine = Arrays.copyOf(cacheLine, exponent + 1);
      for (int i = oldSize; i <= exponent; i++) {
          retVal = cacheLine[i - 1].square();
          cacheLine[i] = retVal;
      }
      synchronized (pc) {
        if (pc[radix].length < cacheLine.length) {
          pc[radix] = cacheLine;
          powerCache = pc; // volatile write, publish
        }
      }
  } else {
      retVal = cacheLine[exponent];
  }
  return retVal;
}

Is dummy volatile write necessary inside synchronized block ?
          powerCache = pc; // volatile write, publish


On Tue, Jun 25, 2013 at 11:12 PM, Aleksey Shipilev <
aleksey.shipilev at oracle.com> wrote:

> On 06/25/2013 08:29 PM, Brian Burkhalter wrote:
> > Hi Aleksey,
> >
> > On Jun 25, 2013, at 1:40 AM, Aleksey Shipilev wrote:
> >
> >> Thanks! Looks good to me.
> >
> > Cool!
>
> Hold on now. Similar to what Peter suggests in the separate thread,
> there is the data race between 3458 and 3466:
>
> 3455     private static BigInteger getRadixConversionCache(int radix,
> int exponent) {
>
> 3456         BigInteger retVal = null;
> 3457         BigInteger[][] pc = powerCache; // volatile read
> 3458         BigInteger[] cacheLine = pc[radix];
> 3459         int oldSize = cacheLine.length;
> 3460         if (exponent >= oldSize) {
> 3461             cacheLine = Arrays.copyOf(cacheLine, exponent + 1);
> 3462             for (int i = oldSize; i <= exponent; i++) {
> 3463                 retVal = cacheLine[i - 1].square();
> 3464                 cacheLine[i] = retVal;
> 3465             }
> 3466             pc[radix] = cacheLine;
> 3467             powerCache = pc; // publish by volatile write
> 3468         } else {
> 3469             retVal = cacheLine[exponent];
> 3470         }
>
> The observable behavior in one of the threads:
>  a) reading cacheLine in 3458
>  b) figuring the cacheLine.length is N
>  c) querying cacheLine[N-1]
>  d) since new cacheLine is published via data race, can see
>     (cacheLine[N-1] == null)
>  e) boom! NPE in caller.
>
> That's the only trouble I see. BigInteger is effectively final and
> publishable by data race. array.length is good even when published via
> data race. Note that this particular trouble have no chance to manifest
> itself on x86 (modulo some runtime optimizations), because the stores
> are ordered there.
>
> Unfortunately, solving this "cleanly" requires the final-like semantics
> for $cacheLine, which lends itself into creating the wrapper around
> BigInteger[]. It seems simpler to fallback and recover while the data
> race resolves, e.g.:
>
> private static volatile BigInteger[][] powerCache;
>
> BigInteger getRadixConversionCache(int radix, int exponent) {
>   BigInteger retVal = null;
>   BigInteger[][] pc = powerCache; // volatile read
>   BigInteger[] cacheLine = pc[radix];
>   int oldSize = cacheLine.length;
>   if (exponent >= oldSize) {
>       cacheLine = Arrays.copyOf(cacheLine, exponent + 1);
>       for (int i = oldSize; i <= exponent; i++) {
>           retVal = cacheLine[i - 1].square();
>           cacheLine[i] = retVal;
>       }
>       pc[radix] = cacheLine;
>       powerCache = pc; // volatile write, publish
>   } else {
>       retVal = cacheLine[exponent];
>       if (retVal == null) {
>           // data race, element is not available yet,
>           // compute on the fly
>           retVal = BigInteger.valueOf(radix);
>           for (int c = 0; c < exponent; c++) {
>               retVal = retVal.square();
>           }
>       }
>   }
>   return retVal;
> }
>
> (In retrospect, this seems the variation on Peter's idea, but fallback
> code is much simpler)
>
> It might be a good idea to turn $powerCache final, not volatile, since
> the code will recover anyway. The trouble I'm seeing is weak enough
> hardware, which will never see the updated value of cacheLine, always
> falling back. Hence, I'm keen to keep "volatile".
>
> I haven't tested this code, so...
>
> -Aleksey.
>



More information about the core-libs-dev mailing list