【问题标题】:Whats the performance cost on iterating on a list multiple times多次迭代列表的性能成本是多少
【发布时间】:2017-05-13 06:21:27
【问题描述】:

我有一个需要遍历并在此过程中执行工作的列表。我的问题是,对于我的程序,由于并发性,很难在每个循环中完成列表中所需的所有工作。我的解决方案是根据需要多次迭代循环,并在每次迭代中完成部分工作。这个例子应该能说明我的意思:

List<String> list = new ArrayList<>();
    list.add("A");
    list.add("B");
    list.add("C");
    list.add("D");

    // Method A
    for (String item : list) {
        doWorkThing1(item);
        doWorkThing2(item);
    }

    // Method B
    for (String item : list) {
        doWorkThing1(item);
    }
    for (String item : list) {
        doWorkThing2(item);
    }

方法B是我很好奇的。多次迭代循环是否有显着的成本?由于我假设大部分性能成本将用于“工作”方法,我想知道是否可以公平地说方法 A 和方法 B 之间的差异可以忽略不计?

【问题讨论】:

  • 我看不出并发的来源。您是否有多个线程在列表上工作?在这种情况下,A 和 B 都不安全。你能把代码做成单线程的吗?
  • 如果 A 修改列表(即删除项目),则可能需要 B。
  • 我不想关注问题中的并发问题。只是假设想知道在性能方面是否存在差异。
  • 请注意语义有何不同:要么一起运行doWorkThing 1 和2,要么先运行所有doWorkThing1,然后运行所有doWorkThing2。
  • 好问题,我会尽力给你答案;-)

标签: java performance iteration


【解决方案1】:

首先,当您谈论性能时,您必须定义您的意思。

如果您在谈论算法的时间复杂度,我们可以说迭代大小为n 的列表的算法的时间复杂度为 O(n)。 因此,如果您对列表进行c 迭代(其中c 是一个常数),时间复杂度保持不变。换句话说,成本不重要,所以如果你在一个大小为 n 的列表上迭代 3 次,你的时间复杂度将是

3 * O(n) ~= O(n).

现在,定义方法的作用非常重要。如果他们执行一些需要花费时间的操作(例如打印项目的值),则复杂性保持不变 -> O(n)。

你可以找到其他关于时间估计的信息here。

还有另一种衡量算法性能的方法,即空间复杂度。我没有谈论这个,因为你的代码中只定义了一个简单的数据结构,但你可以找到一些关于这个主题的有用信息here。

【讨论】:

    【解决方案2】:

    有太多变量无法告诉您使用哪个以获得最佳性能,因为这可能会因发生的情况而异。没有一种方法可以在所有情况下都发挥最佳效果。

    有时拆分循环会带来更好的性能(也许处理器的指令缓存不需要每次迭代多次重新填充)。有时组合成一个循环会带来更好的性能(也许处理器的数据缓存不需要像一个循环那样重新填充 n 次)。

    使用真实世界数据的一种方式。以另一种方式使用真实世界数据进行分析。使用效果最好的方式。

    【讨论】:

      【解决方案3】:

      一般来说,明显的开销只是由于额外的计数器/获取对额外 for 循环的额外增量。如果它对于一个 for 循环不重要但显然是一个 for 循环的成本的 N 倍,则这并不重要。

      缓存等也可能存在问题,但您应该没有太大问题。

      for 循环是有效的 (伪)

      loop:
         x = list[k++];
         work1(x);
         work2(x);
      goto loop;
      

      所以 2 个循环是

      loop:
         x = list[k++];
         work1(x);
      goto loop;
      
      loop:
         x = list[k++];
         work2(x);
      goto loop;
      

      最终,要确切知道会发生什么,您必须进行概要分析.. 但即使如此,您也并不确切知道。如果你想挤掉最后 0.00001% 的 cpu 周期,那么你可以担心,否则不要。

      如果您的工作功能不是“繁重”(做的很少),那么还有其他方法可以优化诸如展开循环等。

      【讨论】:

        【解决方案4】:

        性能差异可能是可测量的,但在您的示例中,它会小于微秒。我的直觉是在 100 纳秒的范围内......一旦代码被 JIT 编译。

        有可能,但不太可能该尺寸的性能差异显着。要使差异显着,您需要满足以下一项或多项条件:

        • 这些方法被多次调用。
        • 应用有硬实时要求;例如对其中一种方法的调用必须在很短的时间内完成。

        即使满足这些条件之一,如果完成工作所花费的时间是微秒、毫秒或更大,那么完成工作的时间将支配“浪费”的时间第二次迭代。


        这是我的建议。

        1. 在开始考虑优化之前,请清楚地了解性能要求。如果没有明示或暗示的性能要求,那么不要在优化上浪费您的时间。 (尽可能快地编写代码没有道德义务。)

        2. 获得正确(足够)的答案比快速获得答案更重要。因此,在您的代码编写和测试之前,不要花时间优化。

        3. 为自己构建一个基准(使用真实的输入数据等),您可以使用它来判断代码是否运行得足够快,并对候选优化进行前后比较。 (在对 Java 代码进行基准测试时,请注意标准陷阱。)

        4. 使用分析来决定代码中值得优化的部分。寻找热点;即花费大部分时间的方法/部分。 (优化不是热点的代码不太可能对整体应用程序性能产生显着影响。)

        5. 当您达到目标时,或当您用完热点时……停止。

        【讨论】:

        • 边缘案例:如果它是一个 9-1-1 系统,其中(微)秒很重要,这可能在道德上是必要的。永远不要说永远,哈哈。
        • 我没有说从不,但是如果你设计了一个 9-1-1 系统,而你在实现级别上只有微秒的余地,那么你可能已经做了一个认真的 系统 设计错误。但无论哪种方式,从软件工程的角度来看,基于可观察的证据而不是直觉进行优化的效率要高得多。
        【解决方案5】:

        对于这个特定的用例,您可以运行微基准测试来比较实现相同逻辑的不同代码方法,并选择最好的方法来使用。

        public void methodA() {
            List<String> list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
            list.add("D");
        
            for (String item : list) {
                doWorkThing1(item);
                doWorkThing2(item);
            }
        
        }
        
        public void methodB() {
        
            List<String> list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");
            list.add("D");
        
            for (String item : list) {
                doWorkThing1(item);
            }
            for (String item : list) {
                doWorkThing2(item);
            }
        
        }
        
        private void doWorkThing2(String item) {
            int j = 0;
            for (int i = 0; i < 10000; i++) {
                j = i + j;
            }
        }
        
        private void doWorkThing1(String item) {
            int j = 0;
            for (int i = 0; i < 10000; i++) {
                j = i + j;
            }
        }
        

        当我使用 jmh(即 Java Microbenchmarking Harness)工具运行代码时。正在产生以下结果;

        Benchmark               Mode  Cnt         Score         Error  Units
        IterationCost.methodA  thrpt   20  56183172.456 ± 1388825.737  ops/s
        IterationCost.methodB  thrpt   20  49693471.457 ±  777747.554  ops/s
        IterationCost.methodA   avgt   20        ≈ 10⁻⁸                 s/op
        IterationCost.methodB   avgt   20        ≈ 10⁻⁸                 s/op
        

        注意可能影响结果的其他因素。

        【讨论】:

          猜你喜欢
          • 2010-09-20
          • 2011-02-12
          • 1970-01-01
          • 2018-02-16
          • 1970-01-01
          • 1970-01-01
          • 2011-06-20
          • 2017-07-15
          • 1970-01-01
          相关资源
          最近更新 更多