Rob*_*man 177 java performance
我是java的新手,并且昨晚运行了一些代码,这真让我烦恼.我正在构建一个简单的程序来显示for循环中的每个X输出,当我使用模数作为variable % variablevs variable % 5000或诸如此类时,我注意到性能的大幅下降.有人可以向我解释为什么会这样,是什么导致它?所以我可以更好......
这是"高效"代码(对不起,如果我得到一些语法错误我现在不在计算机上的代码)
long startNum = 0;
long stopNum = 1000000000L;
for (long i = startNum; i <= stopNum; i++){
if (i % 50000 == 0) {
System.out.println(i);
}
}
Run Code Online (Sandbox Code Playgroud)
这是"效率低下的代码"
long startNum = 0;
long stopNum = 1000000000L;
long progressCheck = 50000;
for (long i = startNum; i <= stopNum; i++){
if (i % progressCheck == 0) {
System.out.println(i);
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,我有一个日期变量来衡量差异,一旦它变得足够长,第一个花了50毫秒而另一个花了12秒或类似的东西.如果你的电脑比我的电脑更有效,你可能不得不增加stopNum或减少progressCheck.
我在网上找了这个问题,但我找不到答案,也许我只是没有问它.
编辑:我没想到我的问题如此受欢迎,我很欣赏所有答案.我确实在每一半的时间内执行了一个基准测试,效率低下的代码需要相当长的时间,1/4秒与10秒的时间相比.当然他们正在使用println,但他们都做了相同的数量,所以我不认为这会扭曲很多,特别是因为差异是可重复的.至于答案,因为我是Java新手,我会让投票现在决定哪个答案最好.我会在星期三之前选择一个.
EDIT2:我今晚要进行另一次测试,而不是模数,它只是递增一个变量,当它达到progressCheck时,它将执行一次,然后将该变量重置为0.对于第三个选项.
EDIT3.5:
我使用了这段代码,下面我将展示我的结果..谢谢大家的精彩帮助!我也尝试将long的短值与0进行比较,因此我所有的新检查都会发生"65536"次,使其在重复中相等.
public class Main {
public static void main(String[] args) {
long startNum = 0;
long stopNum = 1000000000L;
long progressCheck = 65536;
final long finalProgressCheck = 50000;
long date;
// using a fixed value
date = System.currentTimeMillis();
for (long i = startNum; i <= stopNum; i++) {
if (i % 65536 == 0) {
System.out.println(i);
}
}
long final1 = System.currentTimeMillis() - date;
date = System.currentTimeMillis();
//using a variable
for (long i = startNum; i <= stopNum; i++) {
if (i % progressCheck == 0) {
System.out.println(i);
}
}
long final2 = System.currentTimeMillis() - date;
date = System.currentTimeMillis();
// using a final declared variable
for (long i = startNum; i <= stopNum; i++) {
if (i % finalProgressCheck == 0) {
System.out.println(i);
}
}
long final3 = System.currentTimeMillis() - date;
date = System.currentTimeMillis();
// using increments to determine progressCheck
int increment = 0;
for (long i = startNum; i <= stopNum; i++) {
if (increment == 65536) {
System.out.println(i);
increment = 0;
}
increment++;
}
//using a short conversion
long final4 = System.currentTimeMillis() - date;
date = System.currentTimeMillis();
for (long i = startNum; i <= stopNum; i++) {
if ((short)i == 0) {
System.out.println(i);
}
}
long final5 = System.currentTimeMillis() - date;
System.out.println(
"\nfixed = " + final1 + " ms " + "\nvariable = " + final2 + " ms " + "\nfinal variable = " + final3 + " ms " + "\nincrement = " + final4 + " ms" + "\nShort Conversion = " + final5 + " ms");
}
}
Run Code Online (Sandbox Code Playgroud)
结果:
不足为奇,由于缺乏划分,短转换比"快速"方式快23%.这很有趣.如果你需要每256次(或那里)显示或比较一些东西,你可以这样做,并使用
if ((byte)integer == 0) {'Perform progress check code here'}
Run Code Online (Sandbox Code Playgroud)
一个最终的有趣注意,使用"最终声明的变量"上的模数与65536(不是一个漂亮的数字)是速度(慢)的一半比固定值.之前它的基准测试速度接近相同的速度.
apa*_*gin 139
您正在测量OSR(堆栈替换)存根.
OSR存根是编译方法的特殊版本,专门用于在方法运行时将执行从解释模式转移到编译代码.
OSR存根不像常规方法那样优化,因为它们需要与解释帧兼容的帧布局.我在下面的回答表明这已经:1,2,3.
类似的事情也发生在这里.虽然"效率低下的代码"正在运行一个长循环,但该方法是专门为循环内的堆栈替换而编译的.状态从解释的帧转移到OSR编译的方法,并且该状态包括progressCheck局部变量.此时JIT不能用常量替换变量,因此不能应用某些优化,如强度降低.
特别是这意味着JIT不会用乘法替换整数除法.(请参阅为什么在执行整数除法由一个陌生的号码确实GCC使用乘法?对ASM伎俩从名列前茅的时间编译器,当值是内联/恒定传播之后编译时间常数,如果这些优化被启用表达式中的整数文字右边也得到了优化,类似于这里它甚至在OSR存根中由JITer优化.)%gcc -O0
但是,如果多次运行相同的方法,则第二次和后续运行将执行完全优化的常规(非OSR)代码.以下是证明该理论的基准(使用JMH进行基准测试):
@State(Scope.Benchmark)
public class Div {
@Benchmark
public void divConst(Blackhole blackhole) {
long startNum = 0;
long stopNum = 100000000L;
for (long i = startNum; i <= stopNum; i++) {
if (i % 50000 == 0) {
blackhole.consume(i);
}
}
}
@Benchmark
public void divVar(Blackhole blackhole) {
long startNum = 0;
long stopNum = 100000000L;
long progressCheck = 50000;
for (long i = startNum; i <= stopNum; i++) {
if (i % progressCheck == 0) {
blackhole.consume(i);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
结果如下:
# Benchmark: bench.Div.divConst
# Run progress: 0,00% complete, ETA 00:00:16
# Fork: 1 of 1
# Warmup Iteration 1: 126,967 ms/op
# Warmup Iteration 2: 105,660 ms/op
# Warmup Iteration 3: 106,205 ms/op
Iteration 1: 105,620 ms/op
Iteration 2: 105,789 ms/op
Iteration 3: 105,915 ms/op
Iteration 4: 105,629 ms/op
Iteration 5: 105,632 ms/op
# Benchmark: bench.Div.divVar
# Run progress: 50,00% complete, ETA 00:00:09
# Fork: 1 of 1
# Warmup Iteration 1: 844,708 ms/op <-- much slower!
# Warmup Iteration 2: 105,893 ms/op <-- as fast as divConst
# Warmup Iteration 3: 105,601 ms/op
Iteration 1: 105,570 ms/op
Iteration 2: 105,475 ms/op
Iteration 3: 105,702 ms/op
Iteration 4: 105,535 ms/op
Iteration 5: 105,766 ms/op
Run Code Online (Sandbox Code Playgroud)
divVar由于编译效率低下的OSR存根,第一次迭代确实要慢得多.但是一旦方法从头重新开始,就会执行新的无约束版本,该版本利用所有可用的编译器优化.
Ole*_*hov 42
在后续的@phuclv 评论中,我检查了JIT 1生成的代码,结果如下:
for variable % 5000(除以常数):
mov rax,29f16b11c6d1e109h
imul rbx
mov r10,rbx
sar r10,3fh
sar rdx,0dh
sub rdx,r10
imul r10,rdx,0c350h ; <-- imul
mov r11,rbx
sub r11,r10
test r11,r11
jne 1d707ad14a0h
Run Code Online (Sandbox Code Playgroud)
用于variable % variable:
mov rax,r14
mov rdx,8000000000000000h
cmp rax,rdx
jne 22ccce218edh
xor edx,edx
cmp rbx,0ffffffffffffffffh
je 22ccce218f2h
cqo
idiv rax,rbx ; <-- idiv
test rdx,rdx
jne 22ccce218c0h
Run Code Online (Sandbox Code Playgroud)
因为除法总是比乘法更长,所以最后一个代码片段的性能较差.
Java版本:
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)
Run Code Online (Sandbox Code Playgroud)
1 - 使用的VM选项: -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,src/java/Main.main
Jim*_*myB 26
正如其他人所指出的那样,一般模数运算需要进行除法.在某些情况下,可以通过乘法(由编译器)替换除法.但与加/减相比,两者都可能很慢.因此,可以通过以下方式预期最佳性能:
long progressCheck = 50000;
long counter = progressCheck;
for (long i = startNum; i <= stopNum; i++){
if (--counter == 0) {
System.out.println(i);
counter = progressCheck;
}
}
Run Code Online (Sandbox Code Playgroud)
(作为一个次要的optmiziation尝试,我们使用一个预先递减递减计数器在这里,因为在很多平台上,以比较0的运算处理后,因为ALU的标志由前述操作已经设定适当的成本究竟0指令/ CPU周期.一个体面的优化但是,即使您编写,编译器也会自动执行该优化if (counter++ == 50000) { ... counter = 0; }.)
请注意,通常你并不真正想要/需要模数,因为你知道你的循环计数器(i)或其他任何东西只增加1,而你真的不关心模数会给你的实际余数,只看到如果递增计数器达到某个值.
另一个"技巧"是使用两个幂的值/限制,例如progressCheck = 1024;.模数可以通过按位快速计算2的幂and,即if ( (i & (1024-1)) == 0 ) {...}.这应该也非常快,并且可能在一些架构上优于counter上面的显式.
| 归档时间: |
|
| 查看次数: |
17726 次 |
| 最近记录: |