Open jimbogithub opened 7 years ago
@dankurka, @gkdn, @nordligulv - I think there are some similar opportunities in the java.sql
subclasses.
Looks like an interesting idea, though I think that GWT 2.8 already effectively uses double when the "long" is small enough that it doesn't need extra precision.
Have you filed a CLA? Unfortunately, we can't use your code until you do. I understand that is one of the big hurdles to contributing, but we don't add it lightly - there are legal concerns to using someone's code without their express authorization, and the CLA is how this project manages those concerns.
CLA is now signed.
GWT is definitely using emulated Longs in the compareTo() method for present date values. This is observable via the Chrome debugger. Changing to delegate to the native double gives me a 30% real world performance improvement when loading items into a date sorted collection. This must be a much greater relative improvement in the compareTo() itself.
I'm not sure how to confirm a CLA from just a github name, perhaps one of the gerrit admins can help out. Normally you would just push the patch to gerrit (which doesn't even work without the CLA), and we review from there, or have someone else take over, etc. Once someone confirms it, I'll be happy to try to shepherd this forward.
AFAICT all long operations go though LongLib and LongLib then decides wether the long is represented by a native JS number or as an emulated long. Currently all longs that fit into 44 bits should be handled as native JS numbers unless there is a bug somewhere.
https://github.com/gwtproject/gwt/blob/master/dev/core/super/com/google/gwt/lang/LongLib.java
But it also means that Dates can be constructed with long values between 2^44 and 2^53 that are indeed slow emulated longs. While Java Long has a max values of 2^63 - 1 the JS specification has an lower/upper limit for JS dates which is roughly around +-2^53. There is an open bug that suggests letting Date emulation fail fast if a larger long is provided. Currently the Date simply becomes invalid.
A quick example shows that GWT 2.8 properly uses native JS numbers for present dates in its compare method. So are you sure you are using GWT 2.8? Can you share an example project that uses emulated slow longs for dates that have milliseconds within +-2^44 or does your app use dates outside that range?
The following tests demonstrate that a Long representing current time in millis is slower than a Double doing same. Other variants are provided for further comparison.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gwt.junit.client.GWTTestCase;
public class ComparatorGWTTest extends GWTTestCase {
private final Logger log = Logger.getLogger(ComparatorGWTTest.class.getSimpleName());
private static final int SIZE = 10000;
public void testLongSmall() {
log.log(Level.INFO, "testLongSmall " + doLong(1, null));
log.log(Level.INFO, "testLongSmall " + doLong(1, null));
log.log(Level.INFO, "testLongSmall " + doLong(1, null));
}
public void testLongBig() {
log.log(Level.INFO, "testLongBig " + doLong(Long.MAX_VALUE - (SIZE * 2), null));
log.log(Level.INFO, "testLongBig " + doLong(Long.MAX_VALUE - (SIZE * 2), null));
log.log(Level.INFO, "testLongBig " + doLong(Long.MAX_VALUE - (SIZE * 2), null));
}
public void testLongCurrent() {
log.log(Level.INFO, "testLongCurrent " + doLong(System.currentTimeMillis(), null));
log.log(Level.INFO, "testLongCurrent " + doLong(System.currentTimeMillis(), null));
log.log(Level.INFO, "testLongCurrent " + doLong(System.currentTimeMillis(), null));
}
public void testLongCurrentWithComparator() {
log.log(Level.INFO,
"testLongCurrentWithComparator " + doLong(System.currentTimeMillis(), new LongComparator()));
log.log(Level.INFO,
"testLongCurrentWithComparator " + doLong(System.currentTimeMillis(), new LongComparator()));
log.log(Level.INFO,
"testLongCurrentWithComparator " + doLong(System.currentTimeMillis(), new LongComparator()));
}
public void testDouble() {
log.log(Level.INFO, "testDouble " + doDouble(System.currentTimeMillis(), null));
log.log(Level.INFO, "testDouble " + doDouble(System.currentTimeMillis(), null));
log.log(Level.INFO, "testDouble " + doDouble(System.currentTimeMillis(), null));
}
public void testDoubleWithComparator() {
log.log(Level.INFO, "testDoubleWithComparator " + doDouble(System.currentTimeMillis(), new DoubleComparator()));
log.log(Level.INFO, "testDoubleWithComparator " + doDouble(System.currentTimeMillis(), new DoubleComparator()));
log.log(Level.INFO, "testDoubleWithComparator " + doDouble(System.currentTimeMillis(), new DoubleComparator()));
}
public void testDate() {
log.log(Level.INFO, "testDate " + doDate(System.currentTimeMillis(), null));
log.log(Level.INFO, "testDate " + doDate(System.currentTimeMillis(), null));
log.log(Level.INFO, "testDate " + doDate(System.currentTimeMillis(), null));
}
public void testDateWithComparator() {
log.log(Level.INFO, "testDateWithComparator " + doDate(System.currentTimeMillis(), new DateComparator()));
log.log(Level.INFO, "testDateWithComparator " + doDate(System.currentTimeMillis(), new DateComparator()));
log.log(Level.INFO, "testDateWithComparator " + doDate(System.currentTimeMillis(), new DateComparator()));
}
private long doLong(long seed, Comparator<Long> comparator) {
ArrayList<Long> longs = new ArrayList<>(SIZE);
long item = seed;
long start = System.currentTimeMillis();
for (int i = 0; i < SIZE; i++) {
int index = Collections.binarySearch(longs, item);
if (index >= 0) {
longs.set(index, item);
} else {
int insertIndex = -index - 1;
longs.add(insertIndex, item);
}
item++;
}
long end = System.currentTimeMillis();
return end - start;
}
private long doDouble(double seed, Comparator<Double> comparator) {
ArrayList<Double> doubles = new ArrayList<>(SIZE);
double item = seed;
long start = System.currentTimeMillis();
for (int i = 0; i < SIZE; i++) {
int index = Collections.binarySearch(doubles, item, comparator);
if (index >= 0) {
doubles.set(index, item);
} else {
int insertIndex = -index - 1;
doubles.add(insertIndex, item);
}
item++;
}
long end = System.currentTimeMillis();
return end - start;
}
private long doDate(long seed, Comparator<Date> comparator) {
ArrayList<Date> doubles = new ArrayList<>(SIZE);
Date item = new Date(seed);
long start = System.currentTimeMillis();
for (int i = 0; i < SIZE; i++) {
int index = Collections.binarySearch(doubles, item, comparator);
if (index >= 0) {
doubles.set(index, item);
} else {
int insertIndex = -index - 1;
doubles.add(insertIndex, item);
}
item = new Date(item.getTime() + 1);
}
long end = System.currentTimeMillis();
return end - start;
}
@Override
public String getModuleName() {
return "your.module.Here";
}
class LongComparator implements Comparator<Long> {
@Override
public int compare(Long o1, Long o2) {
return o1.compareTo(o2);
}
}
class DoubleComparator implements Comparator<Double> {
@Override
public int compare(Double o1, Double o2) {
return o1.compareTo(o2);
}
}
class DateComparator implements Comparator<Date> {
@Override
public int compare(Date o1, Date o2) {
return o1.compareTo(o2);
}
}
}
Change-Id: I5550713256de1d4f332736f8d8bd410205e6a8e5 pushed to Gerrit.
Two com.google.gwt.i18n.I18NSuite
tests failed for me but from a quick look I'm pretty sure that's because the tests themselves don't work when run in a TZ with local date ahead of assumed UTC date (I'm in +13 and was running in my A.M. so still prior UTC day). For the record they were:
Testcase: testChineseDateParse took 5.154 sec
FAILED
expected: <23>, actual: <24>
junit.framework.AssertionFailedError: expected: <23>, actual: <24>
at com.google.gwt.i18n.client.DateTimeParse_zh_CN_Test.testChineseDateParse(DateTimeParse_zh_CN_Test.java:72)
Testcase: testPredefDateFormat took 0.066 sec
FAILED
expected: <Short: 2010-02-01>, actual: <Short: 2010-02-02>
junit.framework.AssertionFailedError: expected: <Short: 2010-02-01>, actual: <Short: 2010-02-02>
at com.google.gwt.i18n.client.I18N2Test.testPredefDateFormat(I18N2Test.java:460)
I also spotted the following com.google.gwt.emultest.CollectionsSuite
failure but I think it worked fine. The test expects an IllegalArgumentException which is being thrown but is still being reported as an ERROR.
Testcase: testParse took 0.099 sec
Caused an ERROR
null
java.lang.RuntimeException(EXACT TYPE UNKNOWN)
at java.lang.Throwable.Throwable(Throwable.java:61)
at java.lang.RuntimeException.RuntimeException(Exception.java:25)
at java.lang.IllegalArgumentException.IllegalArgumentException(IllegalArgumentException.java:25)
at java.util.Date.parseDbl(Date.java:47)
at com.google.gwt.emultest.java.util.DateTest.testParse(Date.java:41)
Ideally I would also have changed Timestamp:
@Override
public int compareTo(java.util.Date o) {
if (o instanceof Timestamp) {
return compareTo((Timestamp) o);
} else {
return compareTo(new Timestamp(o.getTime()));
}
}
to:
return compareTo(new Timestamp(o.jsdate.getTime()));
but there is some kind of API validator claiming that jsdate
is not visible in this case (even though it is for our emulated super class).
Try running your tests with the TZ=America/Los_Angeles
env variable set
GWT version: 2.8 Browser (with version): Any Operating System: Any
Description
Unnecessary
long
conversion is impacting Date performance, especially methods like compareTo() which are heavily used in sorted collections etc. This can be improved by simply delegating to theJsDate double
.A proposed patch is provided below. Hoping a committer can take this on so that I don't have to overcome the initial Contributor hurdles for this small change.
Proposed Patch