简体   繁体   English

多次遍历列表的性能成本是多少

[英]Whats the performance cost on iterating on a list multiple times

I have a List I need to iterate through and perform work on along the way. 我有一个清单,需要遍历并在此过程中继续进行。 My problem is that, for my program, its difficult to do all the work needed on the list every loop due to concurrency. 我的问题是,对于我的程序而言,由于并发性,很难在每个循环中完成列表中所需的所有工作。 My solution was to iterate over the loop as many times as necessary and doing part of the work in every iteration. 我的解决方案是根据需要遍历循环多次,并在每次迭代中完成部分工作。 This example should illustrate what I mean: 这个例子应该说明我的意思:

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

Method B is what I am curious about. 方法B是我很好奇的。 Is there a notable cost to iterating over a loop multiple times? 多次遍历循环是否有显着成本? Since I assume most of the cost in performance will go to the "work" methods, I was wondering if it's fair to say the difference between Method A and Method B would be negligible? 由于我假设性能的大部分成本都将落在“工作”方法上,因此我想知道方法A与方法B之间的差异是否可以忽略不计?

First of all you have to define what do you mean when you talk about performance. 首先,您必须定义谈论性能时的含义。

If you are talking about time complexity of your algorithm we can say that an algorithm that iterates over a list of size n has a time complexity of O(n). 如果您在谈论算法的时间复杂度 ,我们可以说,迭代大小为n的列表的算法的时间复杂度为O(n)。 So if you make c iterations of your list (where c is a costant number), the time complexity remains the same. 因此,如果对列表进行c次迭代(其中c是一个重要数字),则时间复杂度保持不变。 In other words, costants are not important, so if you iterate 3 time over a list of size n you'll have a time complexity of 换句话说,costant并不重要,因此,如果对大小为n的列表进行3次迭代,则时间复杂度为

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

Now, it's really important to define what your methods do. 现在,定义您的方法做什么非常重要。 If they perform some operation that require costant time (eg print the value of the item) the complexity remains the same -> O(n). 如果他们执行一些需要花费时间的操作(例如,打印项目的值),则复杂度保持不变-> O(n)。

You can find other informatoin about time extimation here . 您可以在此处找到有关时间激励的其他信息。

There is another way to measure the perfomance of an algorithm, the space complexity . 还有另一种衡量算法性能的方法,即空间复杂度 I didn't talk about that because there is only a simple data structure defined in your code but you can find some useful information about this topic here . 我之所以没有这样说是因为您的代码中仅定义了一个简单的数据结构,但是您可以在这里找到有关此主题的一些有用信息。

The performance difference would probably be measurable, but in your example it would be less than a microsecond. 性能差异可能是可测量的,但在您的示例中,该差异小于一微秒。 My gut feeling is in the region of 100 nanoseconds ... once the code has been JIT compiled. 我的直觉是在100纳秒的范围内……一旦对代码进行了JIT编译。

It is possible, but unlikely that that the performance difference of that size is significant . 这是可能的,但不可能是这种规模的性能差异显著 For the difference to be significant, you need one or more of the following to be true: 为了使差异显着,您需要满足以下一项或多项要求:

  • The methods are called many, many times. 这些方法被称为很多很多次。
  • The application has hard real-time requirements; 该应用程序具有严格的实时要求; eg a call to one of the methods has to complete in a very short time window. 例如,对方法之一的调用必须在很短的时间内完成。

And even if one of those criteria is met, if the time taken to do the work is microseconds, milliseconds or larger, then the time to do the work will dominate the time "wasted" on an second iteration. 即使这些条件之一满足时,如果做的工作所花费的时间是微秒,毫秒或更大,那么做的工作将主宰上的第二次迭代“浪费”时间的时间。


