Permalink
Browse files

perf: implement fast-path to TimeZone.getDefault if the cache field i…

…s accessible through reflection

In case TimeZone#defaultTimeZone field is accessible, get that field
to see if the default zone was changed.

see #588
  • Loading branch information...
vlsi committed Jul 11, 2016
1 parent d2b86a0 commit 6b3f2e07ac81a440c58b3f8231b4754f60fb5b67
@@ -9,13 +9,13 @@
package org.postgresql.core.v2;
import org.postgresql.core.SqlCommand;
import org.postgresql.core.SqlCommandType;
import org.postgresql.core.NativeQuery;
import org.postgresql.core.ParameterList;
import org.postgresql.core.Parser;
import org.postgresql.core.ProtocolConnection;
import org.postgresql.core.Query;
import org.postgresql.core.SqlCommand;
import org.postgresql.core.SqlCommandType;
import java.util.List;
@@ -11,8 +11,6 @@
import org.postgresql.PGProperty;
import org.postgresql.copy.CopyOperation;
import org.postgresql.core.SqlCommand;
import org.postgresql.core.SqlCommandType;
import org.postgresql.core.Field;
import org.postgresql.core.Logger;
import org.postgresql.core.NativeQuery;
@@ -26,6 +24,8 @@
import org.postgresql.core.QueryExecutor;
import org.postgresql.core.ResultCursor;
import org.postgresql.core.ResultHandler;
import org.postgresql.core.SqlCommand;
import org.postgresql.core.SqlCommandType;
import org.postgresql.core.Utils;
import org.postgresql.jdbc.BatchResultHandler;
import org.postgresql.jdbc.TimestampUtils;
@@ -1665,7 +1665,11 @@ public void setURL(int parameterIndex, java.net.URL x) throws SQLException {
}
private Calendar getDefaultCalendar() {
Calendar sharedCalendar = connection.getTimestampUtils().getSharedCalendar(defaultTimeZone);
TimestampUtils timestampUtils = connection.getTimestampUtils();
if (timestampUtils.hasFastDefaultTimeZone()) {
return timestampUtils.getSharedCalendar(null);
}
Calendar sharedCalendar = timestampUtils.getSharedCalendar(defaultTimeZone);
if (defaultTimeZone == null) {
defaultTimeZone = sharedCalendar.getTimeZone();
}
@@ -3630,7 +3630,11 @@ public boolean isWrapperFor(Class<?> iface) throws SQLException {
}
private Calendar getDefaultCalendar() {
Calendar sharedCalendar = connection.getTimestampUtils().getSharedCalendar(defaultTimeZone);
TimestampUtils timestampUtils = connection.getTimestampUtils();
if (timestampUtils.hasFastDefaultTimeZone()) {
return timestampUtils.getSharedCalendar(null);
}
Calendar sharedCalendar = timestampUtils.getSharedCalendar(defaultTimeZone);
if (defaultTimeZone == null) {
defaultTimeZone = sharedCalendar.getTimeZone();
}
@@ -16,6 +16,7 @@
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import java.lang.reflect.Field;
import java.sql.Date;
import java.sql.SQLException;
import java.sql.Time;
@@ -48,6 +49,11 @@
private static final char[][] NUMBERS;
private static final HashMap<String, TimeZone> GMT_ZONES = new HashMap<String, TimeZone>();
private static final Field DEFAULT_TIME_ZONE_FIELD;
private TimeZone prevDefaultZoneFieldValue;
private TimeZone defaultTimeZoneCache;
static {
// The expected maximum value is 60 (seconds), so 64 is used "just in case"
NUMBERS = new char[64][];
@@ -75,6 +81,23 @@
GMT_ZONES.put(pgZoneName + Math.abs(i), timeZone);
GMT_ZONES.put(pgZoneName + NUMBERS[Math.abs(i)], timeZone);
}
// Fast path to getting the default timezone.
// Accessing the default timezone over and over creates a clone with regular API.
// Because we don't mutate that object in our use of it, we can access the field directly.
// This saves the creation of a clone everytime, and the memory associated to all these clones.
Field tzField;
try {
tzField = TimeZone.class.getDeclaredField("defaultTimeZone");
tzField.setAccessible(true);
TimeZone defaultTz = TimeZone.getDefault();
Object tzFromField = tzField.get(null);
if (defaultTz == null || !defaultTz.equals(tzFromField)) {
tzField = null;
}
} catch (Exception e) {
tzField = null;
}
DEFAULT_TIME_ZONE_FIELD = tzField;
}
private final StringBuilder sbuf = new StringBuilder();
@@ -807,8 +830,26 @@ public Date toDateBin(TimeZone tz, byte[] bytes) throws PSQLException {
return new Date(millis);
}
private static TimeZone getDefaultTz() {
return TimeZone.getDefault();
private TimeZone getDefaultTz() {
// Fast path to getting the default timezone.
if (DEFAULT_TIME_ZONE_FIELD != null) {
try {
TimeZone defaultTimeZone = (TimeZone) DEFAULT_TIME_ZONE_FIELD.get(null);
if (defaultTimeZone == prevDefaultZoneFieldValue) {
return defaultTimeZoneCache;
}
prevDefaultZoneFieldValue = defaultTimeZone;
} catch (Exception e) {
// If this were to fail, fallback on slow method.
}
}
TimeZone tz = TimeZone.getDefault();
defaultTimeZoneCache = tz;
return tz;
}
public boolean hasFastDefaultTimeZone() {
return DEFAULT_TIME_ZONE_FIELD != null;
}
/**
@@ -8,15 +8,15 @@
package org.postgresql.test.jdbc2;
import static org.junit.Assert.assertEquals;
import org.postgresql.core.BaseConnection;
import org.postgresql.jdbc.TimestampUtils;
import org.postgresql.test.TestUtil;
import org.junit.Assume;
import org.junit.Test;
import static org.junit.Assert.*;
import java.lang.reflect.Field;
import java.sql.BatchUpdateException;
import java.sql.Date;
@@ -382,7 +382,9 @@ private TimeZone getTimeZoneCache(Object stmt) {
a table for this test. */
public void setUp() throws Exception {
super.setUp();
TimestampUtils timestampUtils = ((BaseConnection) con).getTimestampUtils();
Assume.assumeFalse("If connection has fast access to TimeZone.getDefault,"
+ " then no cache is needed", timestampUtils.hasFastDefaultTimeZone());
/* Drop the test table if it already exists for some reason. It is
not an error if it doesn't exist. */
TestUtil.createTable(con, "testtz", "col1 INTEGER, col2 TIMESTAMP");
@@ -1,8 +1,8 @@
package org.postgresql.test.jdbc3;
import org.postgresql.core.SqlCommandType;
import org.postgresql.core.NativeQuery;
import org.postgresql.core.Parser;
import org.postgresql.core.SqlCommandType;
import junit.framework.TestCase;

0 comments on commit 6b3f2e0

Please sign in to comment.