Java 遍历ArrayList的干净且高效的方法

Java 遍历ArrayList的干净且高效的方法,java,arrays,performance,arraylist,Java,Arrays,Performance,Arraylist,首先,请注意,关于stackoverflow已经有很多类似的问题,例如。然而,我想强调问题的另一个方面,在这些讨论中我看不到答案: 在ArrayList上执行三种迭代变量的基准测试(特别是ArrayList,而不是其他List实现): 备选案文1: int size = list.size(); for(int i = 0; i < size; i++) doSomething(list.get(i)); 备选案文3: list.forEach(this::doSomething

首先,请注意,关于stackoverflow已经有很多类似的问题,例如。然而,我想强调问题的另一个方面,在这些讨论中我看不到答案:

ArrayList
上执行三种迭代变量的基准测试(特别是
ArrayList
,而不是其他
List
实现):

备选案文1:

int size = list.size();
for(int i = 0; i < size; i++)
    doSomething(list.get(i));
备选案文3:

list.forEach(this::doSomething);
事实证明,在我的机器上,1号和3号的表现是一样的,而2号则需要大约55%的时间。这证实了一种直觉,即在No.2中隐式创建的
迭代器
在每次迭代中比传统的数组循环(已有数十年的编译器优化研究)做更多的工作。变型3当然在内部执行与变型1相同的循环

现在我的实际问题是:假设变体3由于某种原因不适用(循环中的副作用或没有Java 8),那么在Java中,在
列表上循环最干净的方式是什么?如讨论中所述,对于其他
列表
s,变体1的效率可能比
数组列表
低得多。记住这一点,我们可以这样做:

if(list instanceof ArrayList)
    doVariant1(list);
else
    doVariant2(list);
不幸的是,这有点像引入样板代码。那么,对于迭代
列表
,您会推荐什么样的干净、高性能的解决方案呢

编辑:以下是那些想要重现结果的人的完整代码。我使用的是JRE 1.8.0,JavaHotspot服务器虚拟机在MacOS 10上构建25.0-b70,采用2 GHz Intel Core i7

private static final int ARRAY_SIZE = 10_000_000;
private static final int WARMUP_COUNT = 100;
private static final int TEST_COUNT = 100 + (int)(Math.random() + 20);

public void benchmark()
{
    for(int i = 0; i < WARMUP_COUNT; i++)
        test();
    long totalTime = 0;
    for(int i = 0; i < TEST_COUNT; i++)
        totalTime += test();
    System.out.println(totalTime / TEST_COUNT);
}

private long test()
{
    ArrayList<Object> list = new ArrayList<>(ARRAY_SIZE);
    for(int i = 0; i < ARRAY_SIZE; i++)
        list.add(null);
    long t = System.nanoTime();
    doLoop(list);
    return System.nanoTime() - t;
}

private void doLoop(ArrayList<Object> list)
{   // replace with the variant to test.
    for(Object element : list)
        doSomething(element);
}

private void doSomething(Object element)
{
    Objects.nonNull(element);
}
private static final int ARRAY\u SIZE=10\u 000\u 000;
专用静态最终整备预热计数=100;
私有静态最终整数测试计数=100+(整数)(Math.random()+20);
公共服务基准()
{
对于(int i=0;i
您真的需要这种微性能吗?因为你要求的是“最干净”的方式,所以2)和(3)是最好的。而且,(2)和(3)总体上有更好的性能,因为具体类将提供自己最合适的迭代器,而这通常是最好的迭代器

(2) 对于基于数组的列表具有良好的性能,但对于其他列表(例如LinkedList)则不好。但是,即使使用ArrayList,大多数实际应用程序在(1)和(2)/(3)之间的性能上也不会有太大的差异,因为大部分CPU时间都花在doSomething()上,而不是循环列表

  • 您的“黑洞”方法
    doSomething
    ,它应该利用迭代的值,但什么都不做,并且很容易成为死代码消除优化的目标。当我试图重现您的结果时,在
    indexedFor
    的情况下,我每次运行的时间只有125纳秒,而在其他两种情况下只有3-6毫秒

  • 修复后(通过增加一个
    静态int计数器并在最后打印它),“增强型for”变量的速度是其他两个变量的两倍,一般范围为每次运行5-9毫秒。为了保持透视效果,这是每个元素0.5-0.9纳秒

  • 测试的相关性实际上是没有的,因为它试图测量迭代的纯开销,而不实际处理元素。实际上没有元素,只有空值

  • 如果我们使用如下所示的修改代码,它对每个元素所做的工作最少(取消引用其
    int
    字段并将其添加到计数器),则速度差异几乎消失:“增强型”为9.5毫秒,而其他两种情况为8毫秒。要编写一个有用的程序,使这种差异真正起作用几乎是不可能的

  • 总而言之:这篇文章不会给出“最佳成语”的建议。选择符合其他标准的成语,如可读性和简洁性。由于需要额外的
    i
    变量和检查来确保您处理的实现实际上从中受益,因此索引for循环是最复杂的一个


    private static final int ARRAY\u SIZE=10\u 000\u 000;
    专用静态最终整备预热计数=100;
    私有静态最终整数测试计数=100+(整数)(Math.random()+20);
    专用静态整数计数器;
    公共静态无效基准(){
    对于(int i=0;i
    这是一个很好的观点,这个问题在e中可能相关性很低
    private static final int ARRAY_SIZE = 10_000_000;
    private static final int WARMUP_COUNT = 100;
    private static final int TEST_COUNT = 100 + (int)(Math.random() + 20);
    
    public void benchmark()
    {
        for(int i = 0; i < WARMUP_COUNT; i++)
            test();
        long totalTime = 0;
        for(int i = 0; i < TEST_COUNT; i++)
            totalTime += test();
        System.out.println(totalTime / TEST_COUNT);
    }
    
    private long test()
    {
        ArrayList<Object> list = new ArrayList<>(ARRAY_SIZE);
        for(int i = 0; i < ARRAY_SIZE; i++)
            list.add(null);
        long t = System.nanoTime();
        doLoop(list);
        return System.nanoTime() - t;
    }
    
    private void doLoop(ArrayList<Object> list)
    {   // replace with the variant to test.
        for(Object element : list)
            doSomething(element);
    }
    
    private void doSomething(Object element)
    {
        Objects.nonNull(element);
    }
    
    private static final int ARRAY_SIZE = 10_000_000;
    private static final int WARMUP_COUNT = 100;
    private static final int TEST_COUNT = 100 + (int)(Math.random() + 20);
    
    private static int counter;
    
    public static void benchmark() {
        for (int i = 0; i < WARMUP_COUNT; i++)
            test();
        long totalTime = 0;
        for (int i = 0; i < TEST_COUNT; i++)
            totalTime += test();
        System.out.println(totalTime / TEST_COUNT);
        System.out.println("Nonnull count: " + counter);
    }
    
    private static long test() {
        final ArrayList<Integer> list = new ArrayList<>(ARRAY_SIZE);
        for (int i = 0; i < ARRAY_SIZE; i++)
            list.add(i % 256 - 128);
        final long start = System.nanoTime();
        forEach(list);
        return System.nanoTime() - start;
    }
    
    private static void indexedFor(ArrayList<Integer> list) {
        for (int i = 0; i < list.size(); i++) {
            doSomething(list.get(i));
        }
    }
    
    private static void enhancedFor(ArrayList<Integer> list) {
        for (Integer element : list)
            doSomething(element);
    }
    
    private static void forEach(ArrayList<Integer> list) {
        list.forEach(Testing::doSomething);
    }
    
    private static void doSomething(Integer element) {
        counter += element.intValue();
    }
    
    public static void main(String[] args) {
        benchmark();
    }