package org.cache2k.benchmark;
/*
* #%L
* Benchmarks: implementation variants
* %%
* Copyright (C) 2013 - 2017 headissue GmbH, Munich
* %%
* 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.
* #L%
*/
import org.cache2k.Cache;
import org.cache2k.Cache2kBuilder;
import org.cache2k.CacheEntry;
import org.cache2k.integration.CacheLoader;
import org.junit.Test;
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Random;
/**
* This is a proof of concept to cache the results of the Java date formatter.
* The idea is that date formatting speed may be improved by caching, because there
* will be not so many different dates to be displayed in one application.
*
* <p/>The most flexible approach is to use an associative caching approach or e.g.
* instead of DateFormat.getDateInstance(format, locale).format(date) use
* cache.get(locale).get(format).get(date). Another approach is to use a single
* cache with a cache key e.g. cache.get(new CacheKey(locale, format, date). See
* the test implementations below.
*
* @author Jens Wilke; created: 2013-12-08
*/
public class DateFormattingBenchmark {
static List<Date> dates;
static List<Date> provideListWith3MillionDates() {
if (dates != null) {
return dates;
}
Random r = new Random(1802);
ArrayList<Date> l = new ArrayList<>();
for (int i = 0; i < 3000000; i++) {
l.add(new Date(r.nextInt(200)));
}
return dates = l;
}
/**
* Straight forward formatting. Get a new formatter every time.
*/
@Test
public void testWithoutCacheAlwaysNewFormatter() {
PrintWriter w = new PrintWriter(new CharArrayWriter());
List<Date> l = provideListWith3MillionDates();
for (Date d : l) {
DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
w.print(df.format(d));
}
}
@Test
public void testWithoutCacheSingleFormatter() {
PrintWriter w = new PrintWriter(new CharArrayWriter());
DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
List<Date> l = provideListWith3MillionDates();
for (Date d : l) {
w.print(df.format(d));
}
}
/**
* Work with a single date formatter, but synchronize the access.
*/
@Test
public void testWithCacheSingleFormatter() {
final PrintWriter w = new PrintWriter(new CharArrayWriter());
final DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
Cache<Date, String> c =
Cache2kBuilder.of(Date.class, String.class)
.loader(new CacheLoader<Date, String>() {
@Override
public synchronized String load(Date o) {
return df.format(o);
}
})
.build();
List<Date> l = provideListWith3MillionDates();
for (Date d : l) {
w.print(c.get(d));
}
}
/**
* Always produce a new date formatter.
*/
@Test
public void testWithCacheNewFormatter() {
final PrintWriter w = new PrintWriter(new CharArrayWriter());
Cache<Date, String> c =
Cache2kBuilder.of(Date.class, String.class)
.loader(new CacheLoader<Date, String>() {
@Override
public String load(Date o) {
DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE);
return df.format(o);
}
})
.build();
List<Date> l = provideListWith3MillionDates();
for (Date d : l) {
w.print(c.get(d));
}
c.close();
}
/**
* Associative caching: This puts a cache, in the cache, within a cache.
* Although three cache requests are made, the speed is comparable to the
* other versions. Disadvantage: Since we have a bunch of caches, we don't
* have control on how many date strings will be cached.
*
* <p/>Warning: This works, but it may hog memory since caches will not be garbage collected.
* Either support noname caches which are garbage collected, or make use of an eviction event
* to call destroy() on the cache.
*/
@Test
@SuppressWarnings("unchecked")
public void testAssociativeCache() {
Cache<Locale, Cache<Integer, Cache<Date, String>>> c = (Cache)
Cache2kBuilder.of(Locale.class, Cache.class)
.eternal(true)
.name(DateFormattingBenchmark.class, "testAssociativeCache")
.loader(new CacheLoader<Locale, Cache>() {
@Override
public Cache load(final Locale l) {
return
Cache2kBuilder.of(Integer.class, Cache.class)
.name(DateFormattingBenchmark.class, "testAssociativeCache-" + l)
.eternal(true)
.loader(new CacheLoader<Integer, Cache>() {
public Cache load(final Integer _format) {
return Cache2kBuilder.of(Date.class, String.class)
.name(DateFormattingBenchmark.class, "testAssociativeCache-" + l + "-" + _format)
.loader(new CacheLoader<Date, String>() {
public String load(Date d) {
DateFormat df = DateFormat.getDateInstance(_format, l);
return df.format(d);
}
})
.build();
}
})
.build();
}
})
.build();
PrintWriter w = new PrintWriter(new CharArrayWriter());
List<Date> l = provideListWith3MillionDates();
for (Date d : l) {
w.print(c.get(Locale.FRANCE).get(DateFormat.LONG).get(d));
}
for (CacheEntry<Locale, Cache<Integer, Cache<Date, String>>> e : c.entries()) {
for (CacheEntry<Integer, Cache<Date, String>> e1 : e.getValue().entries()) {
e1.getValue().close();
}
e.getValue().close();
}
c.close();
}
/**
* Work with cache key object and one big cache.
*/
@Test
public void testWithCacheAndKeyObject() {
Cache<CacheKey, String> c =
Cache2kBuilder.of(CacheKey.class, String.class)
.eternal(true)
.loader(new CacheLoader<CacheKey, String>() {
@Override
public String load(CacheKey o) {
DateFormat df = DateFormat.getDateInstance(o.format, o.locale);
return df.format(o.date);
}
})
.build();
PrintWriter w = new PrintWriter(new CharArrayWriter());
List<Date> l = provideListWith3MillionDates();
for (Date d : l) {
w.print(c.get(new CacheKey(Locale.FRANCE, DateFormat.LONG, d)));
}
c.close();
}
static class CacheKey {
Locale locale;
int format;
Date date;
CacheKey(Locale locale, int format, Date date) {
this.locale = locale;
this.format = format;
this.date = date;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CacheKey that = (CacheKey) o;
if (format != that.format) return false;
if (!date.equals(that.date)) return false;
if (!locale.equals(that.locale)) return false;
return true;
}
@Override
public int hashCode() {
int result = locale.hashCode();
result = 31 * result + format;
result = 31 * result + date.hashCode();
return result;
}
}
}