当前位置: 首页 > 知识库问答 >
问题:

为什么if(variable1%variable2==0)是低效的?

公沈浪
2023-03-14

这里是“高效”的代码(对不起,如果我有一点语法错误,我现在不在计算机上使用代码)

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);
    }
}

请注意,我有一个日期变量来测量差异,当它变得足够长时,第一个用了50ms,而另一个用了12秒或类似的时间。如果您的PC比我的效率高,您可能必须增加stopnum或减少progresscheck

我在网上找这个问题,但我找不到答案,也许我只是问错了。

编辑:我没想到我的问题这么受欢迎,我很感激所有的答案。我确实在每一半的时间上执行了一个基准测试,而低效的代码花费了相当长的时间,1/4秒相对于10秒。当然,他们使用的是println,但他们都做了同样的量,所以我不认为这会有太大的偏差,特别是因为差异是可重复的。至于答案,因为我是Java的新手,所以我现在将让投票来决定哪个答案是最好的。我会试着在星期三之前挑一个。

Edit2:今晚我要做另一个测试,它不是modulus,而是增加一个变量,当它达到progressCheck时,它将执行一个,然后将该变量重置为0。第三种选择。

编辑3.5:

我使用了这个代码,下面我将显示我的结果…谢谢大家的帮助!我还尝试将长的短值与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");
    }
}

结果:

  • 固定=874ms(通常在1000ms左右,但由于它是2的次方而更快)
  • 变量=8590 ms
  • 最终变量=1944 ms(使用50000时为~1000ms)
  • 增量=1904 ms
  • 短转换=679 ms

不足为奇,由于缺乏除法,短转换比“快速”方式快23%。这一点值得注意。如果您需要每256次(或大约256次)显示或比较某件事,您可以这样做,并使用

if ((byte)integer == 0) {'Perform progress check code here'}

最后一个有趣的注意事项是,在“FINAL声明变量”上使用模数65536(不是一个漂亮的数字)的速度是固定值的一半(慢)。在此之前,它的基准测试速度接近相同。

共有1个答案

归明诚
2023-03-14

您正在测量OSR(堆栈上替换)存根。

OSR存根是编译方法的一个特殊版本,专门用于在方法运行时将执行从解释模式转换为编译代码。

OSR存根不如常规方法优化,因为它们需要一个与解释帧兼容的帧布局。我已经在下面的答案中显示了这一点:1、2、3。

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

特别是,这意味着JIT不会用乘法代替整数除法。(请参见为什么GCC在实现整数除法时使用一个奇怪的数字的乘法?对于来自提前编译器的asm技巧,当值是内联/常数传播后的编译时常数时,如果启用了这些优化。%表达式中的整数文字也会通过gcc-o0进行优化,类似于这里,即使在OSR存根中也会通过JITer进行优化。)

但是,如果您多次运行相同的方法,第二次和随后的运行将执行常规的(非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

divvar的第一次迭代确实要慢得多,因为编译的OSR存根效率不高。但是只要方法从一开始就重新运行,就会执行新的无约束版本,它利用了所有可用的编译器优化。

 类似资料:
  • 问题内容: 我必须编写一个例程,如果变量的类型为,则将变量的值加1,否则将变量的值分配为0,其中变量的初始值为或。 第一个实现是因为我认为没有数字会使算术表达式为假,但是由于计算为真,所以这是错误的。然后,我得知行为类似于0,并且以下表达式均被评估为true。 当然不是0。被评估为false。这使看似重言式的表达成为错误。 为什么实际上不是0,却像0? 问题答案: 您真正的问题似乎是: 为什么:

  • 今天我决定测试一下,结果我惊讶地发现(至少在C#正则表达式引擎中)似乎比其他两个没有太大区别的代码效率要低。下面是我测试输出的10000个由1000个随机字符组成的字符串,其中5077个实际包含一个数字: 这对我来说是一个惊喜,有两个原因,如果有人能给我一些启示,我会很感兴趣: 我本以为范围的实现会比集合的实现效率高得多。 我不能理解为什么比差。除了的简写之外,还有其他内容吗? 下面是测试代码:

  • 问题内容: 我有以下代码。应该返回表的最后一行的mysqli_insert_id()(在本例中为“ $ last_row”)始终返回0。为什么会这样呢? 问题答案: 并 没有 返回表的最后一排的ID。从文档中,它: …返回由查询产生的ID,该查询是对具有具有AUTO_INCREMENT属性的列的表进行的。如果最后一个查询不是or 语句,或者如果修改后的表没有带有属性的列,则此函数 将返回零 。 (

  • 问题内容: 什么是高/低算法? 我已经在NHibernate文档中找到了这一点(这是生成唯一密钥的一种方法,第5.1.4.2节),但是我没有找到有关其工作原理的很好的解释。 我知道Nhibernate可以处理它,并且我不需要了解内部,但是我很好奇。 问题答案: 基本思想是,您有两个数字组成主键-“高”数字和“低”数字。客户端可以从本质上增加“高”序列,知道它随后可以安全地从先前的“高”值的整个范围

  • 为什么在Python中比慢?难道不应该比快吗? 我试图学习模块。从基础开始,我尝试了这些: null 注意:我运行三次,取结果的平均值,然后将时间和代码一起张贴在这里。 这个问题与如何做微基准测试无关(我在这个例子中做了,但我也明白它太基础了),而是为什么检查一个‘真’变量比一个常量慢。

  • 问题内容: 我一直在获取要使用mysqli返回的行数方面遇到麻烦。即使确实有一些结果,我每次都会得到0。 为什么没有显示正确的数字? 问题答案: 您需要先调用num_rows查找: 请参阅文档,该文档显示在页面顶部附近(在主要说明区域中)…