Existe uma diferença de desempenho entre um loop for e um loop for-each?

184

Qual é, se houver, a diferença de desempenho entre os dois loops a seguir?

for (Object o: objectArrayList) {
    o.DoSomething();
}

e

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}
eflles
fonte
@Keparo: É um "para cada" loop não um "para-in" laço
Ande Turner
em java é chamado "for each", mas quando se trata de objetivo C é chamado "for In".
DamithH
O desempenho estendido para loop é discutido aqui: stackoverflow.com/questions/12155987/…
eckes
Mesmo que houvesse uma diferença, seria tão pequeno que você deveria favorecer a legibilidade, a menos que esse trecho de código seja executado trilhões de vezes por segundo. E então você precisaria de uma referência adequada para evitar a otimização prematura de qualquer maneira.
Zabuzard

Respostas:

212

Do item 46 em Java efetivo de Joshua Bloch:

O loop for-each, introduzido no release 1.5, elimina a desordem e a oportunidade de erro ocultando completamente o iterador ou a variável de índice. O idioma resultante se aplica igualmente a coleções e matrizes:

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}

Quando vir os dois pontos (:), leia-os como "dentro". Assim, o loop acima é lido como "para cada elemento e em elementos". Observe que não há penalidade no desempenho pelo uso do loop for-each, mesmo para matrizes. De fato, ele pode oferecer uma pequena vantagem de desempenho em relação a um loop for comum em algumas circunstâncias, pois calcula o limite do índice da matriz apenas uma vez. Enquanto você pode fazer isso manualmente (Item 45), os programadores nem sempre o fazem.

Vijay Dev
fonte
48
Vale a pena mencionar que, em um para cada loop-não há nenhuma maneira de aceder a um contador do índice (uma vez que não existe)
basszero
Sim, mas esse contador agora está visível fora do loop. Claro, é uma solução simples, mas o mesmo vale para cada um!
Indolering 04/11/12
74
Há uma penalidade de desempenho ao alocar o iterador. Eu tinha um código altamente paralelo em um papel de parede ao vivo no Android. Vi que o coletor de lixo estava ficando louco. Isso porque cada loop estava alocando iteradores temporários em muitos segmentos diferentes (de curta duração), causando muito trabalho ao coletor de lixo. A mudança para loops regulares baseados em índice corrigiu o problema.
precisa saber é o seguinte
1
@ gsingh2011 Mas isso também depende se você está usando uma lista de acesso aleatório ou não. Usar o acesso baseado em índice a listas de acesso não aleatórias será muito pior do que usar para cada uma com listas de acesso aleatórias, eu acho. Se você estiver trabalhando com a interface List e, portanto, não souber o tipo de implementação real, poderá verificar se a lista é uma instância de (implementa) RandomAccess, se você realmente se importa: docs.oracle.com/javase/8 /docs/api/java/util/RandomAccess.html
Puce
3
@ gsingh2011 Os documentos do Android ( developer.android.com/training/articles/perf-tips.html#Loops ) mencionam que você terá uma penalidade de desempenho ao usar o foreach somente em um ArrayList, não em outras coleções. Estou curioso para saber se foi esse o seu caso.
Viccari
29

Todos esses loops fazem exatamente o mesmo, eu só quero mostrá-los antes de jogar meus dois centavos.

Primeiro, a maneira clássica de percorrer a Lista:

for (int i=0; i < strings.size(); i++) { /* do something using strings.get(i) */ }

Segundo, a maneira preferida, pois é menos suscetível a erros (quantas vezes você fez o "oops, misturou as variáveis ​​iej nesses loops dentro dos loops")?).

for (String s : strings) { /* do something using s */ }

Terceiro, o micro-otimizado para loop:

int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }

Agora, os dois centavos reais: pelo menos quando eu estava testando, o terceiro foi o mais rápido ao contar milissegundos em quanto tempo levou para cada tipo de loop com uma operação simples repetida alguns milhões de vezes - isso estava usando o Java 5 com jre1.6u10 no Windows, caso alguém esteja interessado.

Embora pelo menos pareça ser o terceiro mais rápido, você realmente deve se perguntar se deseja correr o risco de implementar essa otimização do olho mágico em todo o código do loop, pelo que eu vi, o loop real não é ' Geralmente, é a parte mais demorada de qualquer programa real (ou talvez eu esteja apenas trabalhando no campo errado, quem sabe). E também como eu mencionei no pretexto para o Java loop for-each (alguns se referem a ele como loop Iterator e outros como loop for-in ), é menos provável que você bata nesse bug estúpido em particular ao usá-lo. E antes de debater como isso pode até ser mais rápido do que os outros, lembre-se de que o javac não otimiza o bytecode de forma alguma (bem, quase de qualquer maneira), apenas o compila.

Se você gosta de micro-otimização e / ou seu software usa muitos loops recursivos e outros, então você pode estar interessado no terceiro tipo de loop. Lembre-se de avaliar bem seu software antes e depois de alterar os loops for, para esse estranho e micro-otimizado.

P Arrayah
fonte
5
Observe que o loop for com ++ i <= size é "baseado em 1", por exemplo, o método get dentro do loop será chamado para os valores 1, 2, 3 etc.
volley
15
Uma maneira melhor de escrever o loop micro-otimizado é para (int i = 0, size = strings.size (); ++ i <= size;) {} Isso é preferível porque minimiza o escopo do tamanho
Dónal
1
o terceiro não começa com i = 1 na primeira vez que passa pelo loop, pulando o primeiro elemento. e ser um loop for é desnecessário. int n = strings.length; while (n -> 0) {System.out.println ("" + n + "" + seqüências de caracteres [n]); }
Lassi Kinnunen
1
@ Dónal esse padrão de loop perde o primeiro e dá um IOOBE. Este funciona: para (int i = -1, tamanho = list.size (); i ++ <tamanho;)
Nathan Adams
1
"Todos esses loops fazem exatamente o mesmo" está incorreto. Um usa acesso aleatório get(int), o outro usa um Iterator. Considere LinkedListonde o desempenho de for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }é muito pior, uma vez que está ocorrendo get(int)n vezes.
Steve Kuo
13

O loop for-each geralmente deve ser preferido. A abordagem "get" pode ser mais lenta se a implementação da lista que você está usando não suportar acesso aleatório. Por exemplo, se um LinkedList for usado, você incorreria em um custo transversal, enquanto a abordagem for-each usa um iterador que monitora sua posição na lista. Mais informações sobre as nuances do loop for-each .

Eu acho que o artigo está aqui agora: nova localização

O link mostrado aqui estava morto.

Zach Scrivena
fonte
12

Bem, o impacto no desempenho é praticamente insignificante, mas não é zero. Se você observar o JavaDoc da RandomAccessinterface:

Como regra geral, uma implementação de Lista deve implementar essa interface se, para instâncias típicas da classe, esse loop:

for (int i=0, n=list.size(); i < n; i++)
    list.get(i);

roda mais rápido que esse loop:

for (Iterator i=list.iterator(); i.hasNext();)
      i.next();

E para cada loop está usando a versão com o iterador, ArrayListpor exemplo, para cada loop não é mais rápido.

Sarmun
fonte
Realmente cada um? Mesmo um com uma matriz? Eu li aqui stackoverflow.com/questions/1006395/… que não envolve um iterador.
Ondra Žižka
@ OndraŽižka: O loop for-each usa iteradores ao repetir Iterables, mas não matrizes. Para matrizes, um loop for com uma variável de índice é usado. Há informações sobre isso no JLS .
Lii 26/01
Sim, então você precisa primeiro criá-lo em uma matriz com o toArray, tendo um custo.
Lassi Kinnunen
6

Parece haver uma diferença, infelizmente.

Se você observar o código de bytes gerado para os dois tipos de loops, eles serão diferentes.

Aqui está um exemplo do código fonte do Log4j.

Em /log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java, temos uma classe interna estática chamada Log4jMarker, que define:

    /*
     * Called from add while synchronized.
     */
    private static boolean contains(final Marker parent, final Marker... localParents) {
        //noinspection ForLoopReplaceableByForEach
        for (final Marker marker : localParents) {
            if (marker == parent) {
                return true;
            }
        }
        return false;
    }

Com laço padrão:

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: iconst_0
       1: istore_2
       2: aload_1
       3: arraylength
       4: istore_3
       5: iload_2
       6: iload_3
       7: if_icmpge     29
      10: aload_1
      11: iload_2
      12: aaload
      13: astore        4
      15: aload         4
      17: aload_0
      18: if_acmpne     23
      21: iconst_1
      22: ireturn
      23: iinc          2, 1
      26: goto          5
      29: iconst_0
      30: ireturn

Com para cada um:

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: aload_1
       1: astore_2
       2: aload_2
       3: arraylength
       4: istore_3
       5: iconst_0
       6: istore        4
       8: iload         4
      10: iload_3
      11: if_icmpge     34
      14: aload_2
      15: iload         4
      17: aaload
      18: astore        5
      20: aload         5
      22: aload_0
      23: if_acmpne     28
      26: iconst_1
      27: ireturn
      28: iinc          4, 1
      31: goto          8
      34: iconst_0
      35: ireturn

O que há com esse Oracle?

Eu tentei isso com Java 7 e 8 no Windows 7.

