// Copyright 2006 The Apache Software Foundation | |
// | |
// 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 org.apache.tapestry5.services; | |
import org.apache.tapestry5.ioc.internal.util.ConcurrentBarrier; | |
import static java.lang.String.format; | |
import static java.lang.System.out; | |
import java.util.concurrent.locks.ReadWriteLock; | |
import java.util.concurrent.locks.ReentrantReadWriteLock; | |
/** | |
* Tests single-thread synchronization overhead using different techniques. Note that we're fudging things a bit by | |
* getting a read lock for a write operation .... it's just that I'm more concerned about read locks (which will be very | |
* common) than about write locks (very rare). Another concern is that hotspot is going to mess up our synchronization | |
* when it see we're not really doing anything multi-threaded. | |
* <p/> | |
* The results show that using the {@link org.apache.tapestry5.internal.annotations.Concurrent} aspect (which used a | |
* {@link java.util.concurrent.locks.ReentrantReadWriteLock} under the covers) is about 4x as expensive as just using | |
* the synchronized keyword. There are some anomolous results ... for example, ReadWriteLockRunner is consistently | |
* slower than ReadWriteLockAspectRunner (one would expect it to be the other way around ... must be something about how | |
* AspectJ weaves the code ... and it's use of static methods in many cases). | |
* <p/> | |
* Well, the Concurrent aspect is gone, replaced with the {@link ConcurrentBarrier} utility. | |
*/ | |
public class SyncCostBench | |
{ | |
/** | |
* Calculates a fibunacci series. | |
*/ | |
static class Worker implements Runnable | |
{ | |
private long[] series = { 1, 1 }; | |
public void run() | |
{ | |
long value = series[0] + series[1]; | |
// Now shift the values down to prepare for the next iteration. | |
series[0] = series[1]; | |
series[1] = value; | |
} | |
} | |
static class SimpleRunner implements Runnable | |
{ | |
private final Runnable delegate; | |
public SimpleRunner(Runnable delegate) | |
{ | |
this.delegate = delegate; | |
} | |
public void run() | |
{ | |
delegate.run(); | |
} | |
} | |
static class SynchronizedRunner implements Runnable | |
{ | |
private final Runnable delegate; | |
public SynchronizedRunner(Runnable delegate) | |
{ | |
this.delegate = delegate; | |
} | |
public synchronized void run() | |
{ | |
delegate.run(); | |
} | |
} | |
static class ReadWriteLockAspectRunner implements Runnable | |
{ | |
private final ConcurrentBarrier barrier = new ConcurrentBarrier(); | |
private final Runnable delegate; | |
public ReadWriteLockAspectRunner(Runnable delegate) | |
{ | |
this.delegate = delegate; | |
} | |
public void run() | |
{ | |
barrier.withRead(delegate); | |
} | |
} | |
static class ReadWriteLockRunner implements Runnable | |
{ | |
private final Runnable delegate; | |
private final ReadWriteLock lock = new ReentrantReadWriteLock(); | |
public ReadWriteLockRunner(Runnable delegate) | |
{ | |
this.delegate = delegate; | |
} | |
public void run() | |
{ | |
try | |
{ | |
lock.readLock().lock(); | |
delegate.run(); | |
} | |
finally | |
{ | |
lock.readLock().unlock(); | |
} | |
} | |
} | |
private static final int WARMUP_BLOCK_SIZE = 1000; | |
private static final int BLOCK_SIZE = 5 * 1000 * 1000; | |
static class BlockRunner implements Runnable | |
{ | |
private final Runnable delegate; | |
private final int blockSize; | |
public BlockRunner(int blockSize, Runnable delegate) | |
{ | |
this.blockSize = blockSize; | |
this.delegate = delegate; | |
} | |
public void run() | |
{ | |
for (int i = 0; i < blockSize; i++) | |
delegate.run(); | |
} | |
} | |
public static void main(String[] args) throws Exception | |
{ | |
Runnable simple = new SimpleRunner(new Worker()); | |
Runnable synched = new SynchronizedRunner(new Worker()); | |
Runnable rw = new ReadWriteLockRunner(new Worker()); | |
Runnable aspect = new ReadWriteLockAspectRunner(new Worker()); | |
out.println(format("%40s %9s %9s %9s", ",simple", ",synched", ",rw", ",aspect")); | |
stage("warmup"); | |
go(WARMUP_BLOCK_SIZE, simple); | |
go(WARMUP_BLOCK_SIZE, synched); | |
go(WARMUP_BLOCK_SIZE, rw); | |
go(WARMUP_BLOCK_SIZE, aspect); | |
out.println(); | |
for (int i = 0; i < 10; i++) | |
{ | |
Thread.sleep(5 * 1000); | |
System.gc(); | |
stage(format("stage #%d", i + 1)); | |
go(BLOCK_SIZE, simple); | |
go(BLOCK_SIZE, synched); | |
go(BLOCK_SIZE, rw); | |
go(BLOCK_SIZE, aspect); | |
out.println(); | |
} | |
} | |
private static void stage(String name) | |
{ | |
out.print(format("%30s", name)); | |
} | |
private static void go(int blockSize, Runnable runner) throws InterruptedException | |
{ | |
Thread t = new Thread(new BlockRunner(blockSize, runner)); | |
long tick = System.nanoTime(); | |
t.start(); | |
// Now wait for it to finish. | |
t.join(); | |
long tock = System.nanoTime(); | |
out.print(format(",%9d", tock - tick)); | |
} | |
} |