RFR 8017540: Improve multi-threaded contention behavior of BigInteger.toString() radix conversion cache
Aleksey Shipilev
aleksey.shipilev at oracle.com
Tue Jun 25 19:12:37 UTC 2013
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