我正在测试方法句柄(随Java 7一起提供)对Java反射和镜像(一个使用反射的流畅API)的性能http://projetos.vidageek.net/mirror/mirror/)。所以我不知道我是否正确地编写了测试代码。
我使用JHM工具进行测试,并在要点中分享了代码和结果:https://gist.github.com/garcia-jj/057dcab7f388e5cb42d1
我在setup
方法中缓存了所有查找以提高性能。
反射和方法控制柄之间的最终时间几乎相等。
所以我的问题是:我的测试是对的?在我的应用程序中缓存方法查找有一些问题吗?或者,当我需要使用invokeExact
时,我需要始终查找?如果我不创建缓存,则性能太低。
谢谢
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class Reflecting {
// testing method handle
private MethodHandle mhConcat;
private MethodHandle mhHashCode;
// testing reflection
private Method rconcat;
private Method rhashcode;
// testing mirror api
private MethodHandler mrConcat;
private MethodHandler mrHashcode;
@Setup
public void setup() throws Exception {
mhConcat = publicLookup().findVirtual(String.class, "concat", methodType(String.class, String.class));
mhHashCode = publicLookup().findVirtual(Object.class, "hashCode", methodType(int.class));
rconcat = String.class.getDeclaredMethod("concat", String.class);
rhashcode = String.class.getDeclaredMethod("hashCode");
mrConcat = new Mirror().on((Object) "x").invoke().method("concat");
mrHashcode = new Mirror().on((Object) "xy").invoke().method("hashCode");
}
@GenerateMicroBenchmark
public void invoke(BlackHole bh) throws Throwable {
bh.consume((String) mhConcat.invokeExact("x", "y"));
bh.consume((int) mhHashCode.invokeExact((Object) "xy"));
}
@GenerateMicroBenchmark
public void reflect(BlackHole bh) throws Throwable {
bh.consume(rconcat.invoke("x", "y"));
bh.consume(rhashcode.invoke("xy"));
}
@GenerateMicroBenchmark
public void mirror(BlackHole bh) throws Throwable {
bh.consume(mrConcat.withArgs("y"));
bh.consume(mrHashcode.withoutArgs());
}
}
好吧,"错误"的定义通常从什么是"正确"的定义开始。假设你想通过不同的方法来衡量目标方法的调用成本,我可以列举一些可能需要注意的事情:
-
@GenerateMicroBenchmark
建议您使用相当过时的JMH(几个月前它被重命名为@Benchmark
)。考虑更新以获得更可靠的线束版本。 -
Blackhole
-s的使用是光荣的,但是为什么每个@Benchmark
有两个方法调用呢?难道你不想孤立地量化每个特定调用的性能吗?否则,您可能会用另一种方法的性能下降来掩盖一种方法中的性能改进。拆分这些方法也有助于使用隐式Blackhole
-s(即从@Benchmark
返回结果,而不是使用显式Blackhole
)。 -
基准测试无法阻止调用参数的可预测性,这可能会对被调用者内部的常量折叠进行一些测试。参见e..g JMHSample_10_ConstantFold.java.
但实际上,最大的问题是盲目相信数字。与其在StackOverflow上让其他人用他们神奇的视觉来猜测可能出了什么问题,不如实际检查是否出了问题!这将是更大问题的一部分:"为什么这些基准的性能彼此不同?"一旦你教会自己为每个基准都问这个问题,你就会接受基准之道:你应该根据解释而不是数字来进行基准测试。
要回答这些问题,您可以尝试:
-
建立一个可信的模型,计算机如何工作,以及你的代码如何与计算机交互——这通常需要处理基准参数,看看它是否以你当前模型预测的方式做出反应(看看自然科学如何在这方面处理自然)。
-
看看电脑内部的操作,看看它在忙什么。使用轮廓仪,卢克!现在,对于你在测试中使用的纳米级基准,并不是所有的轮廓仪都是好的。您可能需要深入到生成的程序集和/或硬件计数器。四处寻找。JMH附带一个非常基本的
-prof perfasm
。
换句话说,探索!