On 06/25/2013 09:09 PM, Alan Eliasen wrote:
On 06/25/2013 12:13 PM, Peter Levart wrote:
             else { // resizing
                 // increase by factor of 1.5 (like ArrayList)
                 int newLength = oldLength + (oldLength >> 1);
    We probably don't ever want to extend any row of this cache any more
than we need to.  The entries in each row of the cache, say for base 10,
are 10^(2^n) which obviously grows super-exponentially with n.  (And the
time to perform each squaring to get successive terms will grow
quadratically or at least subquadratically on top of that, along with
memory usage which squares with each additional term.)  So we should
never resize to more entries in that table than we need, and we should
try to avoid recalculating entries in that table in multiple threads, as
the cost to calculate them can be high.

    As I noted years ago, the caching behavior is certainly going to be
one of the most controversial parts of BigInteger improvements.  :)


Hi Alan,

That's only the re-sizing of the array, so that it is not needed to be performed on each bigger exponent request. My code never calculates entries past the requested exponent, but can re-calculate the same entries if races occur. The part about resizing the array could be tuned (change the factor 1.5 -> 1 and you get the only-resize-to-maximum-requested-size behaviour). The re-calculation when concurrent requests are issued for same slot(s) could be avoided with CAS-es combined with locking. Perhaps the first few exponents, where the .square()s can be (re)computed relatively fast, could be cached optimistically to avoid volatile reads/CAS-es on fast paths. When the exponent index reaches certain threshold, the strategy could change and instead of normal racy reads/writes of BigInteger elements, we could CAS Supplier<BigInteger> elements. The Supplier would have a synchronized get() method that actually computes the BigInteger and returns it and installs the BigInteger into the same slot at the same time, so that further accesses only need volatile reads from those slots and instanceof BigInteger checks.

Something like that (the 2nd part with Supplier) is implemented in java.lang.reflect.Proxy to cache generated proxy classes and makes sure that each requested Proxy class is only generated and defined once. This case is trickier, because calculation of a particular exponent index needs the value from previous exponent index which might be in the process of calculation by some other thread, so we need to wait on it to finish, but this other thread might be waiting for the third thread doing calculation of yet previous exponent, etc... Luckily dependencies are ordered so no danger of dead-locks, I think...

Regards, Peter

Reply via email to