Gary Gregory
fonte
7
Para aqueles que estão tentando ler a desmontagem, o resultado final é que o código gerado dentro do loop é idêntico, mas a configuração para cada parece ter criado uma variável temporária extra contendo uma referência ao segundo argumento. Se a variável oculta extra for registrada, mas o parâmetro em si não estiver durante a geração do código, o for-each seria mais rápido; se o parâmetro for registrado no exemplo for (;;), o tempo de execução seria idêntico. Tem referência?
Robin Davies
4

É sempre melhor usar o iterador em vez de indexar. Isso ocorre porque o iterador provavelmente é otimizado para a implementação da Lista, enquanto o indexado (chamando get) pode não ser. Por exemplo, LinkedList é uma lista, mas a indexação através de seus elementos será mais lenta que a iteração usando o iterador.

Steve Kuo
fonte
10
Eu acho que não existe "sempre" nas otimizações de desempenho.)
eckes
4

O foreach torna a intenção do seu código mais clara e, normalmente, é preferível a uma melhoria muito pequena da velocidade - se houver.

Sempre que vejo um loop indexado, tenho que analisá-lo um pouco mais para garantir que ele faça o que penso que faz. Por exemplo, começa do zero, inclui ou exclui o ponto final etc.?

Parece que passo a maior parte do tempo lendo código (que escrevi ou que alguém escreveu) e a clareza é quase sempre mais importante que o desempenho. Hoje em dia é fácil descartar o desempenho porque o Hotspot faz um trabalho incrível.

Fortyrunner
fonte
4

O código a seguir:

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

interface Function<T> {
    long perform(T parameter, long x);
}

class MyArray<T> {

    T[] array;
    long x;

    public MyArray(int size, Class<T> type, long x) {
        array = (T[]) Array.newInstance(type, size);
        this.x = x;
    }

    public void forEach(Function<T> function) {
        for (T element : array) {
            x = function.perform(element, x);
        }
    }
}

class Compute {
    int factor;
    final long constant;

    public Compute(int factor, long constant) {
        this.factor = factor;
        this.constant = constant;
    }

    public long compute(long parameter, long x) {
        return x * factor + parameter + constant;
    }
}

public class Main {

