我是java新手,昨晚正在运行一些代码,这真的让我很困扰。variable % variable我正在构建一个简单的程序来在 for 循环中显示每个 X 输出,当我使用模数作为vsvariable % 5000或诸如此类时,我注意到性能大幅下降。有人可以向我解释为什么会这样以及是什么原因造成的吗?这样我才能更好…
variable % variable
variable % 5000
这是“高效”的代码(对不起,如果我有一些语法错误,我现在不在电脑上使用代码)
long startNum = 0; long stopNum = 1000000000L; for (long i = startNum; i <= stopNum; i++){ if (i % 50000 == 0) { System.out.println(i); } }
这是“低效的代码”
long startNum = 0; long stopNum = 1000000000L; long progressCheck = 50000; for (long i = startNum; i <= stopNum; i++){ if (i % progressCheck == 0) { System.out.println(i); } }
请注意,我有一个日期变量来测量差异,一旦它变得足够长,第一个需要 50 毫秒,而另一个需要 12 秒或类似的时间。如果您的 PC 比我的效率更高,您可能需要增加stopNum或减少。progressCheck
stopNum
progressCheck
我在网上寻找这个问题,但我找不到答案,也许我只是问得不对。
编辑:我没想到我的问题如此受欢迎,我感谢所有答案。我确实在每一半所花费的时间上执行了一个基准测试,效率低下的代码花费了相当长的时间,1/4 秒与 10 秒的给予或接受。当然,他们正在使用 println,但他们都在做相同的数量,所以我不认为这会扭曲它,特别是因为差异是可重复的。至于答案,由于我是 Java 新手,我现在让投票决定哪个答案是最好的。我会尽量在星期三之前挑一个。
EDIT2:今晚我将进行另一次测试,而不是模数,它只是增加一个变量,当它到达progressCheck时,它将执行一个,然后将该变量重置为0。对于第三个选项。
EDIT3.5:
我使用了这段代码,下面我将展示我的结果。谢谢大家的帮助!我还尝试将 long 的 short 值与 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"); } }
结果:
不足为奇的是,由于缺少划分,短转换比“快速”方式快 23%。这很有趣。如果您需要每 256 次(或大约在那里)显示或比较某些内容,您可以这样做,并使用
if ((byte)integer == 0) {'Perform progress check code here'}
最后一个有趣的说明,在“最终声明的变量”上使用 65536(不是一个漂亮的数字)的模数是(慢)固定值速度的一半。之前它以接近相同的速度进行基准测试。
您正在测量OSR(堆栈上替换)存根。
OSR 存根 是一种特殊版本的编译方法,专门用于在方法运行时将执行从解释模式转移到编译代码。
类似的事情也发生在这里。当“低效代码”运行一个长循环时,该方法是专门为循环内的堆栈替换而编译的。状态从解释帧转移到 OSR 编译方法,该状态包括progressCheck局部变量。此时 JIT 无法用常量替换变量,因此无法应用某些优化,如强度降低。
特别是这意味着 JIT 不会用 乘法代替 整数除法 。
但是,如果您多次运行相同的方法,则第二次和后续运行将执行常规(非 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); } } } }
# 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
由于 OSR 存根编译效率低下,第一次迭代divVar确实慢得多。但是,只要方法从头开始重新运行,就会执行新的不受约束的版本,该版本会利用所有可用的编译器优化。
divVar