为了实现一些图像分析算法,而不必担心数据类型过多(即没有太多重复的代码),我正在为Java中的原始数组设置访问者模式。Java性能困惑:包装类比原始类型更快?
在下面的例子中,我定义两种类型的访问者
- 基本类型,其中
visit
方法的签名是visit(int, int double)
- 一个通用类型,
visit
方法,其中所述签名的是visit(int, int Double)
。
从这个公寓,这两个游客做了完全相同的操作。我的想法是尝试衡量拳击/拆箱的成本。
因此,这里是完整的程序
public class VisitorsBenchmark {
public interface Array2DGenericVisitor<TYPE, RET> {
void begin(int width, int height);
RET end();
void visit(int x, int y, TYPE value);
}
public interface Array2DPrimitiveVisitor<RET> {
void begin(final int width, final int height);
RET end();
void visit(final int x, final int y, final double value);
}
public static <RET>
RET
accept(final int width,
final int height,
final double[] data,
final Array2DGenericVisitor<Double, RET> visitor) {
final int size = width * height;
visitor.begin(width, height);
for (int i = 0, x = 0, y = 0; i < size; i++) {
visitor.visit(x, y, data[i]);
x++;
if (x == width) {
x = 0;
y++;
if (y == height) {
y = 0;
}
}
}
return visitor.end();
}
public static <RET> RET accept(final int width,
final int height,
final double[] data,
final Array2DPrimitiveVisitor<RET> visitor) {
final int size = width * height;
visitor.begin(width, height);
for (int i = 0, x = 0, y = 0; i < size; i++) {
visitor.visit(x, y, data[i]);
x++;
if (x == width) {
x = 0;
y++;
if (y == height) {
y = 0;
}
}
}
return visitor.end();
}
private static final Array2DGenericVisitor<Double, double[]> generic;
private static final Array2DPrimitiveVisitor<double[]> primitive;
static {
generic = new Array2DGenericVisitor<Double, double[]>() {
private double[] sum;
@Override
public void begin(final int width, final int height) {
final int length = (int) Math.ceil(Math.hypot(WIDTH, HEIGHT));
sum = new double[length];
}
@Override
public void visit(final int x, final int y, final Double value) {
final int r = (int) Math.round(Math.sqrt(x * x + y * y));
sum[r] += value;
}
@Override
public double[] end() {
return sum;
}
};
primitive = new Array2DPrimitiveVisitor<double[]>() {
private double[] sum;
@Override
public void begin(final int width, final int height) {
final int length = (int) Math.ceil(Math.hypot(WIDTH, HEIGHT));
sum = new double[length];
}
@Override
public void visit(final int x, final int y, final double value) {
final int r = (int) Math.round(Math.sqrt(x * x + y * y));
sum[r] += value;
}
@Override
public double[] end() {
return sum;
}
};
}
private static final int WIDTH = 300;
private static final int HEIGHT = 300;
private static final int NUM_ITERATIONS_PREHEATING = 10000;
private static final int NUM_ITERATIONS_BENCHMARKING = 10000;
public static void main(String[] args) {
final double[] data = new double[WIDTH * HEIGHT];
for (int i = 0; i < data.length; i++) {
data[i] = Math.random();
}
/*
* Pre-heating.
*/
for (int i = 0; i < NUM_ITERATIONS_PREHEATING; i++) {
accept(WIDTH, HEIGHT, data, generic);
}
for (int i = 0; i < NUM_ITERATIONS_PREHEATING; i++) {
accept(WIDTH, HEIGHT, data, primitive);
}
/*
* Benchmarking proper.
*/
double[] sumPrimitive = null;
double[] sumGeneric = null;
double aux = System.nanoTime();
for (int i = 0; i < NUM_ITERATIONS_BENCHMARKING; i++) {
sumGeneric = accept(WIDTH, HEIGHT, data, generic);
}
final double timeGeneric = System.nanoTime() - aux;
aux = System.nanoTime();
for (int i = 0; i < NUM_ITERATIONS_BENCHMARKING; i++) {
sumPrimitive = accept(WIDTH, HEIGHT, data, primitive);
}
final double timePrimitive = System.nanoTime() - aux;
System.out.println("prim = " + timePrimitive);
System.out.println("generic = " + timeGeneric);
System.out.println("generic/primitive = "
+ (timeGeneric/timePrimitive));
}
}
我知道,JIT是相当聪明的,所以我并没有太惊讶,如果游客都变成了同样表现出色。 更令人惊讶的是,通用访问者似乎执行比原始略快,这是意想不到的。我知道基准测试有时可能很困难,所以我一定做错了。你能发现错误吗?
非常感谢您的帮助! 塞巴斯蒂安
[编辑]我已经更新的代码占一个预热阶段(为了让JIT编译器完成其工作)。这不会改变结果,它始终低于1(0.95 - 0.98)。
传递一个Primitive double需要在堆栈上复制8个字节。传递Double只需要复制指针。 –
你应该把测量的任务放在不同的方法中,并运行几次直到它们被编译(10,000/15,000应该没问题)。然后在循环中运行它们并进行测量。 [这篇文章是必读](http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java)。 – assylias
如果我反复运行测试,其差异在0.99和1.06之间,泛型稍慢。 –