Skip to content

Commit

Permalink
Introduces a faster long emulation.
Browse files Browse the repository at this point in the history
Doubles are capable of holding integral numbers up to 53
bits without losing any precision. The new implementation
exploits this fact and uses double to represent long numbers
up to 45 bits and then switches to existing number triplet
representation for any number beyond that.

45 bits is chosen to simplify the conversion between two
representations as the number triplet representation uses two 22bit
slots for lower bits. Later the approach can be extended to
represent all numbers that fits into 53 bits.

The added overhead for big longs is very small. However it
uses much less memory for small longs and provides extremely
fast arithmetic operations.
Benchmarks results shows division is now 5170 times faster
in Firefox compared to earlier implementation[1].

The new implementation is also JsInterop friendly as small
numbers are real non-opaque numbers usable in javascript side
without any conversion. For example, even though time is
represented by long in java and double in javascript, both
uses less than 45 bits hence now interoperable.

I also experimented with doing optimistic arithmetic, meaning that,
without any type check I can assume SmallLong and do the arithmethic
operation. If it is not a SmallLong the result will never be in the
safe-range so we will fallback to BigLong arithmethic.
That appraoch provided around another 20% performance improvement
for small longs. However surprisingly the overhead for large numbers
significantly increased (slows down by half). It doesn't seem like
a good trade off so this patches uses explicit type check.


[1] Benchmark results:

            Chrome    Firefox    IE11
Add(Small): +84.96%   +227.51%   +7.59%
Add(Big):   -23.56%   -35.74%    -59.48%
Div(Small): +694.25%  +5170.80%  +717.81%
Div(Big):   -8.60%    -8.31%     -14.12%
Mul(Small): +242.78%  +785.87%   +33.80%
Mul(Big):   -9.12%    -8.55%     -38.86%
Shr(Small): -34.72%   -12.21%    -77.41%
Shr(Big):   -12.23%   -16.02%    -35.75%

Change-Id: I6fcf5650c9357a7255fc126d65f463138fa47e68
  • Loading branch information
