当从回调类执行代码时,Java性能测试会发生变化。Java堆栈框架问题?
我曾试图编写一个相当通用的测试框架来评测一组函数,但遇到了一个问题,我无法指出下面的详细内容 这个概念很简单。我创建了一个当从回调类执行代码时,Java性能测试会发生变化。Java堆栈框架问题?,java,performance-testing,stack-frame,Java,Performance Testing,Stack Frame,我曾试图编写一个相当通用的测试框架来评测一组函数,但遇到了一个问题,我无法指出下面的详细内容 这个概念很简单。我创建了一个Test抽象类: public abstract class Test { private final String name; public Test(String name) { this.name = name; } public abstract void test(); } 然后,我有一个主测试类,带有一些配置信
Test
抽象类:
public abstract class Test {
private final String name;
public Test(String name) {
this.name = name;
}
public abstract void test();
}
然后,我有一个主测试类,带有一些配置信息和运行测试的循环
public class MyTestClass {
public static double staticMethod1(Quat4f q) {
double angle;
float dirW = q.w;
if (q.w * q.w + q.y * q.y > 0.0f) {
if (q.w > 0.f && q.y < 0.0f)
dirW *= -1.0f;
angle = 2.0f * Math.acos(dirW);
} else {
angle = 0.0f;
}
return angle / 6.283f * 100.f;
}
public static double staticMethod2(Quat4f q) {
AxisAngle4f axisAngle = new AxisAngle4f();
axisAngle.set(q);
return axisAngle.angle / 6.283f * 100.f;
}
public static final void main(String[] args) {
final Quat4f quaternion = new Quat4f(0, 0, 0, 1);
Test[] tests = new Test[] {
new Test("staticMethod1") {
@Override
public void test() {
staticMethod1(quaternion);
}
},
new Test("staticMethod2") {
@Override
public void test() {
staticMethod2(quaternion);
}
}
};
long startTime = 0;
int repeat = 10; //How many times to repeat each iteration.
int[] tiers = new int[] { 1000, 10000, 100000, 1000000 };
long[][][] times = new long[tests.length][tiers.length][iterations];
for (int testIndex = 0; testIndex < tests.length; testIndex++) {
for (int tierIndex = 0; tierIndex < tiers.length; tierIndex++) {
for (int r = 0; r < repeat; r++) {
startTime = System.nanoTime();
for (int i = 0; i < tiers[tierIndex]; i++) {
tests[testIndex].run(); //run the test
}
times[testIndex][tierIndex][r] = System.nanoTime() - startTime; //Stash the execution time in the array.
}
}
}
}
}
回调结果
下面是相同的代码,仅从我的抽象基Test
类执行:
1000 10000 100000 1000000
staticMethod1
360453 454686 1985445 15699447
155191 449400 1639298 15048205
152391 449089 1576165 15128134
175095 451888 1537289 15300429
156746 466816 1600734 15190645
157989 464950 1641476 15483610
157368 452198 1559681 15316290
157990 460285 1572122 15402439
157367 527773 1538222 15078995
878274 454065 1548485 15077439
staticMethod2
1519562 1101263 1674130 8842756
274616 335883 1481309 8728930
285190 339616 1471046 8842135
291721 334950 1280089 8591155
294831 347391 1339491 13402065
332152 343970 1299683 10950426
300429 326553 1252100 7778814
285190 324999 1365615 8569385
297008 341792 1284133 7734030
283324 326554 1327984 11505256
重提问题
我能形成的唯一猜测是,这可能与Java堆栈框架概念有关。我真的在寻找一个能深入分析为什么会发生这种情况的人
这些截然不同的结果会是什么?
我在家里的电脑上重新运行了测试,所以我想我会重新发布这些结果以确保准确性 回调(staticMethod1、staticMethod2) 回调(staticMethod2、staticMethod1)
回调(staticMethod1、staticMethod2),只实例化AxisAngle4f
1000 10000 100000 1000000
staticMethod1
693138 745420 4382752 26091003
405098 677355 3378227 41866476
390630 669793 4349213 42472807
430088 699057 4296931 27899147
385697 675711 4300549 42643790
382410 658941 4296603 32330563
393918 662888 2602557 42622417
380437 666833 2588747 32903026
393918 738515 2616367 26079823
1805843 679985 2570004 42191343
staticMethod2
444556 1640449 963422 8620168
463298 464942 946325 8545856
431732 474478 877931 8645487
452776 466915 870698 8761229
432718 449487 882534 8572490
443898 464613 876288 8482066
414633 538596 871684 8672121
408715 190054 876287 8626744
405427 96342 874643 8607016
436664 96343 847681 8543883
这些结果并不“引人注目”。1000000次跑步的平均值为:
- 静态方法1
- 显式:14541456.7(14.5ns每次迭代)
- 回调:15272563.3(15.2ns每次迭代)
- 减速:每次迭代0.7纳秒
- 静态方法2
- 显式:2167413.1(2.2ns每次迭代)
- 回调:94495.2(9.5ns每次迭代)
- 减速:(每次迭代7纳秒)
method2
和method1
)与以下内容一致:
Test
类的相同实现李>
--XX:+printcomilation
和println
组合,可以进一步验证这种重新编译只发生一次
进一步说明
test(intiterations)
方法并将最内部的循环推入其中。这将允许关键方法调度至少每1000次迭代只发生一次,并且变得无关紧要
系统。nanoTime
的准确度远不及1ns:其粒度通常在1µs左右,要获得良好的准确度,必须远远高于1ns
这是非常好的手工制作基准。我不会仅从一般观点来攻击它。我尊重这些建议,但这个问题不是关于应用程序微观基准测试的最佳方法。这是关于理解为什么从调用静态方法到在回调类/函数中调用静态方法的结果发生了如此巨大的变化。从应用程序运行到应用程序运行,输出相对一致。+/-~每次重复的迭代次数为50000ns。请尝试以下方法:重新排序
staticMethod1
和staticMethod2
测试。看看开销的跳跃是否会停留在第二个测试上,不管该测试是什么。看起来method1
没有受到太大的影响,而method2
在第一次运行时要快得多(4ns vs 16ns)。所以,我猜是单态调用站点加内联的组合。当method2
首先运行时,JIT首先发现它是单态的,然后意识到该方法很短,将其内联。如果您想知道如何避免这些干扰对函数性能的测量,那么我建议将最内部的循环推到回调中。它将导致重复,但是测试方法调度将至少每1000次迭代只发生一次。将所需的迭代次数传递给test()
。顺便说一句,谷歌卡钳就是这样做的,不像甲骨文的jmh。我想你已经帮我找到了一个很好的理由回到卡钳:)请注意,OP首先完全测试一个代码路径,然后切换到另一个。这不应该让JVM陷入困境。@MarkoTopolnik我正在取消删除,因为你说我删除了它很遗憾。:)但我将把它编辑成一个社区维基,因为它是相当投机的。你已经有了一些重要的见解:每次呼叫的持续时间(我设法错误地计算了两个数量级:)和每次呼叫的开销(及其变化)。这是进一步得出结论的重要输入。我喜欢你的建议,即使用切换的两种方法重新运行。这可能是因为staticMethod1
的减速只是内联差异,甚至是噪声(0.7ns根本不算多!),而staticMethod2
的减速与JIT在看到一个非常确定的假设(单态调用)后感到困惑有关
1000 10000 100000 1000000
staticMethod1
629020 688864 3016204 40796517
348542 589891 2662401 39673949
355447 611921 3559403 39613447
416936 617511 4022701 39335929
412660 635267 4290355 38862108
409702 751996 4055583 38823967
405426 761202 4063803 38998238
410030 760545 4024016 39131407
411346 656640 3877366 38737489
1794991 723060 4139759 38286028
staticMethod2
2219818 4198617 2526272 15647236
735555 1939011 2651879 14482251
761860 445542 2480238 12990096
734569 222607 2437822 14278058
734898 264366 2323394 23561771
743118 220633 2739672 15669266
746734 224909 5159080 12916113
781918 223593 2342794 14843616
789481 229512 2740658 13400784
865108 227210 5202155 22015033
1000 10000 100000 1000000
staticMethod2
2159974 1598690 4343951 4011522
755284 484013 4646131 3989491
779945 460667 390302 4114111
866752 469874 413318 3833963
911141 495193 433376 4024016
878918 468230 424827 4162118
892070 452447 431074 3830346
806579 419894 463298 4003301
814142 424169 424826 3961871
830253 417593 432718 3823112
staticMethod1
768437 632637 4596480 38401771
421539 655325 3811603 37536663
418579 657626 3917481 37377517
425813 648091 3887230 37347924
423512 653023 3800095 38334692
428772 570820 3810288 37640568
435020 581013 3795162 37543896
426800 578382 3805027 37390670
448830 567861 4004617 37502466
1883443 663874 3848101 38096961
1000 10000 100000 1000000
staticMethod1
693138 745420 4382752 26091003
405098 677355 3378227 41866476
390630 669793 4349213 42472807
430088 699057 4296931 27899147
385697 675711 4300549 42643790
382410 658941 4296603 32330563
393918 662888 2602557 42622417
380437 666833 2588747 32903026
393918 738515 2616367 26079823
1805843 679985 2570004 42191343
staticMethod2
444556 1640449 963422 8620168
463298 464942 946325 8545856
431732 474478 877931 8645487
452776 466915 870698 8761229
432718 449487 882534 8572490
443898 464613 876288 8482066
414633 538596 871684 8672121
408715 190054 876287 8626744
405427 96342 874643 8607016
436664 96343 847681 8543883