【问题标题】:What loop is more efficient?什么循环更有效?
【发布时间】:2019-07-06 12:18:45
【问题描述】:

我想知道哪种代码效率更高,我有两种选择。你会说哪个更有效,为什么?谢谢。

选项 A

array1 size is 1000
array2 size is 2000

for(int i = 0; i < array1.size(); i++)
{
    for(int j = 0; j < array2.size(); j++) {
        if(array1[i].method() == array2[j].method()) // CHECKS IF THERE'S AN EQUAL IN BOTH ARRAYS
        {
            doSomething();
            break;
        }
        if(j == array2.size()-1) // CHECKS IF ARRAY1 DID NOT FIND A MATCH
        {
            doSomething();
            break;
        }
        for(k = 0; k < array1.size(); k++)
        {
            if(array1[k].method() == array2[j].method()) // CHECKS IF THERE'S AN EQUAL IN BOTH ARRAYS
            {
                // BUT DOES NOTHING BECAUSE IT WAS DONE ALREADY UPSIDE
                break;
            }
            if(k == array1.size()-1) // CHECKS IF ARRAY2 DID NOT FIND A MATCH
            {
                doSomething();
                break;
            }
        }
    }
}

选项 B

array1 size is 1000
array2 size is 2000


    for(int i = 0; i < array1.size(); i++)
    {
        for(int j = 0; j < array2.size(); j++) {
            if(array1[i].method() == array2[j].method()) // CHECKS IF THERE'S AN EQUAL IN BOTH ARRAYS
            {
                doSomething();
                break;
            }
            if(j == array2.size-1)  // CHECKS IF ARRAY1 HAS NO ARRAY2 MATCH
            {
                doSomething();
                break;
            }
        }
    }

    for(int j = 0; j < array2.size(); j++)
    {
        for(int i = 0; i < array1.size(); i++) {
            if(array2[j].method() == array1[i].method()) // CHECKS IF THERE'S AN EQUAL IN BOTH ARRAYS
            {
                // BUT DOES NOTHING BECAUSE IT WAS DONE ALREADY UPSIDE
                break;
            }
            if(i == array1.size-1) // CHECKS IF ARRAY2 HAS NO ARRAY1 MATCH
            {
                doSomething();
                break;
            }
        }
    }

我目前已经实现了选项 B,我想知道是否应该转到选项 A,因为尽管选项 A 可能需要更多时间,但我不知道是两个循环都需要更多时间还是一个循环完成所有迭代需要更多时间. 或者可能是一样的,我真的不知道。

【问题讨论】:

  • 我认为你从测量开始。不要猜测,学习如何正确测量您的代码!
  • “如果你有两匹马,并且你想知道这两匹马中哪一个跑得更快,那就让你的马赛跑吧。” - 埃里克·利珀特,ericlippert.com/2012/12/17/performance-rant
  • 我将 array1[i].method() 移到内部循环之外,因为它的值不应该随着 j 的变化而变化,
  • if(array1[i].method() == array2[j].method()) 不能从循环中取出,除非 for(condition) Type of var var1 = array1[i] .method() 和 var var2 的类型 = array2[j].method()。这对优化有帮助还是对 if 内部循环比较无动于衷? @NormR
  • 扩展@GhostCat的评论:如果你测量性能,请使用JMH保证代码是热的。

标签: java loops logic


【解决方案1】:

选项 A 是 O(x^2*y) 选项 B 是 O(x*y) 在您的情况下,选项 A 最多需要 2,000,000,000 次迭代,而 B 最多需要 4,000,000 次迭代。这当然不包括休息或继续。我可能会坚持使用 B。

【讨论】:

  • 谢谢。这是正确的答案。我的循环选项 A 用了 30 分钟,选项 B 用了 3 秒。
【解决方案2】:

正如@Sploder12 所说,但您也将获得很多在循环中取出函数调用,例如

int n= array.size()
for(int i=0; i<n; i++)

而不是

 for(int i=0; i<array.size(); i++)

除非您在循环中修改数组大小,否则重复的函数调用是一种浪费。

【讨论】:

  • 您的建议将时间从经过时间:2.6623344 改进为经过时间:1.9251856。不错!
  • 不会在此级别上进行优化。 JVM 应该能够注意到大小是有效的最终大小并替换它。代码一热,这个优化就应该自动完成。
【解决方案3】:

