/*
* Licensed to the Apache Software Foundation ( ASF ) under one or more
* contributor license agreements . See the NOTICE file distributed with
* this work for additional information regarding copyright ownership .
* The ASF licenses this file to You 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 org.apache.catalina.valves;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.junit.Test;
/**
* Some simple micro - benchmarks to help determine best approach for thread safety in valves , particularly the
* { @ link AccessLogValve } . Implemented as JUnit tests to make the simple to execute but does not used Test * as the class
* name to avoid being included in the automated unit tests .
*/
public class Benchmarks {
@Test
public void testAccessLogGetDate() throws Exception {
// Is it better to use a sync or a thread local here?
BenchmarkTest benchmark = new BenchmarkTest();
Runnable[] tests = new Runnable[] { new GetDateBenchmarkTest_Sync(), new GetDateBenchmarkTest_Local(),
new GetDateBenchmarkTest_LocalMutableLong(), new GetDateBenchmarkTest_LocalStruct() };
benchmark.doTest(5 , tests);
}
private static class GetDateBenchmarkTest_Sync implements Runnable {
@Override
public String toString() {
return "Syncs" ;
}
private volatile long currentMillis = 0 ;
private volatile Date currentDate = null ;
@Override
public void run() {
getCurrentDate();
}
public Date getCurrentDate() {
long systime = System.currentTimeMillis();
if ((systime - currentMillis) > 1000 ) {
synchronized (this ) {
if ((systime - currentMillis) > 1000 ) {
currentDate = new Date(systime);
currentMillis = systime;
}
}
}
return currentDate;
}
}
private static class GetDateBenchmarkTest_Local implements Runnable {
@Override
public String toString() {
return "ThreadLocals" ;
}
private ThreadLocal<Long > currentMillisLocal = new ThreadLocal<>() {
@Override
protected Long initialValue() {
return Long .valueOf(0 );
}
};
private ThreadLocal<Date> currentDateLocal = new ThreadLocal<>();
@Override
public void run() {
getCurrentDate();
}
public Date getCurrentDate() {
long systime = System.currentTimeMillis();
if ((systime - currentMillisLocal.get().longValue()) > 1000 ) {
currentDateLocal.set(new Date(systime));
currentMillisLocal.set(Long .valueOf(systime));
}
return currentDateLocal.get();
}
}
private static class GetDateBenchmarkTest_LocalMutableLong implements Runnable {
@Override
public String toString() {
return "ThreadLocals with a mutable Long" ;
}
private static class MutableLong {
long value = 0 ;
}
private ThreadLocal<MutableLong> currentMillisLocal = new ThreadLocal<>() {
@Override
protected MutableLong initialValue() {
return new MutableLong();
}
};
private ThreadLocal<Date> currentDateLocal = new ThreadLocal<>();
@Override
public void run() {
getCurrentDate();
}
public Date getCurrentDate() {
long systime = System.currentTimeMillis();
if ((systime - currentMillisLocal.get().value) > 1000 ) {
currentDateLocal.set(new Date(systime));
currentMillisLocal.get().value = systime;
}
return currentDateLocal.get();
}
}
private static class GetDateBenchmarkTest_LocalStruct implements Runnable {
@Override
public String toString() {
return "single ThreadLocal" ;
}
// note, that we can avoid (long -> Long) conversion
private static class Struct {
public long currentMillis = 0 ;
public Date currentDate;
}
private ThreadLocal<Struct> currentStruct = new ThreadLocal<>() {
@Override
protected Struct initialValue() {
return new Struct();
}
};
@Override
public void run() {
getCurrentDate();
}
public Date getCurrentDate() {
Struct struct = currentStruct.get();
long systime = System.currentTimeMillis();
if ((systime - struct.currentMillis) > 1000 ) {
struct.currentDate = new Date(systime);
struct.currentMillis = systime;
}
return struct.currentDate;
}
}
@Test
public void testAccessLogTimeDateElement() throws Exception {
// Is it better to use a sync or a thread local here?
BenchmarkTest benchmark = new BenchmarkTest();
Runnable[] tests = new Runnable[] { new TimeDateElementBenchmarkTest_Sync(),
new TimeDateElementBenchmarkTest_Local(), new TimeDateElementBenchmarkTest_LocalStruct(),
new TimeDateElementBenchmarkTest_LocalStruct_SBuilder() };
benchmark.doTest(5 , tests);
}
private abstract static class TimeDateElementBenchmarkTestBase {
protected static final String months[] = { "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" ,
"Nov" , "Dec" };
protected String lookup(String month) {
int index;
try {
index = Integer.parseInt(month) - 1 ;
} catch (Throwable t) {
index = 0 ; // cannot happen, in theory
}
return months[index];
}
}
private static class TimeDateElementBenchmarkTest_Sync extends TimeDateElementBenchmarkTestBase
implements Runnable {
@Override
public String toString() {
return "Syncs" ;
}
private volatile Date currentDate = new Date();
private volatile String currentDateString = null ;
private SimpleDateFormat dayFormatter = new SimpleDateFormat("dd" );
private SimpleDateFormat monthFormatter = new SimpleDateFormat("MM" );
private SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy" );
private SimpleDateFormat timeFormatter = new SimpleDateFormat("hh:mm:ss" );
@Override
public void run() {
printDate();
}
public String printDate() {
StringBuilder buf = new StringBuilder();
Date date = getDateSync();
if (currentDate != date) {
synchronized (this ) {
if (currentDate != date) {
StringBuilder current = new StringBuilder(32 );
current.append('[' );
current.append(dayFormatter.format(date)); // Day
current.append('/' );
current.append(lookup(monthFormatter.format(date))); // Month
current.append('/' );
current.append(yearFormatter.format(date)); // Year
current.append(':' );
current.append(timeFormatter.format(date)); // Time
current.append(']' );
currentDateString = current.toString();
currentDate = date;
}
}
}
buf.append(currentDateString);
return buf.toString();
}
private Date getDateSync() {
long systime = System.currentTimeMillis();
if ((systime - currentDate.getTime()) > 1000 ) {
synchronized (this ) {
if ((systime - currentDate.getTime()) > 1000 ) {
currentDate.setTime(systime);
}
}
}
return currentDate;
}
}
private static class TimeDateElementBenchmarkTest_Local extends TimeDateElementBenchmarkTestBase
implements Runnable {
@Override
public String toString() {
return "ThreadLocals" ;
}
private ThreadLocal<String> currentDateStringLocal = new ThreadLocal<>();
private ThreadLocal<Date> currentDateLocal = new ThreadLocal<>() {
@Override
protected Date initialValue() {
return new Date();
}
};
private ThreadLocal<SimpleDateFormat> dayFormatterLocal = new ThreadLocal<>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("dd" );
}
};
private ThreadLocal<SimpleDateFormat> monthFormatterLocal = new ThreadLocal<>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("MM" );
}
};
private ThreadLocal<SimpleDateFormat> yearFormatterLocal = new ThreadLocal<>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy" );
}
};
private ThreadLocal<SimpleDateFormat> timeFormatterLocal = new ThreadLocal<>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("hh:mm:ss" );
}
};
@Override
public void run() {
printDate();
}
public String printDate() {
getDateLocal();
if (currentDateStringLocal.get() == null ) {
StringBuilder current = new StringBuilder(32 );
current.append('[' );
current.append(dayFormatterLocal.get().format(currentDateLocal.get())); // Day
current.append('/' );
current.append(lookup(monthFormatterLocal.get().format(currentDateLocal.get()))); // Month
current.append('/' );
current.append(yearFormatterLocal.get().format(currentDateLocal.get())); // Year
current.append(':' );
current.append(timeFormatterLocal.get().format(currentDateLocal.get())); // Time
current.append(']' );
currentDateStringLocal.set(current.toString());
}
return currentDateStringLocal.get();
}
private Date getDateLocal() {
long systime = System.currentTimeMillis();
if ((systime - currentDateLocal.get().getTime()) > 1000 ) {
currentDateLocal.get().setTime(systime);
currentDateStringLocal.set(null );
}
return currentDateLocal.get();
}
}
private static class TimeDateElementBenchmarkTest_LocalStruct extends TimeDateElementBenchmarkTestBase
implements Runnable {
@Override
public String toString() {
return "single ThreadLocal" ;
}
private static class Struct {
public String currentDateString;
public Date currentDate = new Date();
public SimpleDateFormat dayFormatter = new SimpleDateFormat("dd" );
public SimpleDateFormat monthFormatter = new SimpleDateFormat("MM" );
public SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy" );
public SimpleDateFormat timeFormatter = new SimpleDateFormat("hh:mm:ss" );
}
private ThreadLocal<Struct> structLocal = new ThreadLocal<>() {
@Override
protected Struct initialValue() {
return new Struct();
}
};
@Override
public void run() {
printDate();
}
public String printDate() {
getDateLocal();
Struct struct = structLocal.get();
if (struct.currentDateString == null ) {
StringBuilder current = new StringBuilder(32 );
current.append('[' );
current.append(struct.dayFormatter.format(struct.currentDate)); // Day
current.append('/' );
current.append(lookup(struct.monthFormatter.format(struct.currentDate))); // Month
current.append('/' );
current.append(struct.yearFormatter.format(struct.currentDate)); // Year
current.append(':' );
current.append(struct.timeFormatter.format(struct.currentDate)); // Time
current.append(']' );
struct.currentDateString = current.toString();
}
return struct.currentDateString;
}
private Date getDateLocal() {
Struct struct = structLocal.get();
long systime = System.currentTimeMillis();
if ((systime - struct.currentDate.getTime()) > 1000 ) {
struct.currentDate.setTime(systime);
struct.currentDateString = null ;
}
return struct.currentDate;
}
}
private static class TimeDateElementBenchmarkTest_LocalStruct_SBuilder extends TimeDateElementBenchmarkTestBase
implements Runnable {
@Override
public String toString() {
return "single ThreadLocal, with StringBuilder" ;
}
private static class Struct {
public String currentDateString;
public Date currentDate = new Date();
public SimpleDateFormat dayFormatter = new SimpleDateFormat("dd" );
public SimpleDateFormat monthFormatter = new SimpleDateFormat("MM" );
public SimpleDateFormat yearFormatter = new SimpleDateFormat("yyyy" );
public SimpleDateFormat timeFormatter = new SimpleDateFormat("hh:mm:ss" );
}
private ThreadLocal<Struct> structLocal = new ThreadLocal<>() {
@Override
protected Struct initialValue() {
return new Struct();
}
};
@Override
public void run() {
printDate();
}
public String printDate() {
getDateLocal();
Struct struct = structLocal.get();
if (struct.currentDateString == null ) {
StringBuilder current = new StringBuilder(32 );
current.append('[' );
current.append(struct.dayFormatter.format(struct.currentDate)); // Day
current.append('/' );
current.append(lookup(struct.monthFormatter.format(struct.currentDate))); // Month
current.append('/' );
current.append(struct.yearFormatter.format(struct.currentDate)); // Year
current.append(':' );
current.append(struct.timeFormatter.format(struct.currentDate)); // Time
current.append(']' );
struct.currentDateString = current.toString();
}
return struct.currentDateString;
}
private Date getDateLocal() {
Struct struct = structLocal.get();
long systime = System.currentTimeMillis();
if ((systime - struct.currentDate.getTime()) > 1000 ) {
struct.currentDate.setTime(systime);
struct.currentDateString = null ;
}
return struct.currentDate;
}
}
private static class BenchmarkTest {
public void doTest(int threadCount, Runnable[] tests) throws Exception {
for (int iterations = 1000000 ; iterations < 10000001 ; iterations += 1000000 ) {
for (Runnable test : tests) {
doTestInternal(threadCount, iterations, test);
}
}
}
private void doTestInternal(int threadCount, int iterations, Runnable test) throws Exception {
long start = System.currentTimeMillis();
Thread [] threads = new Thread [threadCount];
for (int i = 0 ; i < threadCount; i++) {
threads[i] = new Thread (new TestThread(iterations, test));
}
for (int i = 0 ; i < threadCount; i++) {
threads[i].start();
}
for (int i = 0 ; i < threadCount; i++) {
threads[i].join();
}
long end = System.currentTimeMillis();
System.out.println(test.getClass().getSimpleName() + ": " + threadCount + " threads and " + iterations +
" iterations using " + test + " took " + (end - start) + "ms" );
}
}
private static class TestThread implements Runnable {
private int count;
private Runnable test;
TestThread(int count, Runnable test) {
this .count = count;
this .test = test;
}
@Override
public void run() {
for (int i = 0 ; i < count; i++) {
test.run();
}
}
}
}
Messung V0.5 in Prozent C=93 H=90 G=91
¤ Dauer der Verarbeitung: 0.9 Sekunden
¤
*© Formatika GbR, Deutschland