Java parallelStream() with reduce() 没有提高性能
Java parallelStream() with reduce() not improving performance
作为对Java 8 新实现的流和自动并行化的测试,我运行进行了以下简单测试:
ArrayList<Integer> nums = new ArrayList<>();
for (int i=1; i<49999999; i++) nums.add(i);
int sum=0;
double begin, end;
begin = System.nanoTime();
for (Integer i : nums) sum += i;
end = System.nanoTime();
System.out.println( "1 core: " + (end-begin) );
begin = System.nanoTime();
sum = nums.parallelStream().reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println( "8 cores: " + (end-begin) );
我认为将一系列整数相加可以充分利用所有 8 个内核,但输出如下所示:
1 core: 1.70552398E8
8 cores: 9.938507635E9
我知道 nanoTime() 在多核系统中存在问题,但我怀疑这就是这里的问题,因为我偏离了一个数量级。
我执行的操作是否简单到 reduce() 所需的开销正在克服多核的优势?
要完全正确比较此,您需要对这两个操作使用相似的开销。
ArrayList<Integer> nums = new ArrayList<>();
for (int i = 1; i < 49999999; i++)
nums.add(i);
int sum = 0;
long begin, end;
begin = System.nanoTime();
sum = nums.stream().reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println("1 core: " + (end - begin));
begin = System.nanoTime();
sum = nums.parallelStream().reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println("8 cores: " + (end - begin));
这让我着陆
1 core: 769026020
8 cores: 538805164
实际上 parallelStream()
更快。 (注意:我只有 4 个核心,但 parallelSteam()
并不总是使用你所有的核心)
另一件事是装箱和拆箱。 nums.add(i)
需要装箱,进入 Integer::sum
的所有东西需要两个 int
才能拆箱。我将此测试转换为数组以删除它:
int[] nums = new int[49999999];
System.err.println("adding numbers");
for (int i = 1; i < 49999999; i++)
nums[i - 1] = i;
int sum = 0;
System.err.println("begin");
long begin, end;
begin = System.nanoTime();
sum = Arrays.stream(nums).reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println("1 core: " + (end - begin));
begin = System.nanoTime();
sum = Arrays.stream(nums).parallel().reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println("8 cores: " + (end - begin));
这给出了一个意想不到的时机:
1 core: 68050642
8 cores: 154591290
使用常规 int 的线性化简要快得多(1-2 个数量级),但只有并行化简时间的 1/4 左右,而且速度更慢。 我不确定为什么会这样,但这确实很有趣!
做了一些分析,结果是 fork()
并行流的方法非常昂贵,因为使用 ThreadLocalRandom
,它调用网络接口作为它的种子!这非常慢,也是 parallelStream()
比 stream()
!
慢的唯一原因
我的一些VisualVM数据:(忽略await()
时间,这是我使用的方法,所以我可以跟踪程序)
对于第一个例子:https://www.dropbox.com/s/z7qf2es0lxs6fvu/streams1.nps?dl=0
第二个例子:https://www.dropbox.com/s/f3ydl4basv7mln5/streams2.nps?dl=0
TL;DR:在您的 Integer
案例中,它看起来像是并行获胜,但是 int
案例有一些开销使并行变慢。
您的流示例对每个数字都有 2 次拆箱 (Integer.sum(int,int)
) 和一次装箱(结果 int
必须转换回 Integer
),而 for 循环有只有一个开箱。所以两者没有可比性。
当您计划使用整数进行计算时,最好使用 IntStream
:
nums.stream().mapToInt(i -> i).sum();
这会给您带来类似于 for 循环的性能。并行流在我的机器上仍然较慢。
最快的替代方法是这个顺便说一句:
IntStream.rangeClosed(0, 49999999).sum();
速度提高了一个数量级,而且没有先构建列表的开销。当然,它只是这个特殊用例的替代方案。但它表明重新思考现有方法而不是仅仅 "add a stream".
是值得的
作为对Java 8 新实现的流和自动并行化的测试,我运行进行了以下简单测试:
ArrayList<Integer> nums = new ArrayList<>();
for (int i=1; i<49999999; i++) nums.add(i);
int sum=0;
double begin, end;
begin = System.nanoTime();
for (Integer i : nums) sum += i;
end = System.nanoTime();
System.out.println( "1 core: " + (end-begin) );
begin = System.nanoTime();
sum = nums.parallelStream().reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println( "8 cores: " + (end-begin) );
我认为将一系列整数相加可以充分利用所有 8 个内核,但输出如下所示:
1 core: 1.70552398E8
8 cores: 9.938507635E9
我知道 nanoTime() 在多核系统中存在问题,但我怀疑这就是这里的问题,因为我偏离了一个数量级。
我执行的操作是否简单到 reduce() 所需的开销正在克服多核的优势?
要完全正确比较此,您需要对这两个操作使用相似的开销。
ArrayList<Integer> nums = new ArrayList<>();
for (int i = 1; i < 49999999; i++)
nums.add(i);
int sum = 0;
long begin, end;
begin = System.nanoTime();
sum = nums.stream().reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println("1 core: " + (end - begin));
begin = System.nanoTime();
sum = nums.parallelStream().reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println("8 cores: " + (end - begin));
这让我着陆
1 core: 769026020
8 cores: 538805164
实际上 parallelStream()
更快。 (注意:我只有 4 个核心,但 parallelSteam()
并不总是使用你所有的核心)
另一件事是装箱和拆箱。 nums.add(i)
需要装箱,进入 Integer::sum
的所有东西需要两个 int
才能拆箱。我将此测试转换为数组以删除它:
int[] nums = new int[49999999];
System.err.println("adding numbers");
for (int i = 1; i < 49999999; i++)
nums[i - 1] = i;
int sum = 0;
System.err.println("begin");
long begin, end;
begin = System.nanoTime();
sum = Arrays.stream(nums).reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println("1 core: " + (end - begin));
begin = System.nanoTime();
sum = Arrays.stream(nums).parallel().reduce(0, Integer::sum);
end = System.nanoTime();
System.out.println("8 cores: " + (end - begin));
这给出了一个意想不到的时机:
1 core: 68050642
8 cores: 154591290
使用常规 int 的线性化简要快得多(1-2 个数量级),但只有并行化简时间的 1/4 左右,而且速度更慢。 我不确定为什么会这样,但这确实很有趣!
做了一些分析,结果是 fork()
并行流的方法非常昂贵,因为使用 ThreadLocalRandom
,它调用网络接口作为它的种子!这非常慢,也是 parallelStream()
比 stream()
!
我的一些VisualVM数据:(忽略await()
时间,这是我使用的方法,所以我可以跟踪程序)
对于第一个例子:https://www.dropbox.com/s/z7qf2es0lxs6fvu/streams1.nps?dl=0
第二个例子:https://www.dropbox.com/s/f3ydl4basv7mln5/streams2.nps?dl=0
TL;DR:在您的 Integer
案例中,它看起来像是并行获胜,但是 int
案例有一些开销使并行变慢。
您的流示例对每个数字都有 2 次拆箱 (Integer.sum(int,int)
) 和一次装箱(结果 int
必须转换回 Integer
),而 for 循环有只有一个开箱。所以两者没有可比性。
当您计划使用整数进行计算时,最好使用 IntStream
:
nums.stream().mapToInt(i -> i).sum();
这会给您带来类似于 for 循环的性能。并行流在我的机器上仍然较慢。
最快的替代方法是这个顺便说一句:
IntStream.rangeClosed(0, 49999999).sum();
速度提高了一个数量级,而且没有先构建列表的开销。当然,它只是这个特殊用例的替代方案。但它表明重新思考现有方法而不是仅仅 "add a stream".
是值得的