java - 为什么 if (variable1 % variable2 == 0) 效率低下?

标签 java performance

我是 java 新手,昨晚正在运行一些代码,这真的让我很困扰。我正在构建一个简单的程序来在 for 循环中显示每个 X 输出,当我使用模数作为 variable % variablevariable % 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(不是一个漂亮的数字)的模数是(慢)固定值速度的一半。之前它以接近相同的速度进行基准测试。

最佳答案

您正在测量 OSR (on-stack replacement) stub 。

OSR stub 是一种特殊版本的编译方法,专门用于在方法运行时将执行从解释模式转移到编译代码。

OSR stub 不像常规方法那样优化,因为它们需要与解释帧兼容的帧布局。我已经在以下答案中展示了这一点:1 , 2 , 3 .

这里也发生了类似的事情。当“低效代码”运行一个长循环时,该方法是专门为循环内的堆栈替换而编译的。状态从解释帧转移到 OSR 编译方法,该状态包括 progressCheck 局部变量。此时 JIT 无法用常量替换变量,因此无法应用某些优化,如 strength reduction .

这意味着 JIT 不会将 整数除法 替换为 乘法。 (参见 Why does GCC use multiplication by a strange number in implementing integer division? 了解提前编译器的 asm 技巧,当值是内联/常量传播后的编译时常量时,如果启用了这些优化。% 中的整数文字 表达式也被 gcc -O0 优化,类似于这里它被 JITer 优化的地方,即使在 OSR stub 中。)

但是,如果您多次运行相同的方法,则第二次和后续运行将执行已完全优化的常规(非 OSR)代码。这是证明理论的基准(benchmarked using 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

divVar 的第一次迭代确实慢得多,因为 OSR stub 编译效率低下。但只要方法从头开始重新运行,就会执行新的无约束版本,该版本利用了所有可用的编译器优化。

关于java - 为什么 if (variable1 % variable2 == 0) 效率低下?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54405842/

相关文章:

java - Chromium WebView 不存在

java - 如何为 Selenium 测试创建可运行的 jar

mysql - InnoDB 插入非常慢(低于 1000/秒)。如何增加呢?

performance - 为什么golang比scala慢?

perl - 在Perl中,while循环通常比for循环快吗?

java - 如何将字符串数组动态传递给 Java 中的 SQL "IN"子句?

java - 在 eventStartTime 三天前发送电子邮件

java - 退出应用程序之前保存 ListView

python - 过滤图像中补丁位置的最优化方法

php - 序列化/json_encode 到一个字段或将原始数据保留在数据库的多个字段中?