Commit fd1d5e92 authored by Elliott Hughes's avatar Elliott Hughes
Browse files

icu4c DateIntervalFormat objects are expensive enough that we need to cache them.

It takes ~1ms to create a DateIntervalFormat on a z620, and around 8ms on a
current mobile device (Nexus 4). Add a small cache of recently-used formatters,
using a big lock rather than per-thread caches since this typically only
happens on the UI thread anyway, and because all the other frameworks date/time
formatting is behind a single lock too.

Bug: 10696944
Change-Id: I8ccbe0b31b722a95a08fbc5a119173b7a0f44807
parent 298a19b1
/*
* Copyright (C) 2013 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 benchmarks.regression;
import com.google.caliper.SimpleBenchmark;
import java.util.Locale;
import java.util.TimeZone;
import static libcore.icu.DateIntervalFormat.*;
public class DateIntervalFormatBenchmark extends SimpleBenchmark {
public void timeDateIntervalFormat_formatDateRange_DATE(int reps) throws Exception {
Locale l = Locale.US;
TimeZone utc = TimeZone.getTimeZone("UTC");
int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY;
for (int rep = 0; rep < reps; ++rep) {
formatDateRange(l, utc, 0L, 0L, flags);
}
}
public void timeDateIntervalFormat_formatDateRange_TIME(int reps) throws Exception {
Locale l = Locale.US;
TimeZone utc = TimeZone.getTimeZone("UTC");
int flags = FORMAT_SHOW_TIME | FORMAT_24HOUR;
for (int rep = 0; rep < reps; ++rep) {
formatDateRange(l, utc, 0L, 0L, flags);
}
}
public void timeDateIntervalFormat_formatDateRange_DATE_TIME(int reps) throws Exception {
Locale l = Locale.US;
TimeZone utc = TimeZone.getTimeZone("UTC");
int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_SHOW_TIME | FORMAT_24HOUR;
for (int rep = 0; rep < reps; ++rep) {
formatDateRange(l, utc, 0L, 0L, flags);
}
}
}
......@@ -19,6 +19,7 @@ package libcore.icu;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import libcore.util.BasicLruCache;
/**
* Exposes icu4c's DateIntervalFormat.
......@@ -46,7 +47,18 @@ public final class DateIntervalFormat {
private static final int DAY_IN_MS = 24 * 60 * 60 * 1000;
private static final int EPOCH_JULIAN_DAY = 2440588;
// TODO: check whether icu4c's DateIntervalFormat is expensive enough to warrant a native peer.
private static final FormatterCache CACHED_FORMATTERS = new FormatterCache();
static class FormatterCache extends BasicLruCache<String, Long> {
FormatterCache() {
super(8);
}
protected void entryEvicted(String key, Long value) {
destroyDateIntervalFormat(value);
}
};
private DateIntervalFormat() {
}
......@@ -86,7 +98,20 @@ public final class DateIntervalFormat {
}
String skeleton = toSkeleton(startCalendar, endCalendar, flags);
return formatDateInterval(skeleton, locale.toString(), tz.getID(), startMs, endMs);
synchronized (CACHED_FORMATTERS) {
return formatDateInterval(getFormatter(skeleton, locale.toString(), tz.getID()), startMs, endMs);
}
}
private static long getFormatter(String skeleton, String localeName, String tzName) {
String key = skeleton + "\t" + localeName + "\t" + tzName;
Long formatter = CACHED_FORMATTERS.get(key);
if (formatter != null) {
return formatter;
}
long address = createDateIntervalFormat(skeleton, localeName, tzName);
CACHED_FORMATTERS.put(key, address);
return address;
}
private static String toSkeleton(Calendar startCalendar, Calendar endCalendar, int flags) {
......@@ -204,5 +229,7 @@ public final class DateIntervalFormat {
return (int) (utcMs / DAY_IN_MS) + EPOCH_JULIAN_DAY;
}
private static native String formatDateInterval(String skeleton, String localeName, String timeZoneName, long fromDate, long toDate);
private static native long createDateIntervalFormat(String skeleton, String localeName, String tzName);
private static native void destroyDateIntervalFormat(long address);
private static native String formatDateInterval(long address, long fromDate, long toDate);
}
......@@ -23,41 +23,52 @@
#include "cutils/log.h"
#include "unicode/dtitvfmt.h"
static jstring DateIntervalFormat_formatDateInterval(JNIEnv* env, jclass, jstring javaSkeleton, jstring javaLocaleName, jstring javaTzName, jlong fromDate, jlong toDate) {
static jlong DateIntervalFormat_createDateIntervalFormat(JNIEnv* env, jclass, jstring javaSkeleton, jstring javaLocaleName, jstring javaTzName) {
Locale locale = getLocale(env, javaLocaleName);
ScopedJavaUnicodeString skeletonHolder(env, javaSkeleton);
if (!skeletonHolder.valid()) {
return NULL;
}
ScopedJavaUnicodeString tzNameHolder(env, javaTzName);
if (!tzNameHolder.valid()) {
return NULL;
return 0;
}
UErrorCode status = U_ZERO_ERROR;
UniquePtr<DateIntervalFormat> formatter(DateIntervalFormat::createInstance(skeletonHolder.unicodeString(), locale, status));
DateIntervalFormat* formatter(DateIntervalFormat::createInstance(skeletonHolder.unicodeString(), locale, status));
if (maybeThrowIcuException(env, "DateIntervalFormat::createInstance", status)) {
return NULL;
return 0;
}
ScopedJavaUnicodeString tzNameHolder(env, javaTzName);
if (!tzNameHolder.valid()) {
return 0;
}
formatter->adoptTimeZone(TimeZone::createTimeZone(tzNameHolder.unicodeString()));
return reinterpret_cast<uintptr_t>(formatter);
}
static void DateIntervalFormat_destroyDateIntervalFormat(JNIEnv*, jclass, jlong address) {
delete reinterpret_cast<DateIntervalFormat*>(address);
}
static jstring DateIntervalFormat_formatDateInterval(JNIEnv* env, jclass, jlong address, jlong fromDate, jlong toDate) {
DateIntervalFormat* formatter(reinterpret_cast<DateIntervalFormat*>(address));
DateInterval date_interval(fromDate, toDate);
UnicodeString result;
UnicodeString s;
FieldPosition pos = 0;
formatter->format(&date_interval, result, pos, status);
UErrorCode status = U_ZERO_ERROR;
formatter->format(&date_interval, s, pos, status);
if (maybeThrowIcuException(env, "DateIntervalFormat::format", status)) {
return NULL;
return NULL;
}
return env->NewString(result.getBuffer(), result.length());
return env->NewString(s.getBuffer(), s.length());
}
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(DateIntervalFormat, formatDateInterval, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JJ)Ljava/lang/String;"),
NATIVE_METHOD(DateIntervalFormat, createDateIntervalFormat, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J"),
NATIVE_METHOD(DateIntervalFormat, destroyDateIntervalFormat, "(J)V"),
NATIVE_METHOD(DateIntervalFormat, formatDateInterval, "(JJJ)Ljava/lang/String;"),
};
void register_libcore_icu_DateIntervalFormat(JNIEnv* env) {
jniRegisterNativeMethods(env, "libcore/icu/DateIntervalFormat", gMethods, NELEM(gMethods));
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment