Вот еще одна техника, с которой я столкнулся на днях:
Collections.nCopies(8, 1)
.stream()
.forEach(i -> System.out.println(i));
Collections.nCopiesВызов создает Listсодержащие nкопии любое значение , вы предоставляете. В данном случае это Integerзначение в рамке 1. Конечно, на самом деле он не создает список с nэлементами; он создает «виртуализированный» список, содержащий только значение и длину, и любой вызов в getпределах диапазона просто возвращает значение. Этот nCopiesметод существует с тех пор, как Collections Framework был представлен еще в 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: (Core2Duo 2,8 ГГц)
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! Я ожидал, что потребуется создать специализированный сплитератор, чтобы все работало быстро, но это уже кажется неплохим.