Ось ще одна техніка, яку я натрапив на днях:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
Collections.nCopiesВиклик створює Listмістять nкопії будь-яке значення , ви надаєте. У цьому випадку це Integerзначення в коробці 1. Звичайно, він фактично не створює список з nелементами; він створює "віртуалізований" список, що містить лише значення та довжину, і будь-який виклик в getмежах діапазону просто повертає значення. nCopiesМетод був навколо з Колекцією рамкової був введений ще в JDK 1.2. Звичайно, можливість створення потоку з його результату була додана в Java SE 8.
Велика справа, ще один спосіб зробити те ж саме приблизно в однаковій кількості рядків.
Однак ця методика швидша за IntStream.generateта IntStream.iterateнаближається, і на диво, вона також швидша за IntStream.rangeпідхід.
Бо iterateі generateрезультат, мабуть, не надто дивний. Рамка потоків (насправді сплітератори для цих потоків) побудована на припущенні, що лямбди потенційно можуть генерувати різні значення кожного разу, і що вони будуть генерувати необмежену кількість результатів. Це робить паралельне розщеплення особливо складним. iterateМетод також проблематичний для цього випадку , тому що кожен виклик вимагає результату попередньої. Таким чином, потоки, що використовують generateі iterateне дуже добре для отримання повторних констант.
Відносно погані показники роботи rangeдивують. Це теж віртуалізується, тому елементи насправді не всі існують у пам'яті, а розмір відомий наперед. Це повинно створити швидкий і легко паралельний сплітератор. Але це дивно не дуже добре. Можливо, причина полягає в тому, що rangeнеобхідно обчислити значення для кожного елемента діапазону, а потім викликати функцію на ньому. Але ця функція просто ігнорує вхід і повертає константу, тому я здивований, що це не вбудовано і не вбито.
Collections.nCopiesТехніка повинна зробити бокс / розпакування для того , щоб обробляти значення, так як немає примітивних спеціалізацій List. Оскільки значення є однаковим щоразу, воно в основному є одним кодом, і це поле ділиться всіма nкопіями. Я підозрюю, що бокс / розблокування дуже оптимізований, навіть непростий, і це може бути добре накреслено.
Ось код:
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();
}
Ось результати JMH: (2,8 ГГц 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
У версії ncopies є досить велика дисперсія, але в цілому вона здається комфортно на 20 разів швидшою, ніж версія діапазону. (Я б дуже хотів вірити, що я щось не так зробив.)
Я здивований тим, наскільки добре nCopiesпрацює техніка. Всередині він не особливо особливий, при цьому потік віртуалізованого списку просто реалізується за допомогою IntStream.range! Я очікував, що потрібно буде створити спеціалізований розширювач, щоб це швидко пройшло, але це вже здається досить непоганим.