【发布时间】:2019-06-21 16:23:22
【问题描述】:
我是 java 新手,昨晚正在运行一些代码,这真的让我很困扰。我正在构建一个简单的程序来在 for 循环中显示每个 X 输出,当我使用模数作为 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。
我在网上找了这个问题,但找不到答案,也许我问得不对。
编辑: 我没想到我的问题如此受欢迎,我感谢所有的答案。我确实在每一半所花费的时间上执行了一个基准测试,效率低下的代码花费了相当长的时间,1/4 秒与 10 秒的给予或接受。当然他们正在使用 println,但他们都在做相同的数量,所以我不认为这会扭曲它,特别是因为差异是可重复的。至于答案,由于我是 Java 新手,我现在让投票决定哪个答案是最好的。我会在星期三之前挑一个。
编辑2: 今晚我将进行另一次测试,它不是模数,而是增加一个变量,当它到达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");
}
}
结果:
- 固定 = 874 毫秒(通常约为 1000 毫秒,但由于它是 2 的幂而更快)
- 变量 = 8590 毫秒
- 最终变量 = 1944 毫秒(使用 50000 时约为 1000 毫秒)
- 增量 = 1904 毫秒
- 短转换 = 679 毫秒
不足为奇,由于缺少除法,Short Conversion 比“快速”方式快 23%。这很有趣。如果您需要每 256 次(或大约在那里)显示或比较一些东西,您可以这样做,并使用
if ((byte)integer == 0) {'Perform progress check code here'}
最后一个有趣的注意,在“最终声明的变量”上使用 65536(不是一个漂亮的数字)的模数是(慢)固定值速度的一半。之前它以接近相同的速度进行基准测试。
【问题讨论】:
-
我实际上得到了相同的结果。在我的机器上,第一个循环运行大约 1.5 秒,第二个循环运行大约 9 秒。如果我在
progressCheck变量前面添加final,则两者再次以相同的速度运行。这让我相信当编译器或 JIT 知道progressCheck是常量时,它会设法优化循环。 -
除以常数can be easily converted to a multiplication by the multiplicative inverse。除以变量不能。在 x86 上,32 位除法比 64 位除法更快
-
@phuclv 注意 32 位除法在这里不是问题,在这两种情况下都是 64 位余数运算
-
@RobertCotterman 如果将变量声明为 final,编译器会创建与使用常量 (eclipse/Java 11) 相同的字节码((尽管为变量使用了一个内存槽))
标签: java performance