AnswerBun.com

Por que existe tanta diferença de performance entre stream e loops normais?

Estava lendo um artigo relacionado à performance das streams/loops, e me assustei com a diferença de performance entre a utilização de loops em relação à grandes quantidades de dados.

Resolvi realizar um teste rápido utilizando o seguinte código:

public static void main(String[] args) {
    final int limite = 10_000;

    long inicioFor = System.currentTimeMillis();

    for(int i = 0; i < limite; i++) {
        System.out.println(i);
    }

    long terminoFor = System.currentTimeMillis();

    long inicioStream = System.currentTimeMillis();

    IntStream.range(0, limite).forEach(System.out::println);

    long terminoStream = System.currentTimeMillis();

    System.out.println();
    System.out.println("Usando for: " + (terminoFor - inicioFor) );
    System.out.println("Usando stream: " + (terminoStream - inicioStream) );
}

É um código relativamente simples, porém notei uma coisa interessante:

  1. Quando o limite é definido com valor 10_000, o tempo resultante é o seguinte:

Usando for: 54
Usando stream: 72

  1. Quando o limite é definido com valor 1_000_000, a stream é relativamente mais rápida:

Usando for: 4314
Usando stream: 4202

Entendo que este é um teste simples, sem utilização de ferramentas de benchmark entre outras métricas.

Gostaria de saber o porquê da existência desta diferença de tempo no processamento utilizando streams e loops, posso não estar correto, mas parece que as streams performam melhor quanto maior a quantidade de dados (caminhando para a questão das streams infinitas).


Complementando com algumas informações colocadas pelo @Victor, tive resultados extremamente diferentes retirando as operações de IO:

  1. Quando o limite é definido com valor 10_000:

Usando for: 0
Usando stream: 36

  1. Quando o limite é definido com valor 1_000_000:

Usando for: 2
Usando stream: 43

Conforme apontado também por @Maniero, a razão se deve ser o alto custo para fazer a infraestrutura funcionar de acordo.

Stack Overflow em Português Asked by nullptr on December 31, 2020

2 Answers

2 Answers

Acabei de responder sobre isto (O LINQ é o stream do C#). O stream é uma abstração, é uma camada a mais para executar, há um custo para fazer esta infraestrutura funcionar, há chamadas de função onde não deveria ocorrer no puro, há indireções, tudo isso tem custo, então ele precisa ser mais caro que a execução pura e simples de um laço. Está executando mais coisas, tem que ser mais lento.

O stream é um método que tem um laço dentro dele e que executa um método dentro dele de forma indireta, neste exemplo é o System.out::println. Que por sinal não é uma boa forma porque ele é IO e o custo disso é tão alto que você está medindo algo como 98% do tempo o IO e não o mecanismo do laço ou do stream. Em algo que custa pouco a diferença é muito maior.

Até me impressiona a excelente otimização que o compilador Java faz, mas o teste onde dá o for mais lento certamente pegou alguma interferência, não tem como ele ser mais lento. Certamente em grande volume o custo da infraestrutura pode não impactar tanto, mas não pode ser mais rápido. E por isso que dá uma discrepância entre o seu resultado e do Victor, o dele executa muitas vezes que diminui a influência da infra do stream.

Correct answer by Maniero on December 31, 2020

Bem, fiz esse teste:

import java.util.stream.LongStream;

class Teste {
    public static void main(String[] args) {
        var limite = 50_000_000_000L;
        var x = new long[1];

        var inicioStream1 = System.currentTimeMillis();
        LongStream.range(0, limite).forEach(z -> x[0] = z);
        var terminoStream1 = System.currentTimeMillis();

        var inicioFor1 = System.currentTimeMillis();
        for (var i = 0L; i < limite; i++) {
            x[0] = i;
        }
        var terminoFor1 = System.currentTimeMillis();

        var inicioStream2 = System.currentTimeMillis();
        LongStream.range(0, limite).forEach(z -> x[0] = z);
        var terminoStream2 = System.currentTimeMillis();

        var inicioFor2 = System.currentTimeMillis();
        for (var i = 0L; i < limite; i++) {
            x[0] = i;
        }
        var terminoFor2 = System.currentTimeMillis();

        System.out.println();
        System.out.println("Usando for: " + (terminoFor1 - inicioFor1));
        System.out.println("Usando stream: " + (terminoStream1 - inicioStream1));
        System.out.println("Usando for outra vez: " + (terminoFor2 - inicioFor2));
        System.out.println("Usando stream outra vez: " + (terminoStream2 - inicioStream2));
    }
}

O resultado foi esse:

Usando for: 15169
Usando stream: 18422
Usando for outra vez: 15140
Usando stream outra vez: 18498

Quando você for fazer um bechmark de alguma coisa, lembre-se que o IO para o console é uma operação custosa. Isso significa que você nunca deve usar System.out.println ou System.out::println dentro do código que você está avaliando, porque ele vai interferir bastante com a performance daquilo que você está avaliando.

Acreditar que o System.out.println tem um custo de desempenho zero ou desprezível é um erro comum.

Answered by Victor Stafusa on December 31, 2020

Add your own answers!

Related Questions

Como gerar 10 vezes números aleatórios sem ser repetidos, com loop?

1  Asked on November 10, 2021 by thaina-lopes

 

Baixar arquivo setando o header

1  Asked on November 10, 2021 by joo-silva

   

Ancestrais em Prolog

0  Asked on November 10, 2021 by juliano

 

Vários Select Sum com parametros de tabelas relacionadas

1  Asked on November 10, 2021 by ale_moraes

   

Eu posso setar o else para fazer duas coisas?

1  Asked on November 8, 2021 by luccadgf

     

Filtrar arquivo específico para não excluir

3  Asked on November 8, 2021

     

Qual a diferença entre essas formas de execução de comando?

1  Asked on November 8, 2021 by rebeca-nonato

     

Cannot read property ‘data’ of undefined

1  Asked on November 8, 2021 by oitathi

       

Pausar um for para executar uma solicitação

1  Asked on November 7, 2021 by ricardo-jardim-braz

   

Remover números anterior a vírgula

1  Asked on November 7, 2021 by enio-amarantes

   

Retornar coluna especifica com a biblioteca csv

1  Asked on November 7, 2021 by esabin

   

Como converter UNIQUEIDENTIFIER para INT?

1  Asked on November 7, 2021 by bruno-taletti

     

Como da um display Block ao rolar a pagina

1  Asked on November 7, 2021

     

Ask a Question

Get help from others!

© 2022 AnswerBun.com. All rights reserved.