/**
* 時間計測用クラス
*/
public class StopWatch {
static {
// JITコンパイルでも走るのか、最初の1回目は遅いので適当に動かしておく
StopWatch s = new StopWatch(2, TimeUnit.NANOSECONDS);
s.start();
s.phase("");
s.split("");
s.stop();
s.phases();
}
/**
* 個々の経過時間を保持するクラス
*/
public class Phase {
private String _name;
private AtomicLong _start;
private long _end;
/**
* 記録を識別する名前を返す
*
* @return 名前
*/
public String getName() {
return _name;
}
/**
* 経過時間を返す。<br/>
* 単位はStopWatchクラスのコンストラクタに指定した{@link TimeUnit}による。(デフォルトはナノ秒)
*
* @return 時間
*/
public long getTime() {
return _timeUnit.convert(_end - _start.get(), TimeUnit.NANOSECONDS);
}
/**
* "名前=時間"形式の文字列を返す
*
* @return
*/
@Override
public String toString() {
return String.format("%s=%d", _name, getTime());
}
}
private int _phaseCount = 1; // stopの分
private TimeUnit _timeUnit = TimeUnit.NANOSECONDS;
private long _startTime = -1;
private Phase[] _phases;
private AtomicInteger _phaseIndex = new AtomicInteger();
/**
* デフォルトコンストラクタ。<br/>
* {@link #phase(String)}、{@link #split(String)}は使用できない。
*/
public StopWatch() {}
/**
* フェーズ計測、スプリット計測をするためのコンストラクタ
*
* @param phaseCount
* 想定される{@link #phase(String)}、{@link #split(String)}の最大呼出し回数。<br/>
* この数を超えて呼出すとエラーになる。
* @param timeUnit
* 測定結果の単位。内部的にはすべてナノ秒で記録するが、各メソッドの結果はこの単位で丸められる。
*/
public StopWatch(int phaseCount, TimeUnit timeUnit) {
_phaseCount = phaseCount + 1;
_timeUnit = timeUnit;
}
/**
* 計測を開始する
*/
public void start() {
_phases = new Phase[_phaseCount];
// phase()呼出し時に後続のすべてのPhaseの開始時間を再設定していると時間がかかるので、それを1回で済ませるためにこの時点ではすべてのPhaseで開始時間を共有する
AtomicLong start = new AtomicLong();
for (int i = 0; i < _phaseCount; i++) {
_phases[i] = new Phase();
_phases[i]._start = start;
}
_phaseIndex.set(0);
// 開始時間の取得は極力遅らせる
start.set(_startTime = System.nanoTime());
}
/**
* {@link #start()}もしくは直前の{@link #phase(String)}呼出しからの経過時間を記録する。<br/>
* このメソッドのみスレッドセーフ。
*
* @param name
* 記録を識別する名前
* @return 経過時間
*/
public long split(String name) {
long now = System.nanoTime();
checkRunning();
Phase phase = _phases[_phaseIndex.getAndIncrement()];
phase._name = name;
// 後続のPhaseと開始時間を切り離すために再設定
phase._start = new AtomicLong(phase._start.get());
phase._end = now;
return phase.getTime();
}
/**
* {@link #start()}もしくは直前の{@link #phase(String)}呼出しからの経過時間を記録する
*
* @param name
* 記録を識別する名前
* @return 経過時間
*/
public long phase(String name) {
long now = System.nanoTime();
checkRunning();
Phase phase = _phases[_phaseIndex.getAndIncrement()];
phase._name = name;
// 後続のPhaseと開始時間を切り離すために再設定
phase._start = new AtomicLong(phase._start.get());
phase._end = now;
long ret = phase.getTime();
// 後続のPhaseの開始時間を再設定
// 開始時間の取得は極力遅らせる
// 後続のPhaseは同じAtomicLongを共有しているので再設定するのは1つだけでOK
_phases[_phaseIndex.get()]._start.set(System.nanoTime());
return ret;
}
/**
* 計測を止める
*
* @return {@link #start()}からの経過時間
*/
public long stop() {
long now = System.nanoTime();
checkRunning();
Phase phase = _phases[_phaseIndex.getAndIncrement()];
phase._name = "stop";
phase._start = new AtomicLong(_startTime);
phase._end = now;
return phase.getTime();
}
private void checkRunning() {
if (_startTime == -1) {
throw new IllegalStateException();
}
}
/**
* 記録した結果を配列で返す
*
* @return
*/
public Phase[] phases() {
return Arrays.copyOf(_phases, _phaseIndex.get());
}
}