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; } } }