Here's my advice. 这是我的建议。

  1. Before you start thinking about optimizing, have a clear understanding of the performance requirements. 在开始考虑优化之前,请对性能要求有清楚的了解。 If there are no performance requirements stated or implied, then don't waste your time on optimizing. 如果没有明示或暗示的性能要求, 请不要浪费时间进行优化。 (There is no moral imperative to make your code as fast as possible.) (没有道德上的要求使您的代码尽可能快。)

  2. It is more important to get the correct (enough) answer than to get the answer fast. 获得正确(足够)的答案比快速获得答案更重要。 So don't spend time optimizing until your code is written and tested. 因此,在编写和测试代码之前,不要花时间进行优化。

  3. Build yourself a benchmark (using realistic input data, etc) that you can use to judge whether the code is running fast enough, and make before-and-after comparisons of your candidate optimizations. 建立一个基准(使用实际的输入数据等),您可以使用该基准来判断代码是否运行得足够快,并对候选优化进行前后比较。 (Beware of the standard traps when benchmarking Java code.) (在对Java代码进行基准测试时,请注意标准陷阱。)

  4. Use profiling to decide the parts of your code where it is worthwhile to optimize. 使用性能分析来确定代码中值得优化的部分。 Look for the hotspots; 寻找热点; ie the methods / sections where most of the time is spent. 即大部分时间都用在的方法/部分。 (Optimizing code that is not a hotspot is unlikely to make a significant difference to overall application performance.) (优化不是热点的代码不太可能整体应用程序性能产生重大影响。)

  5. When you reach your goal, or when you run out of hotspots ... stop . 当您达到目标或热点用尽时,请停止

For this particular use case, you can run micro-benchmark to compare different approaches of code which implements the same logic and choose the best one to use. 对于此特定用例,您可以运行微基准测试,以比较实现相同逻辑的不同代码方法,并选择最佳的方法来使用。

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

When I run the code using jmh (ie Java Microbenchmarking Harness) tool. 当我使用jmh(即Java Microbenchmarking Harness)工具运行代码时。 Following result being produced; 产生以下结果;

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

Be aware of other factors that can impact the results. 注意可能影响结果的其他因素。

There are too many variables to tell you which to use for best performance, since that can vary depending on what's going on. 变量太多,无法告诉您应使用哪个变量来获得最佳性能,因为变量会根据情况而变化。 There is no one way that will work best in all situations. 没有一种方法可以在所有情况下都发挥最佳作用。

Sometimes splitting up the loop will give better performance (maybe the processor's instruction cache won't need refilling several times each iteration). 有时拆分循环可以提供更好的性能(也许处理器的指令缓存不需要在每次迭代中重新填充几次)。 Sometimes combining into one loop will give better performance (maybe the processor's data cache won't need refilling n times as much as with one loop). 有时组合成一个循环会带来更好的性能(也许处理器的数据缓存不需要比一个循环多重复n次)。

Profile with real world data one way. 用真实世界数据进行配置的一种方式。 Profile with real world data another way. 用另一种方式对现实世界数据进行配置。 Use the best performing way. 使用最佳执行方式。

In general the obvious overhead is only due to the extra incrementing of the additional counters/fetching do to the extra for loops. 通常,明显的开销仅是由于额外的计数器/获取操作对额外的for循环的额外增加。 This is not significant if it is not significant for one for loop but is obviously N times the cost of one for loop. 如果对于一个for循环来说并不重要,但显然是一个for循环的成本的N倍,则这并不重要。

There also might be issues with caching and such but you shouldn't have much a problem. 缓存等也可能存在问题,但是您应该不会有太大问题。

a for loop is effectively (pseudo) for循环有效(伪)

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

so 2 loops are 所以有两个循环

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

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

Ultimately, to know EXACTLY what happens you have to profile.. but even then you don't know exactly. 最终,要确切地知道会发生什么,您必须进行概要分析。但是即使如此,您也不是很清楚。 If you are trying to milk the last 0.00001% of cpu cycles then you can worry about it, otherwise don't. 如果您尝试挤奶的最后0.00001%的CPU周期,则可以担心,否则不必担心。

If you if your work functions are not "heavy"(do very little) then there are other ways to optimize things like unrolling the loop, etc. 如果您的工作功能不是“繁重的”(做得很少),那么还有其他方法可以优化事情,例如展开循环等。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM