java 8新特性--数据并行化
1.并行与并发
并发:不同任务共享一个CPU的时间段。比如一个CPU分配不同的时间片。
并行:一个任务共享不同CPU的时间段。比如一个任务由多个CPU执行。
2.为什么选择并行化
现在的芯片的处理速度达到GHz,超级计算机的处理速度更快。
芯片的速度发展速度变慢,芯片的核数发展增加。
可以预计,在未来一段时间内,通过增加芯片的核心数的方式增加芯片的处理速度将是一个主流。
3.并行化流操作
parallelStream 从一个集合创建一个具有并行能力的流
@Test
public void test1() {
LongStream idStream = getUsers().parallelStream()
.mapToLong(User::getId).peek(System.out::println);
System.out.println(idStream.sum());
}
73
76
40
63
68
7
79
9
35
13
463
parallel 使一个流具有并行能力
@Test
public void test2() {
Stream<User> stream = getUsers().stream();
System.out.println(stream.parallel().mapToLong(User::getId)
.peek(System.out::println).sum());
}
56
27
64
83
18
40
70
11
12
80
461
4.一些小例子
4.1进行1亿次骰子模拟
@Test
public void parallelDiceRolls() {
final int N = 100000000;
double fraction = 1.0 / N;
Map<Integer, Double> map = IntStream
.range(0, N)
.parallel()
.mapToObj(twoDiceThrows())
.collect(
Collectors.groupingBy(side -> side,
Collectors.summingDouble(n -> fraction)));
map.forEach((k, v) -> System.out.println(k + ":" + v));
}
private IntFunction<Integer> twoDiceThrows() {
return i -> {
ThreadLocalRandom random = ThreadLocalRandom.current();
int firstThrow = random.nextInt(1, 7);
int secondThrow = random.nextInt(1, 7);
return firstThrow + secondThrow;
};
}
2:0.0277587
3:0.055531779999999996
4:0.08332411
5:0.11111029
6:0.1389104
7:0.16665778000000003
8:0.13886200999999998
9:0.11119159999999999
10:0.08331452
11:0.055548310000000004
12:0.027790500000000003
4.2简化模拟
@Test
public void test3() {
final int N = 100000000;
double fraction = 1.0 / N;
Map<Object, Double> map = IntStream
.range(0, N)
.parallel()
.mapToObj(i -> {
ThreadLocalRandom random = ThreadLocalRandom.current();
return random.nextInt(1, 13);
})
.collect(
Collectors.groupingBy(s -> s,
Collectors.summingDouble(n -> fraction)));
map.forEach((k, v) -> System.out.println(k + ":" + v));
}
1:0.08336189000000001
2:0.08329647999999999
3:0.08336718
4:0.08335002000000001
5:0.08334555
6:0.08334459999999999
7:0.08332626
8:0.08331256000000001
9:0.08328557
10:0.08335501000000002
11:0.08329951000000001
12:0.08335537
还是很快的。
4.3不使用并行处理
这个例子是个反例,不推荐
@Test
public void method(){
long result2 = 0;
long result3 = 0;
long result4 = 0;
long result5 = 0;
long result6 = 0;
long result7 = 0;
long result8 = 0;
long result9 = 0;
long result10 = 0;
long result11 = 0;
long result12 = 0;
ThreadLocalRandom random = ThreadLocalRandom.current();
for(long i = 0;i < 100000000;i++){
switch(random.nextInt(1, 13)){
case 2:
result2++;
break;
case 3:
result3++;
break;
case 4:
result4++;
break;
case 5:
result5++;
break;
case 6:
result6++;
break;
case 7:
result7++;
break;
case 8:
result8++;
break;
case 9:
result9++;
break;
case 10:
result10++;
break;
case 11:
result11++;
break;
case 12:
result12++;
break;
}
}
System.out.println(2+":"+(double)result2/100000000);
System.out.println(3+":"+(double)result3/100000000);
System.out.println(4+":"+(double)result4/100000000);
System.out.println(5+":"+(double)result5/100000000);
System.out.println(6+":"+(double)result6/100000000);
System.out.println(7+":"+(double)result7/100000000);
System.out.println(8+":"+(double)result8/100000000);
System.out.println(9+":"+(double)result9/100000000);
System.out.println(10+":"+(double)result10/100000000);
System.out.println(11+":"+(double)result11/100000000);
System.out.println(12+":"+(double)result12/100000000);
}
2:0.08334111
3:0.0833105
4:0.08332852
5:0.08330855
6:0.0833043
7:0.08332969
8:0.08336629
9:0.08336569
10:0.08336787
11:0.08332725
12:0.08331923
可以明显的看出来,并行处理比非必行快3倍。这只是因为我的系统的处理器是4核心的,如果是更多核心应该会更快。
4.4并行处理更多数据:100亿
@Test
public void test4() {
Map<Integer, Long> map = LongStream
.range(0, 10000000000L)
.parallel()
.mapToObj(i -> {
ThreadLocalRandom random = ThreadLocalRandom.current();
return random.nextInt(0, 10);
})
.collect(
Collectors.groupingBy(k -> k,Collectors.counting()));
map.forEach((k, v) -> System.out.println(k + ":" + v));
}
0:999972756
1:1000018597
2:1000021399
3:999996456
4:1000044502
5:999991778
6:999953577
7:999998465
8:999964328
9:1000038142
之前测试出1亿的数据需要0.68秒左右现在是100亿的数据,需要70秒,可以看到随着数据量的增加,计算速度并没有慢下来。
很赞。
5.并行处理的限制
对于大多数的处理是没有限制的,但是reduce方法需要满足一些条件:
1.满足交换律
2.初始值恒等
6.性能因素
1.数据大小
数据量足够大时,才有意义。一般判断的标准就是并行处理时,数据的分割与结果的合并所需的时间远远的小于处理一组数据的时间。
2.源数据结构
这里说的就是在数据分割时的性能问题,如果源数据结构简单,那么分割数据用的操作就比较少,性能就好。
3.装箱
基本类型肯定比装箱类型快,性能好,开销低。
4.核的数量
核心数量是影响性能的主要因素,俗话说的好,人多力量大,核心数量多,性能就好(大量的数据前提下)
5.单元处理开销
这个是一组数据处理的开销,也就是最小单位处理的开销,比如说分割数据需要的时间为1,处理操作的时间为10,合并操作的时间为1,那么有10个核心,每个单元处理时间为1,因为并行导致合并的时间比率为2/3
单元处理开销只是1/3、
其中分割1,处理1,合并1.10个核心。
并行与串行时间比:
串行:
10
并行:
3
比:3/10 = 0.3
含义:并行比串行快1/0.3 = 3倍
如果分割是1,合并是1,处理是1000:
那么10个核心处理时间是102,
并行导致时间比率2/102
单元处理开销是100/102
看下并行与串行时间比:
串行:
1000
并行:
102
比:102/1000=0.102
含义:并行比串行快1/0.102=10倍
所以单元处理开销所占时间比例越高,并行性能越明显。
7.并行流框架
并行在大量数据与大于1核心的情况下是快于串行的,那么,并行数据分割与合并是如何实现的呢?
fork/join框架:
8.核心类库性能分组
1.性能好
ArrayList、数组或IntStream.range,这些数据结构支持随机读取,能被随意的任意分解。
2.性能一般
HashSet、TreeSet,这些数据不容易被公平的分解,不公平的分解还是可以。
3.性能差
LinkedList、Stream.iterate、BufferedReader.lines等,因为长度未知,所以较难预测分解的位置。
为了提升性能,尽可能的使用无状态、无序的集合,无序的操作。
9.数组并行操作
方法名 | 操作 |
---|---|
parallelPrefix | 任意给定一个函数,计算数组的和 |
parallelSetAll | 使用Lambda表达式更新数据元素 |
parallelSort | 并行化对数组元素排序 |
@Test
public void test6() {
double[] va = new double[2];
va[0] = 10;
va[1] = 1;
print(va);
System.out.println();
Arrays.parallelSetAll(va, x -> va[x] + 0.5);// 更新数组每一项
print(va);
System.out.println();
Arrays.parallelSort(va);// 对数组进行排序
print(va);
System.out.println();
Arrays.parallelPrefix(va, (x, y) -> x + y);// 对数组求和
print(va);
System.out.println();
Arrays.parallelSort(va);// 对数组进行排序
print(va);
}
private void print(double[] va) {
for (double d : va) {
System.out.print(d + " ");
}
}
结果:
10.0 1.0
10.5 1.5
1.5 10.5
1.5 12.0
1.5 12.0
求和求的是数组前n项的和;
更新传入的是下标
源码:
public static void parallelSetAll(double[] array, IntToDoubleFunction generator) {
Objects.requireNonNull(generator);
IntStream.range(0, array.length).parallel().forEach(i -> { array[i] = generator.applyAsDouble(i); });
}
public static void parallelSort(double[] a) {
int n = a.length, p, g;
if (n <= MIN_ARRAY_SORT_GRAN ||
(p = ForkJoinPool.getCommonPoolParallelism()) == 1)
DualPivotQuicksort.sort(a, 0, n - 1, null, 0, 0);
else
new ArraysParallelSortHelpers.FJDouble.Sorter
(null, a, new double[n], 0, n, 0,
((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
MIN_ARRAY_SORT_GRAN : g).invoke();
}
public static void parallelPrefix(double[] array, DoubleBinaryOperator op) {
Objects.requireNonNull(op);
if (array.length > 0)
new ArrayPrefixHelpers.DoubleCumulateTask
(null, op, array, 0, array.length).invoke();
}