Предположим, что у меня есть два массива int[]
input1
и input2
. Я хочу принимать только положительные числа из первого, брать разные числа из второго, объединять их вместе, сортировать и хранить в результирующем массиве. Это можно выполнить с помощью потоков:
int[] result = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0),
Arrays.stream(input2).distinct()).sorted().toArray();
Я хочу ускорить задачу, поэтому я считаю, что поток параллелен. Обычно это означает, что я могу вставить .parallel()
где угодно между построением потока и работой терминала, и результат будет таким же. JavaDoc для IntStream.concat говорит, что результирующий поток будет параллельным, если любой из входных потоков будет параллельным. Поэтому я думал, что создание потока parallel()
потока потока input1
или потока input2
или конкатенированного потока приведет к такому же результату.
На самом деле я ошибся: если я добавлю .parallel()
к результирующему потоку, кажется, что входные потоки остаются последовательными. Кроме того, я могу пометить входные потоки (любой из них или обоих) как .parallel()
, а затем превратить результирующий поток в .sequential()
, но вход остается параллельным. Так что на самом деле существует 8 возможностей: любой из входных 1, input2 и конкатенированный поток может быть параллельным или нет:
int[] sss = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0),
Arrays.stream(input2).distinct()).sorted().toArray();
int[] ssp = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0),
Arrays.stream(input2).distinct()).parallel().sorted().toArray();
int[] sps = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0),
Arrays.stream(input2).parallel().distinct()).sequential().sorted().toArray();
int[] spp = IntStream.concat(Arrays.stream(input1).filter(x -> x > 0),
Arrays.stream(input2).parallel().distinct()).sorted().toArray();
int[] pss = IntStream.concat(Arrays.stream(input1).parallel().filter(x -> x > 0),
Arrays.stream(input2).distinct()).sequential().sorted().toArray();
int[] psp = IntStream.concat(Arrays.stream(input1).parallel().filter(x -> x > 0),
Arrays.stream(input2).distinct()).sorted().toArray();
int[] pps = IntStream.concat(Arrays.stream(input1).parallel().filter(x -> x > 0),
Arrays.stream(input2).parallel().distinct()).sequential().sorted().toArray();
int[] ppp = IntStream.concat(Arrays.stream(input1).parallel().filter(x -> x > 0),
Arrays.stream(input2).parallel().distinct()).sorted().toArray();
Benchmark (n) Mode Cnt Score Error Units
ConcatTest.SSS 100 avgt 20 7.094 ± 0.069 us/op
ConcatTest.SSS 10000 avgt 20 1542.820 ± 22.194 us/op
ConcatTest.SSS 1000000 avgt 20 350173.723 ± 7140.406 us/op
ConcatTest.SSP 100 avgt 20 6.176 ± 0.043 us/op
ConcatTest.SSP 10000 avgt 20 907.855 ± 8.448 us/op
ConcatTest.SSP 1000000 avgt 20 264193.679 ± 6744.169 us/op
ConcatTest.SPS 100 avgt 20 16.548 ± 0.175 us/op
ConcatTest.SPS 10000 avgt 20 1831.569 ± 13.582 us/op
ConcatTest.SPS 1000000 avgt 20 500736.204 ± 37932.197 us/op
ConcatTest.SPP 100 avgt 20 23.871 ± 0.285 us/op
ConcatTest.SPP 10000 avgt 20 1141.273 ± 9.310 us/op
ConcatTest.SPP 1000000 avgt 20 400582.847 ± 27330.492 us/op
ConcatTest.PSS 100 avgt 20 7.162 ± 0.241 us/op
ConcatTest.PSS 10000 avgt 20 1593.332 ± 7.961 us/op
ConcatTest.PSS 1000000 avgt 20 383920.286 ± 6650.890 us/op
ConcatTest.PSP 100 avgt 20 9.877 ± 0.382 us/op
ConcatTest.PSP 10000 avgt 20 883.639 ± 13.596 us/op
ConcatTest.PSP 1000000 avgt 20 257921.422 ± 7649.434 us/op
ConcatTest.PPS 100 avgt 20 16.412 ± 0.129 us/op
ConcatTest.PPS 10000 avgt 20 1816.782 ± 10.875 us/op
ConcatTest.PPS 1000000 avgt 20 476311.713 ± 19154.558 us/op
ConcatTest.PPP 100 avgt 20 23.078 ± 0.622 us/op
ConcatTest.PPP 10000 avgt 20 1128.889 ± 7.964 us/op
ConcatTest.PPP 1000000 avgt 20 393699.222 ± 56397.445 us/op
Из этих результатов я могу только заключить, что распараллеливание шага distinct()
снижает общую производительность (по крайней мере, в моих тестах).
Итак, у меня есть следующие вопросы:
- Существуют ли какие-либо официальные рекомендации о том, как лучше использовать распараллеливание с конкатенированными потоками? Не всегда возможно проверить все возможные комбинации (особенно при конкатенации более двух потоков), поэтому иметь некоторые "эмпирические правила" было бы неплохо.
- Кажется, что если я конкатенирую потоки, созданные непосредственно из коллекции/массива (без промежуточных операций, выполняемых до конкатенации), то результаты не так сильно зависят от местоположения
parallel()
. Это правда? - Существуют ли какие-либо другие случаи, кроме конкатенации, где результат зависит от того, в какой момент расчленен потоковый конвейер?