Aqui está outra técnica que usei outro dia:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
A Collections.nCopies
chamada cria um List
contendo n
cópias de qualquer valor que você fornece. Nesse caso, é o Integer
valor 1 na caixa . É claro que, na verdade, ele não cria uma lista com n
elementos; ele cria uma lista "virtualizada" que contém apenas o valor e o comprimento, e qualquer chamada para get
dentro do intervalo apenas retorna o valor. onCopies
método existe desde que o Collections Framework foi introduzido no JDK 1.2. Obviamente, a capacidade de criar um fluxo a partir de seu resultado foi adicionada ao Java SE 8.
Grande coisa, outra maneira de fazer a mesma coisa com aproximadamente o mesmo número de linhas.
No entanto, esta técnica é mais rápido do que o IntStream.generate
e IntStream.iterate
se aproxima e, surpreendentemente, é também mais rápido do que a IntStream.range
abordagem.
Pois iterate
e generate
o resultado talvez não seja muito surpreendente. A estrutura de fluxos (na verdade, os divisores para esses fluxos) é construída na suposição de que os lambdas irão potencialmente gerar valores diferentes a cada vez e que eles irão gerar um número ilimitado de resultados. Isso torna a divisão paralela particularmente difícil. O iterate
método também é problemático para este caso porque cada chamada requer o resultado da anterior. Portanto, os fluxos usando generate
e iterate
não funcionam muito bem para gerar constantes repetidas.
O desempenho relativamente baixo de range
é surpreendente. Isso também é virtualizado, de modo que nem todos os elementos existem realmente na memória e o tamanho é conhecido desde o início. Isso deve resultar em um divisor de fácil paralelização rápido e fácil. Mas, surpreendentemente, não foi muito bem. Talvez a razão seja que range
tem que calcular um valor para cada elemento do intervalo e então chamar uma função sobre ele. Mas essa função simplesmente ignora sua entrada e retorna uma constante, então estou surpreso que isso não seja embutido e eliminado.
A Collections.nCopies
técnica tem que fazer boxing / unboxing para lidar com os valores, já que não existem especializações primitivas de List
. Como o valor é o mesmo todas as vezes, é basicamente embalado uma vez e essa caixa é compartilhada por todas as n
cópias. Eu suspeito que o boxing / unboxing é altamente otimizado, até mesmo intrinsecado, e pode ser bem embutido.
Aqui está o código:
public static final int LIMIT = 500_000_000;
public static final long VALUE = 3L;
public long range() {
return
LongStream.range(0, LIMIT)
.parallel()
.map(i -> VALUE)
.map(i -> i % 73 % 13)
.sum();
}
public long ncopies() {
return
Collections.nCopies(LIMIT, VALUE)
.parallelStream()
.mapToLong(i -> i)
.map(i -> i % 73 % 13)
.sum();
}
E aqui estão os resultados do JMH: (2.8 GHz Core2Duo)
Benchmark Mode Samples Mean Mean error Units
c.s.q.SO18532488.ncopies thrpt 5 7.547 2.904 ops/s
c.s.q.SO18532488.range thrpt 5 0.317 0.064 ops/s
Existe uma grande variação na versão ncopies, mas no geral parece confortavelmente 20x mais rápida do que a versão range. (Eu estaria bastante disposto a acreditar que fiz algo errado, no entanto.)
Estou surpreso com o quão bem a nCopies
técnica funciona. Internamente, ele não faz muita coisa especial, com o fluxo da lista virtualizada simplesmente sendo implementado usando IntStream.range
! Eu esperava que fosse necessário criar um divisor especializado para fazer isso funcionar rápido, mas já parece estar muito bom.