gkdn authored and Gerrit Code Review committed Sep 4, 2015
1 parent dec4e9b commit e31f9a4
Show file tree
Hide file tree
Showing 9 changed files with 805 additions and 496 deletions.
7 changes: 5 additions & 2 deletions dev/core/src/com/google/gwt/dev/jjs/impl/JjsUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,10 @@ JsLiteral translate(JExpression literal) {
@Override
JsLiteral translate(JExpression literal) {
SourceInfo sourceInfo = literal.getSourceInfo();
int[] values = LongLib.getAsIntArray(((JLongLiteral) literal).getValue());
long[] values = LongLib.getAsLongArray(((JLongLiteral) literal).getValue());
if (values.length == 1) {
return new JsNumberLiteral(literal.getSourceInfo(), ((JLongLiteral) literal).getValue());
}
JsObjectLiteral objectLiteral = new JsObjectLiteral(sourceInfo);
objectLiteral.setInternable();

Expand Down Expand Up @@ -449,7 +452,7 @@ JsLiteral translate(JExpression literal) {
}

private static void addPropertyToObject(SourceInfo sourceInfo, JsName propertyName,
int propertyValue, JsObjectLiteral objectLiteral) {
long propertyValue, JsObjectLiteral objectLiteral) {
JsExpression label = propertyName.makeRef(sourceInfo);
JsExpression value = new JsNumberLiteral(sourceInfo, propertyValue);
objectLiteral.addProperty(sourceInfo, label, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,6 @@ public static native Object coerceToLong(Object o) /*-{
* Convert a double to a long.
*/
public static native Object coerceFromLong(Object o) /*-{
return @com.google.gwt.lang.LongLib::toDouble(Lcom/google/gwt/lang/LongLibBase$LongEmul;)(o);
return @com.google.gwt.lang.LongLib::toDouble(*)(o);
}-*/;
}
366 changes: 366 additions & 0 deletions dev/core/super/com/google/gwt/lang/BigLongLib.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,366 @@
/*
* Copyright 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.lang;

/**
* Implements a Java <code>long</code> in a way that can be translated to
* JavaScript and could handle numbers needs more than 44 bits.
*/
class BigLongLib extends BigLongLibBase {

static class Const {
static final BigLong MAX_VALUE = create(MASK, MASK, MASK_2 >> 1);
static final BigLong MIN_VALUE = create(0, 0, SIGN_BIT_VALUE);
static final BigLong ONE = fromInt(1);
static final BigLong TWO = fromInt(2);
static final BigLong ZERO = fromInt(0);
}

public static BigLong add(BigLong a, BigLong b) {
int sum0 = getL(a) + getL(b);
int sum1 = getM(a) + getM(b) + (sum0 >> BITS);
int sum2 = getH(a) + getH(b) + (sum1 >> BITS);

return create(sum0 & MASK, sum1 & MASK, sum2 & MASK_2);
}

public static BigLong and(BigLong a, BigLong b) {
return create(getL(a) & getL(b), getM(a) & getM(b), getH(a) & getH(b));
}

public static double compare(BigLong a, BigLong b) {
int signA = sign(a);
int signB = sign(b);
if (signA != signB) {
return signB - signA;
}

int a2 = getH(a);
int b2 = getH(b);
if (a2 != b2) {
return a2 - b2;
}

int a1 = getM(a);
int b1 = getM(b);
if (a1 != b1) {
return a1 - b1;
}

int a0 = getL(a);
int b0 = getL(b);
return a0 - b0;
}

public static BigLong div(BigLong a, BigLong b) {
return divMod(a, b, false);
}

public static BigLong fromDouble(double value) {
if (Double.isNaN(value)) {
return Const.ZERO;
}
if (value < -TWO_PWR_63_DBL) {
return Const.MIN_VALUE;
}
if (value >= TWO_PWR_63_DBL) {
return Const.MAX_VALUE;
}

boolean negative = false;
if (value < 0) {
negative = true;
value = -value;
}
int a2 = 0;
if (value >= TWO_PWR_44_DBL) {
a2 = (int) (value / TWO_PWR_44_DBL);
value -= a2 * TWO_PWR_44_DBL;
}
int a1 = 0;
if (value >= TWO_PWR_22_DBL) {
a1 = (int) (value / TWO_PWR_22_DBL);
value -= a1 * TWO_PWR_22_DBL;
}
int a0 = (int) value;
BigLong result = create(a0, a1, a2);
if (negative) {
negate(result);
}
return result;
}

public static BigLong fromInt(int value) {
return create(value);
}

public static long[] getAsLongArray(long l) {
long[] a = new long[3];
a[0] = (int) (l & MASK);
a[1] = (int) ((l >> BITS) & MASK);
a[2] = (int) ((l >> BITS01) & MASK_2);
return a;
}

public static BigLong mod(BigLong a, BigLong b) {
divMod(a, b, true);
return remainder;
}

// Assumes BITS == 22
public static BigLong mul(BigLong a, BigLong b) {
// Grab 13-bit chunks
int a0 = getL(a) & 0x1fff;
int a1 = (getL(a) >> 13) | ((getM(a) & 0xf) << 9);
int a2 = (getM(a) >> 4) & 0x1fff;
int a3 = (getM(a) >> 17) | ((getH(a) & 0xff) << 5);
int a4 = (getH(a) & 0xfff00) >> 8;

int b0 = getL(b) & 0x1fff;
int b1 = (getL(b) >> 13) | ((getM(b) & 0xf) << 9);
int b2 = (getM(b) >> 4) & 0x1fff;
int b3 = (getM(b) >> 17) | ((getH(b) & 0xff) << 5);
int b4 = (getH(b) & 0xfff00) >> 8;

// Compute partial products
// Optimization: if b is small, avoid multiplying by parts that are 0
int p0 = a0 * b0; // << 0
int p1 = a1 * b0; // << 13
int p2 = a2 * b0; // << 26
int p3 = a3 * b0; // << 39
int p4 = a4 * b0; // << 52

if (b1 != 0) {
p1 += a0 * b1;
p2 += a1 * b1;
p3 += a2 * b1;
p4 += a3 * b1;
}
if (b2 != 0) {
p2 += a0 * b2;
p3 += a1 * b2;
p4 += a2 * b2;
}
if (b3 != 0) {
p3 += a0 * b3;
p4 += a1 * b3;
}
if (b4 != 0) {
p4 += a0 * b4;
}

// Accumulate into 22-bit chunks:
// .........................................c10|...................c00|
// |....................|..................xxxx|xxxxxxxxxxxxxxxxxxxxxx| p0
// |....................|......................|......................|
// |....................|...................c11|......c01.............|
// |....................|....xxxxxxxxxxxxxxxxxx|xxxxxxxxx.............| p1
// |....................|......................|......................|
// |.................c22|...............c12....|......................|
// |..........xxxxxxxxxx|xxxxxxxxxxxxxxxxxx....|......................| p2
// |....................|......................|......................|
// |.................c23|..c13.................|......................|
// |xxxxxxxxxxxxxxxxxxxx|xxxxx.................|......................| p3
// |....................|......................|......................|
// |.........c24........|......................|......................|
// |xxxxxxxxxxxx........|......................|......................| p4

int c00 = p0 & 0x3fffff;
int c01 = (p1 & 0x1ff) << 13;
int c0 = c00 + c01;

int c10 = p0 >> 22;
int c11 = p1 >> 9;
int c12 = (p2 & 0x3ffff) << 4;
int c13 = (p3 & 0x1f) << 17;
int c1 = c10 + c11 + c12 + c13;

int c22 = p2 >> 18;
int c23 = p3 >> 5;
int c24 = (p4 & 0xfff) << 8;
int c2 = c22 + c23 + c24;

// Propagate high bits from c0 -> c1, c1 -> c2
c1 += c0 >> BITS;
c0 &= MASK;
c2 += c1 >> BITS;
c1 &= MASK;
c2 &= MASK_2;

return create(c0, c1, c2);
}

public static BigLong neg(BigLong a) {
int neg0 = (~getL(a) + 1) & MASK;
int neg1 = (~getM(a) + (neg0 == 0 ? 1 : 0)) & MASK;
int neg2 = (~getH(a) + ((neg0 == 0 && neg1 == 0) ? 1 : 0)) & MASK_2;

return create(neg0, neg1, neg2);
}

public static BigLong not(BigLong a) {
return create((~getL(a)) & MASK, (~getM(a)) & MASK, (~getH(a)) & MASK_2);
}

public static BigLong or(BigLong a, BigLong b) {
return create(getL(a) | getL(b), getM(a) | getM(b), getH(a) | getH(b));
}

public static BigLong shl(BigLong a, int n) {
n &= 63;

int res0, res1, res2;
if (n < BITS) {
res0 = getL(a) << n;
res1 = (getM(a) << n) | (getL(a) >> (BITS - n));
res2 = (getH(a) << n) | (getM(a) >> (BITS - n));
} else if (n < BITS01) {
res0 = 0;
res1 = getL(a) << (n - BITS);
res2 = (getM(a) << (n - BITS)) | (getL(a) >> (BITS01 - n));
} else {
res0 = 0;
res1 = 0;
res2 = getL(a) << (n - BITS01);
}

return create(res0 & MASK, res1 & MASK, res2 & MASK_2);
}

public static BigLong shr(BigLong a, int n) {
n &= 63;

int res0, res1, res2;

// Sign extend h(a)
int a2 = getH(a);
boolean negative = (a2 & SIGN_BIT_VALUE) != 0;
if (negative) {
a2 |= ~MASK_2;
}

if (n < BITS) {
res2 = a2 >> n;
res1 = (getM(a) >> n) | (a2 << (BITS - n));
res0 = (getL(a) >> n) | (getM(a) << (BITS - n));
} else if (n < BITS01) {
res2 = negative ? MASK_2 : 0;
res1 = a2 >> (n - BITS);
res0 = (getM(a) >> (n - BITS)) | (a2 << (BITS01 - n));
} else {
res2 = negative ? MASK_2 : 0;
res1 = negative ? MASK : 0;
res0 = a2 >> (n - BITS01);
}

return create(res0 & MASK, res1 & MASK, res2 & MASK_2);
}

/**
* Logical right shift. It does not preserve the sign of the input.
*/
public static BigLong shru(BigLong a, int n) {
n &= 63;

int res0, res1, res2;
int a2 = getH(a) & MASK_2;
if (n < BITS) {
res2 = a2 >>> n;
res1 = (getM(a) >> n) | (a2 << (BITS - n));
res0 = (getL(a) >> n) | (getM(a) << (BITS - n));
} else if (n < BITS01) {
res2 = 0;
res1 = a2 >>> (n - BITS);
res0 = (getM(a) >> (n - BITS)) | (getH(a) << (BITS01 - n));
} else {
res2 = 0;
res1 = 0;
res0 = a2 >>> (n - BITS01);
}

return create(res0 & MASK, res1 & MASK, res2 & MASK_2);
}

public static BigLong sub(BigLong a, BigLong b) {
int sum0 = getL(a) - getL(b);
int sum1 = getM(a) - getM(b) + (sum0 >> BITS);
int sum2 = getH(a) - getH(b) + (sum1 >> BITS);

return create(sum0 & MASK, sum1 & MASK, sum2 & MASK_2);
}

public static double toDouble(BigLong a) {
if (BigLongLib.compare(a, Const.ZERO) < 0) {
return -toDoubleHelper(BigLongLib.neg(a));
}
return toDoubleHelper(a);
}

// Assumes Integer.MIN_VALUE <= a <= Integer.MAX_VALUE
public static int toInt(BigLong a) {
return getL(a) | (getM(a) << BITS);
}

public static String toString(BigLong a) {
if (BigLongLibBase.isZero(a)) {
return "0";
}

if (BigLongLibBase.isMinValue(a)) {
// Special-case MIN_VALUE because neg(MIN_VALUE) == MIN_VALUE
return "-9223372036854775808";
}

if (BigLongLibBase.isNegative(a)) {
return "-" + toString(neg(a));
}

BigLong rem = a;
String res = "";

while (!BigLongLibBase.isZero(rem)) {
// Do several digits each time through the loop, so as to
// minimize the calls to the very expensive emulated div.
final int tenPowerZeroes = 9;
final int tenPower = 1000000000;
BigLong tenPowerLong = fromInt(tenPower);

rem = divMod(rem, tenPowerLong, true);
String digits = "" + toInt(BigLongLibBase.remainder);

if (!BigLongLibBase.isZero(rem)) {
int zeroesNeeded = tenPowerZeroes - digits.length();
for (; zeroesNeeded > 0; zeroesNeeded--) {
digits = "0" + digits;
}
}

res = digits + res;
}

return res;
}

public static BigLong xor(BigLong a, BigLong b) {
return create(getL(a) ^ getL(b), getM(a) ^ getM(b), getH(a) ^ getH(b));
}

/**
* Not instantiable.
*/
private BigLongLib() {
}
}

0 comments on commit e31f9a4

Please sign in to comment.