    public static void main(String[] args) {
        List<Long> numbers = new ArrayList<Long>(50000000);
        for (int i = 0; i < 50000000; i++) {
            numbers.add(i * i + 5L);
        }

        long x = 234553523525L;

        long time = System.currentTimeMillis();
        for (int i = 0; i < numbers.size(); i++) {
            x += x * 7 + numbers.get(i) + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        time = System.currentTimeMillis();
        for (long i : numbers) {
            x += x * 7 + i + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        numbers = null;
        MyArray<Long> myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return x * 8 + parameter + 5L;
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
        myArray = null;
        myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return new Compute(8, 5).compute(parameter, x);
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
    }
}

Dá a seguinte saída no meu sistema:

224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895

Estou executando o Ubuntu 12.10 alpha com OracleJDK 1.7, atualização 6.

Em geral, o HotSpot otimiza muitos indirecionamentos e operações reduntantes simples; portanto, em geral, você não deve se preocupar com eles, a menos que haja muitos deles em seqüência ou estejam aninhados.

Por outro lado, o índice indexado no LinkedList é muito mais lento do que chamar o LinkedList no próximo iterador, para evitar o impacto no desempenho, mantendo a legibilidade ao usar os iteradores (explícita ou implicitamente em cada loop).

Wibowit
fonte
3

Mesmo com algo como um ArrayList ou Vector, em que "get" é uma simples pesquisa de array, o segundo loop ainda possui sobrecarga adicional que o primeiro não. Eu esperaria que fosse um pouco mais lento que o primeiro.

Paul Tomblin
fonte
O primeiro loop também precisa obter cada elemento. Ele cria um iterador nos bastidores para fazer isso. Eles são realmente equivalentes.
Bill o Lagarto
Pensando em termos de C, um iterador pode apenas incrementar um ponteiro, mas um get precisaria multiplicar o valor de i pela largura de um ponteiro a cada vez.
Paul Tomblin 02/11/08
Depende do tipo de lista que você usa. Eu acho que você está certo, porém, usar get nunca seria mais rápido e, às vezes, mais lento.
Bill the Lizard
3

A única maneira de saber com certeza é compará-la, e mesmo isso não é tão simples quanto parece . O compilador JIT pode fazer coisas muito inesperadas no seu código.

Jouni K. Seppänen
fonte
3

Aqui está uma breve análise da diferença apresentada pela equipe de desenvolvimento do Android:

https://www.youtube.com/watch?v=MZOf3pOAM6A

O resultado é que não é uma diferença, e em ambientes muito contido com listas muito grandes que poderia ser uma diferença notável. Nos testes, o ciclo de cada loop levou o dobro do tempo. No entanto, o teste foi realizado em uma lista de matrizes de 400.000 números inteiros. A diferença real por elemento na matriz era de 6 microssegundos . Eu não testei e eles não disseram, mas esperaria que a diferença fosse um pouco maior usando objetos em vez de primitivos, mas mesmo assim, a menos que você esteja construindo um código de biblioteca em que não tenha idéia da escala do que será solicitado para repetir, acho que não vale a pena enfatizar a diferença.

TBridges42
fonte
2

Pelo nome da variável objectArrayList, presumo que seja uma instância dejava.util.ArrayList . Nesse caso, a diferença de desempenho seria imperceptível.

Por outro lado, se for um exemplo java.util.LinkedList, a segunda abordagem será muito mais lenta que aList#get(int) é uma operação O (n).

Portanto, a primeira abordagem é sempre preferida, a menos que o índice seja necessário pela lógica no loop.

Changgeng
fonte
1
1. for(Object o: objectArrayList){
    o.DoSomthing();
}
and

2. for(int i=0; i<objectArrayList.size(); i++){
    objectArrayList.get(i).DoSomthing();
}

Ambos fazem o mesmo, mas para um uso fácil e seguro da programação de cada um, há possibilidades de propensão a erros na segunda maneira de usar.

Santosh
fonte
1

É estranho que ninguém tenha mencionado o óbvio - o foreach aloca memória (na forma de um iterador), enquanto um loop for normal não aloca nenhuma memória. Para jogos no Android, isso é um problema, porque significa que o coletor de lixo será executado periodicamente. Em um jogo, você não quer que o coletor de lixo funcione ... NUNCA. Portanto, não use loops foreach no método draw (ou render).

neural
fonte
1

A resposta aceita responde à pergunta, além do caso excepcional de ArrayList ...

Como a maioria dos desenvolvedores confia no ArrayList (pelo menos acredito que sim)

Portanto, sou obrigado a adicionar a resposta correta aqui.

Diretamente da documentação do desenvolvedor: -

O loop for aprimorado (também conhecido como loop "for-each") pode ser usado para coleções que implementam a interface Iterável e para matrizes. Com coleções, um iterador é alocado para fazer chamadas de interface para hasNext () e next (). Com um ArrayList, um loop contado manuscrito é cerca de 3x mais rápido (com ou sem JIT), mas para outras coleções, a sintaxe aprimorada para loop será exatamente equivalente ao uso explícito do iterador.

Existem várias alternativas para iterar através de uma matriz:

static class Foo {
    int mSplat;
}

Foo[] mArray = ...

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
        sum += mArray[i].mSplat;
    }
}

public void one() {
    int sum = 0;
    Foo[] localArray = mArray;
    int len = localArray.length;

    for (int i = 0; i < len; ++i) {
        sum += localArray[i].mSplat;
    }
}

public void two() {
    int sum = 0;
    for (Foo a : mArray) {
        sum += a.mSplat;
    }
}

zero () é o mais lento, porque o JIT ainda não pode otimizar o custo de obter o comprimento da matriz uma vez para cada iteração no loop.

um () é mais rápido. Ele puxa tudo para as variáveis ​​locais, evitando as pesquisas. Somente o comprimento da matriz oferece um benefício de desempenho.

two () é mais rápido para dispositivos sem um JIT e indistinguível de um () para dispositivos com um JIT. Ele usa a sintaxe aprimorada para loop introduzida na versão 1.5 da linguagem de programação Java.

Portanto, você deve usar o loop for aprimorado por padrão, mas considere um loop contado manuscrito para a iteração ArrayList de desempenho crítico.

Sreekanth Karumanaghat
fonte
-2

Sim, a for-eachvariante é mais rápida que o normal index-based-for-loop.

for-eachusos variantes iterator. Assim, o deslocamento é mais rápido que o forloop normal, que é baseado em índices.
Isso ocorre porque eles iteratorsão otimizados para percorrer, porque estão apontando antes do próximo elemento e logo após o elemento anterior . Uma das razões para index-based-for-loopser lento é que, é necessário calcular e mover para a posição do elemento sempre que não estiver com o iterator.

cse
fonte
-3
public class FirstJavaProgram {

    public static void main(String[] args) 
    {
        int a[]={1,2,3,45,6,6};

// Method 1: this is simple way to print array 

        for(int i=0;i<a.length;i++) 
        { 
            System.out.print(a[i]+" ");
        }

// Method 2: Enhanced For loop

        for(int i:a)
        {
            System.out.print(i+" ");
        }
    }
}
SS Tiwari
fonte