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

Peter Levart peter.levart at gmail.com
Thu Jun 27 07:27:52 UTC 2013


On 06/27/2013 08:37 AM, Peter Levart wrote:
> Hi,
>
> This version seems correct. Maybe just replace double volatile read at 
> length re-check with a single one:
>
>
>     private static BigInteger getRadixConversionCache(int radix, int 
> exponent) {
>         BigInteger[] cacheLine = powerCache[radix]; // volatile read
>         if (exponent < cacheLine.length)
>             return cacheLine[exponent];
>
>         int oldLength = cacheLine.length;
>         cacheLine = Arrays.copyOf(cacheLine, exponent + 1);
>         for (int i = oldLength; i <= exponent; i++)
>             cacheLine[i] = cacheLine[i - 1].pow(2);
>
>         BigInteger[][] pc = powerCache; // volatile read again
>         if (exponent >= pc[radix].length) {
>             pc = pc.clone();
>             pc[radix] = cacheLine;
>             powerCache = pc; // volatile write, publish
>         }
>         return cacheLine[exponent];
>     }
>
>
> I like this, since it tries to avoid overwriting larger cacheLines 
> with smaller ones when unexistent exponents are requested concurrently 
> for same radix. This is good, since computation for larger exponents 
> takes quadratically longer time (as Alan Eliasen points out) and 
> possibility of concurrent threads stomping on each other increases. 
> Re-reading and cloning powerCache reference at end of computation also 
> takes care of cacheLines for other radixes that might have expanded 
> while the computation was taking place. So the only overhead remaining 
> is when concurrent threads are uselessly computing same results at 
> same time. To avoid this, locking and waiting would have to be 
> introduced which would *complicate things*.
>
> Regards, Peter

On the other hand, it doesn't complicate thing too much. So if this 
extra CPU time proves to be a problem, here's a variation of above code 
which prevents multiple threads from calculating the same result at the 
same time, by serializing computation with precise granularity:

     private static class Node {
         final BigInteger value;
         Node next;
         Node(BigInteger value) { this.value = value; }
     }

     private static volatile Node[][] powerCache;

     static {
         powerCache = new Node[Character.MAX_RADIX + 1][];
         for (int i = Character.MIN_RADIX; i <= Character.MAX_RADIX; i++) {
             powerCache[i] = new Node[]{new Node(BigInteger.valueOf(i))};
         }
     }

     private static BigInteger getRadixConversionCache(int radix, int 
exponent) {
         Node[] cacheLine = powerCache[radix]; // volatile read
         if (exponent < cacheLine.length)
             return cacheLine[exponent].value;

         int oldLength = cacheLine.length;
         cacheLine = Arrays.copyOf(cacheLine, exponent + 1);
         Node prevNode = cacheLine[oldLength - 1];
         for (int i = oldLength; i <= exponent; i++) {
             Node node;
             synchronized (prevNode) {
                 node = prevNode.next;
                 if (node == null) {
                     node = new Node(prevNode.value.pow(2));
                     prevNode.next = node;
                 }
             }
             cacheLine[i] = prevNode = node;
         }

         Node[][] pc = powerCache; // volatile read again
         if (exponent >= pc[radix].length) {
             pc = pc.clone();
             pc[radix] = cacheLine;
             powerCache = pc; // volatile write, publish
         }
         return cacheLine[exponent].value;
     }


This variation differs only in a subtelty. It wraps each BigInteger with 
a Node, which has a link to next node in chain. Computation of next node 
is serailized using previous node as a lock. So although Nodes can end 
up in several arrays (which are used as index for quick access), there 
is only a single growing chain of Nodes per radix and each Node is 
computed by single thread.

Regards, Peter

>
> On 06/26/2013 08:13 PM, Brian Burkhalter wrote:
>> So do we have consensus on this version?
>>
>> Thanks for the lively "conversation."
>>
>> Brian
>>
>> On Jun 26, 2013, at 12:05 AM, Aleksey Shipilev wrote:
>>
>>> Yes, like that.
>>>
>>> -Aleksey
>>>
>>> On 26.06.2013, at 10:53, Dmitry Nadezhin <dmitry.nadezhin at gmail.com> 
>>> wrote:
>>>
>>>>> We could check for the existing cacheLine.length right before 
>>>>> installing
>>>> the new one
>>>>
>>>> Do you mean something like this ?
>>>>
>>>> BigInteger getRadixConversionCache(int radix, int exponent) {
>>>> BigInteger[] cacheLine = powerCache[radix]; // volatile read
>>>> if (exponent < cacheLine.length)
>>>>     return cacheLine[exponent];
>>>>
>>>> int oldLength = cacheLine.length;
>>>> cacheLine = Arrays.copyOf(cacheLine, exponent + 1);
>>>> for (int i = oldLength; i < exponent + 1; i++)
>>>>     cacheLine[i] = cacheLine[i - 1].square();
>>>>
>>>> if (exponent >= powerCache[radix].length) { // volatile read again
>>>>    BigInteger[][] pc = Arrays.copyOf(powerCache);
>>>>    pc[radix] = cacheLine;
>>>>    powerCache = pc; // volatile write, publish
>>>> }
>>>> return cacheLine[exponent];
>>>> }
>




More information about the core-libs-dev mailing list