Eu queria fazer algumas medições de desempenho e comparações de simples para loops e córregos equivalentes implementações. Eu acredito que é o caso de que fluxos será um pouco mais lento do que o equivalente não-córregos código, mas eu queria ter certeza que eu estou medindo as coisas certas.
Estou incluindo toda a minha classe jmh aqui.
import java.util.ArrayList;
import java.util.List;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
@State(Scope.Benchmark)
public class MyBenchmark {
List<String> shortLengthListConstantSize = null;
List<String> mediumLengthListConstantSize = null;
List<String> longerLengthListConstantSize = null;
List<String> longLengthListConstantSize = null;
@Setup
public void setup() {
shortLengthListConstantSize = populateList(2);
mediumLengthListConstantSize = populateList(12);
longerLengthListConstantSize = populateList(300);
longLengthListConstantSize = populateList(300000);
}
private List<String> populateList(int size) {
List<String> list = new ArrayList<>();
for (int ctr = 0; ctr < size; ++ ctr) {
list.add("xxx");
}
return list;
}
@Benchmark
public long shortLengthConstantSizeFor() {
long count = 0;
for (String val : shortLengthListConstantSize) {
if (val.length() == 3) { ++ count; }
}
return count;
}
@Benchmark
public long shortLengthConstantSizeForEach() {
IntHolder intHolder = new IntHolder();
shortLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
return intHolder.value;
}
@Benchmark
public long shortLengthConstantSizeLambda() {
return shortLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
}
@Benchmark
public long shortLengthConstantSizeLambdaParallel() {
return shortLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
}
@Benchmark
public long mediumLengthConstantSizeFor() {
long count = 0;
for (String val : mediumLengthListConstantSize) {
if (val.length() == 3) { ++ count; }
}
return count;
}
@Benchmark
public long mediumLengthConstantSizeForEach() {
IntHolder intHolder = new IntHolder();
mediumLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
return intHolder.value;
}
@Benchmark
public long mediumLengthConstantSizeLambda() {
return mediumLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
}
@Benchmark
public long mediumLengthConstantSizeLambdaParallel() {
return mediumLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
}
@Benchmark
public long longerLengthConstantSizeFor() {
long count = 0;
for (String val : longerLengthListConstantSize) {
if (val.length() == 3) { ++ count; }
}
return count;
}
@Benchmark
public long longerLengthConstantSizeForEach() {
IntHolder intHolder = new IntHolder();
longerLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
return intHolder.value;
}
@Benchmark
public long longerLengthConstantSizeLambda() {
return longerLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
}
@Benchmark
public long longerLengthConstantSizeLambdaParallel() {
return longerLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
}
@Benchmark
public long longLengthConstantSizeFor() {
long count = 0;
for (String val : longLengthListConstantSize) {
if (val.length() == 3) { ++ count; }
}
return count;
}
@Benchmark
public long longLengthConstantSizeForEach() {
IntHolder intHolder = new IntHolder();
longLengthListConstantSize.forEach(s -> { if (s.length() == 3) ++ intHolder.value; } );
return intHolder.value;
}
@Benchmark
public long longLengthConstantSizeLambda() {
return longLengthListConstantSize.stream().filter(s -> s.length() == 3).count();
}
@Benchmark
public long longLengthConstantSizeLambdaParallel() {
return longLengthListConstantSize.stream().parallel().filter(s -> s.length() == 3).count();
}
public static class IntHolder {
public int value = 0;
}
}
Eu estou correndo estes em um laptop Win7. Eu não me importo sobre medidas absolutas, apenas relativa. Aqui estão os mais recentes resultados de estes:
Benchmark Mode Cnt Score Error Units
MyBenchmark.longLengthConstantSizeFor thrpt 200 2984.554 ± 57.557 ops/s
MyBenchmark.longLengthConstantSizeForEach thrpt 200 2971.701 ± 110.414 ops/s
MyBenchmark.longLengthConstantSizeLambda thrpt 200 331.741 ± 2.196 ops/s
MyBenchmark.longLengthConstantSizeLambdaParallel thrpt 200 2827.695 ± 682.662 ops/s
MyBenchmark.longerLengthConstantSizeFor thrpt 200 3551842.518 ± 42612.744 ops/s
MyBenchmark.longerLengthConstantSizeForEach thrpt 200 3616285.629 ± 16335.379 ops/s
MyBenchmark.longerLengthConstantSizeLambda thrpt 200 2791292.093 ± 12207.302 ops/s
MyBenchmark.longerLengthConstantSizeLambdaParallel thrpt 200 50278.869 ± 1977.648 ops/s
MyBenchmark.mediumLengthConstantSizeFor thrpt 200 55447999.297 ± 277442.812 ops/s
MyBenchmark.mediumLengthConstantSizeForEach thrpt 200 57381287.954 ± 362751.975 ops/s
MyBenchmark.mediumLengthConstantSizeLambda thrpt 200 15925281.039 ± 65707.093 ops/s
MyBenchmark.mediumLengthConstantSizeLambdaParallel thrpt 200 60082.495 ± 581.405 ops/s
MyBenchmark.shortLengthConstantSizeFor thrpt 200 132278188.475 ± 1132184.820 ops/s
MyBenchmark.shortLengthConstantSizeForEach thrpt 200 124158664.044 ± 1112991.883 ops/s
MyBenchmark.shortLengthConstantSizeLambda thrpt 200 18750818.019 ± 171239.562 ops/s
MyBenchmark.shortLengthConstantSizeLambdaParallel thrpt 200 474054.951 ± 1344.705 ops/s
Em uma pergunta anterior, eu confirmou que esses benchmarks parecem ser "funcionalmente equivalente" (apenas procurando olhos adicionais). Que esses números parecem estar em linha, talvez com corridas independentes destes benchmarks?
Outra coisa que eu sempre fui incerto sobre com saída JMH, é determinar exatamente o que os números representam rendimento. Por exemplo, o que o "200" na coluna "Cnt" representam exatamente? As unidades de rendimento estão em "operações por segundo", então o que faz exatamente a "operação" representam, é que a execução de uma chamada para o método de referência? Por exemplo, na última linha, que representaria 474k execuções do método de referência em um segundo.
atualização :
Faço notar que quando eu comparar o "para" com o "lambda", começando com a lista de "short" e ir para listas mais longas, a relação entre eles é muito grande, mas diminui, até que a lista "longa", onde a proporção é ainda maior do que para a lista de "short" (14%, 29%, 78% e 11%). Acho isso surpreendente. Eu teria esperado que a razão entre os fluxos de sobrecarga para diminuir à medida que o trabalho nas reais aumenta de lógica de negócios. Alguém tem alguma ideia sobre isso?
Por exemplo, o que o "200" na coluna "Cnt" representam exatamente?
A cnt
coluna é o número de iterações - isto é, quantas vezes por testes é repetido. Você pode controlar esse valor usando as seguintes anotações:
- Para as medições reais:
@Measurement(iterations = 10, time = 50, timeUnit = TimeUnit.MILLISECONDS)
- Para a fase de aquecimento:
@Warmup(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
Aqui iterations
é cnt
; time
representa a duração requerida de uma iteração, e timeUnit
é a unidade de medida do time
valor.
As unidades de transferência são em "operações por segundo"
Você pode controlar a saída de várias maneiras. Por exemplo, você pode alterar a unidade de medida para o tempo de utilização @OutputTimeUnit(TimeUnit.XXXX)
, para que possa obter ops / US, ops / ms
Você também pode alterar o mode
: em vez de measureing ops / hora você pode medir o "tempo médio", "tempo de amostra", etc. Você pode controlar esta via a @BenchmarkMode({Mode.AverageTime})
anotação
Então o que exatamente faz a "operação" representam, é que a execução de uma chamada para o método de referência
Então, digamos que uma iteração é de 1 segundo de duração e você começa 1000 ops / seg. Isto significa que o método benchamrk foi executada 1000 vezes.
Em outras palavras uma operação é uma execução do método de referência, a menos que você tem a @OperationsPerInvocation(XXX)
anotação, o que significa tha invocação teach dos métodos contará como operações XXX.
O erro é calculado em todas as iterações.
Mais uma dica: em vez de codificar cada tamanho possível, você pode fazer uma referência com parâmetros:
@Param({"3", "12", "300", "3000"})
private int length;
Então você pode usar que param na sua configuração:
@Setup(Level.Iteration)
public void setUp(){
populateList(length)
}