关于@Turing85 的评论,我确实想知道是否有一些编译器机制可以发现循环初始化中 size() 的返回值可能是常量,因此可以用常量替换“有效地最终”。因此,我运行了以下两个循环,第一个没有手动优化常量:

int n1=a1.size();
for(int i=0; i<a1.size(); i++) {
    int n2=a2.size();
    for(int j=0; j<a2.size(); j++) {
        int n3=a3.size();
        for(int k=0; k<a3.size(); k++) {
            int candidate=a1.get(i)*a2.get(j)*a3.get(k);
            candidate+=n1*n2*n3;
            func(candidate);
        }//k
    }//j
}//i

第二个手动常量优化:

int n1=a1.size();
for(int i=0; i<n1; i++) {
    int n2=a2.size();
    for(int j=0; j<n2; j++) {
        int n3=a3.size();
        for(int k=0; k<n3; k++) {
            int candidate=a1.get(i)*a2.get(j)*a3.get(k);
            candidate+=n1*n2*n3;
            func(candidate);
        }//k
    }//j
}//i

然后,我使用 a1.size()=100、a2.size()=200 和 a3.size()=300 运行这些循环 10 次以获得时间并计算 100 次的平均值和标准误差循环的每个变体都随机运行 200 次。

整个过程在单个作业中重复 10 次(即 JVM 的单次调用)以获得 10 对均值和标准误差(一对中的一个来自优化运行,另一个来自未优化运行)。在所有 10 种情况下,优化的平均时间都比未优化的平均时间快得多。在每种情况下,平均值之间的差异都超过标准误差的 6.9 倍,并且在 10 次中的 7 次中,平均值之间的差异超过 15 个标准误差。数据如下。为循环生成的字节码是不同的,虽然我没有声称会说字节码,但我认为对 a*.size() 的额外调用虚拟是造成两个循环版本性能差异的原因。

所以,考虑到@Turing85 的评论,我想知道在什么条件下可以忽略“此级别”的优化?

openjdk 版本“11” 2018-09-25 OpenJDK 运行时环境 18.9(内部版本 11+28) OpenJDK 64-Bit Server VM 18.9(build 11+28,混合模式)

====== 数据======

100 次优化循环运行平均每次耗时 6.412 秒,标准误差 0.013; 100 次未优化循环的运行平均每次耗时 6.502 秒,标准误差 0.013

100 次优化循环运行平均每次耗时 5.143 秒,标准误差 0.004; 100 次未优化循环的运行平均每次耗时 5.232 秒,标准误差 0.005

100 次优化循环运行平均每次耗时 6.090 秒,标准误差 0.006; 100 次未优化循环的运行平均每次耗时 6.175 秒,标准误差 0.006

100 次优化循环运行平均每次耗时 5.739 秒,标准误差 0.005; 100 次未优化循环的运行平均每次耗时 5.827 秒,标准误差 0.005

100 次优化循环运行平均每次耗时 5.613 秒,标准误差 0.005; 100 次未优化循环的运行平均每次耗时 5.697 秒,标准误差 0.004

100 次优化循环运行平均每次耗时 6.045 秒,标准误差 0.004; 100 次未优化循环的运行平均每次耗时 6.121 秒,标准误差 0.004

100 次优化循环运行平均每次耗时 5.333 秒,标准误差 0.003; 100 次未优化循环的运行平均每次耗时 5.415 秒,标准误差 0.003

100 次优化循环运行平均每次耗时 5.903 秒,标准误差 0.009; 100 次未优化循环的运行平均每次耗时 5.972 秒,标准误差 0.007

100 次优化循环运行平均每次耗时 5.770 秒,标准误差 0.005; 100 次未优化循环的运行平均每次耗时 5.851 秒,标准误差 0.005

100 次优化循环运行平均每次耗时 4.975 秒,标准误差 0.004; 100 次未优化循环的运行平均每次耗时 5.052 秒,标准误差 0.004

【讨论】:

  • 非常感谢您的回答。你做了一个非常小尺寸的测试;当每个循环的大小较大时,执行相同的操作。差异将是巨大的。添加到比较中,ArrayList.size() 可能比其他类型的大小调整效率更低。
猜你喜欢
  • 1970-01-01
  • 2023-03-07
  • 2012-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-02
  • 2021-07-14
  • 2013-02-21
相关资源
最近更新 更多