JMH测试与Java代理的使用

3

我正在尝试测量JVM代理对性能的影响,以确保它不会使我们试图运行的测试无效(也许可以从生产环境中取一些样本)。这个案例是一组BTrace脚本,在自动化负载测试期间运行,但问题可能适用于任何代理。

为了运行基准测试,我设置了一个小的JMH项目,并将代理附加为:

java -javaagent:/home/ssube/btrace/build/btrace-agent.jar=scriptdir=/home/ssube/btrace/scripts/,port=0 -jar benchmarks.jar

每次JVM被JMH分叉时,都会出现以下错误:
# Run progress: 0.00% complete, ETA 00:02:00
# Fork: 1 of 1
Exception in thread "main" java.lang.IllegalArgumentException: org.openjdk.jmh.runner.options.CommandLineOptions; local class incompatible: stream classdesc serialVersionUID = 8906142321598115825, local class serialVersionUID = 7529911323947566771
    at org.openjdk.jmh.runner.ForkedMain.main(ForkedMain.java:72)
<binary link had failed, forked VM corrupted the stream? Use EXTRA verbose to print exception>
<forked VM failed with exit code 1>
<stdout last='10 lines'>
</stdout>
<stderr last='10 lines'>
Exception in thread "main" java.lang.IllegalArgumentException: org.openjdk.jmh.runner.options.CommandLineOptions; local class incompatible: stream classdesc serialVersionUID = 8906142321598115825, local class serialVersionUID = 7529911323947566771
    at org.openjdk.jmh.runner.ForkedMain.main(ForkedMain.java:72)
</stderr>

# VM invoker: /usr/java/jdk1.8.0_11/jre/bin/java
# VM options: -javaagent:/home/ssube/btrace/build/btrace-agent.jar=scriptdir=/home/ssube/btrace/scripts/,port=0
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.stackoverflow.questions.ShaderBench.testProcessProc

我的所有类都没有实现序列化,也没有serialVersionUID。JMH基准测试可以在没有BTrace代理的情况下运行,该代理和脚本可以在没有JMH代码的情况下工作。

你如何将javaagent附加到一组JMH基准测试中,并捕获由代理引起的性能差异?

2个回答

6
我是一名有用的助手,可以为您翻译文本。
JMH维护者在这里。从维护者的角度来看,感觉JMH没有为其自己的内部类提供SUID,因此可能会对良性工具产生抵抗力。现在使用3d44d68e45be已经解决了这个问题。
changeset:   960:3d44d68e45be
tag:         tip
user:        shade
date:        Sat Aug 16 15:00:18 2014 +0400
summary:     Apply SUIDs for all Serializable classes: this protects from the benign instrumentation.

请尝试使用最新版JMH,看它是否能解决你的问题。请注意,提供SUID不能保护我们免受Java代理所做的序列化形式的彻底更改(例如注入非瞬态字段),因此如果BTrace像那样(不)表现,我们就有麻烦了。
如果上述JMH更改无效,请向jmh-dev邮件列表发送一些最小可重现的场景,我们会看看如何减轻这个问题。
否则,请查找从仪器中排除org.openjdk.jmh.*类的方法。

今天会调查这个问题,谢谢。我会告诉你我找到了什么。 - ssube
1
这似乎解决了问题。在@Fork注释中测试时,无需指定任何jvmArgs,使用各种fork计数; 现在似乎一切都正常工作。请注意,BTrace确实需要指定port = 0,否则每个fork都会尝试绑定到默认端口并失败。 - ssube
1
BTrace不会注入非瞬态字段,因为引入新字段不受当前JVM重新转换类实现的支持。 - JB-
@J.B:很好,那我们就安全了。 - Aleksey Shipilev

1
我最近遇到了同样的问题。我认为这可能与JMH的分叉有关。从你的基准测试输出中可以看出,JMH创建了一个分叉1 of 1,其中JMH为每个测试行启动一个新的JVM实例。这些JVM不再附加您的代理。
这会影响使用代理重新定义的类,在没有代理的分叉JVM上加载。因此,您会遇到序列化问题。由于您没有为类定义显式UID,因此这些UID是隐式计算的。因此,分叉的JVM将识别到原始JVM的类与分叉的JVM的类不同,导致错误。您可以通过显式定义UID来避免这些序列化问题,但您的类仍将无法被检测。

相反,尝试使用@Fork(0)对基准进行注释,以完全禁用分叉。但是,如果这会严重扭曲您的结果,您需要小心。当您的代码可以通过分析进行大量优化时,情况就是这样。如果您在基准之间共享代码,则来自第一个基准的分析结果通常会对其他基准产生负面影响。

另一种解决方案是将代理应用于分叉的JVM。为此,@Fork注释提供了几个参数。对于您的示例,您可以定义:

@Fork(jvmArgs = "-javaagent:/home/ssube/btrace/build/btrace-agent.jar=scriptdir=/home/ssube/btrace/scripts/,port=0")

请注意,这将使您的构建依赖于您的文件系统。

“@Fork(jvmArgs)” 方法对我不起作用:出现了相同的 serialVersionUID 错误。使用 “@Fork(0)” 可以解决问题,而且幸运的是不会对我的测试结果产生太大影响,但这绝对是一个好的通用解决方案。 - ssube
@ssube,我刚刚看到你在JMH类中遇到了序列化错误。最近的JMH修复程序应该可以在不添加ID到你的类的情况下正常工作。请参考Aleksey的答案获取详细信息。 - Rafael Winterhalter
这是一个目前完美的功能性解决方法。根据你的测试,分叉可能会改变结果,也可能不会(在我的情况下没有)。我将接受修复答案而不是解决方法。 - ssube
很难击败一个维护人员修复的答案,你被允许。 - Rafael Winterhalter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接