ParallelArray 或使用 ForkJoinPool
ParallelArray or using ForkJoinPool
我必须改进以下代码的性能,但我不知道要改进什么(或如何)。
目前,代码需要大约。 12 秒创建 94.478.400 个对象。
我想,我可以通过使用 ForkJoinPool
或 ParallelArray
之类的东西来加快速度,但我不知道如何或在哪里将这种并行化放入我的代码中。
运行 主要方法:
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class ParallelArray {
// class to hold the permutations
public static class Perm {
public Perm(double a, double b, double c) {
this.a = a;
this.b = b;
this.c = c;
}
double a;
double b;
double c;
public String toString() {
return "a=" + a + " b" + b + " c" + c;
}
}
public static void main(String[] args) {
var start = System.currentTimeMillis();
var perms = new ArrayList<Perm>();
// create first permutations
var perm1 = computePerm(2).collect(Collectors.toList());
// create second permutations
var perm2 = computePerm(-2).collect(Collectors.toList());
// now I permutate again with perm1 and perm2:
// iterating is fast
for (var p1 : perm1) {
for (var p2 : perm2) {
// but this operation needs too long
// can I speed this up by using fork-join or parallelArray?
perms.add(new Perm(p1.a + p2.a, p1.b + p2.b, p1.c + p2.c));
}
}
var end = System.currentTimeMillis();
// all the operations needs approx. 12 seconds
System.out.println("diff: " + (end - start) + " millis");
// it returns 94.478.400 objects
System.out.println("objects: " + perms.size());
}
public static Stream<Perm> computePerm(int inc) {
// I use a stream builder (to increase perfomance?)
Stream.Builder<Perm> all = Stream.builder();
// iterating is fast
for (var a : arrayA().toArray()) {
for (var b : arrayB().toArray()) {
for (var c : arrayC().toArray()) {
// but this operation needs too long
// can I speed this up by using fork-join or parallelArray?
all.add(new Perm(a + inc, b + inc, c + inc));
}
}
}
return all.build();
}
public static IntStream arrayA() {
return IntStream.rangeClosed(1, 30);
}
public static DoubleStream arrayB() {
return DoubleStream.iterate(0, d -> d + 0.1).limit(18);
}
public static DoubleStream arrayC() {
return DoubleStream.iterate(1, d -> d - 0.1).limit(18);
}
}
编辑:
玩了一圈之后,我使用了 computePerm(int c) 的替代方法,它使用 parallelStream:
public static Stream<Perm> computePermParallel(int inc) {
Stream<Perm> all = arrayA().parallel().boxed().flatMap(a ->
{
return arrayB().parallel().boxed().flatMap(b ->
{
return arrayC().parallel().mapToObj(c ->
{
return new Perm(a + inc, b + inc, c + inc);
});
});
});
return all;
}
然而,当使用 computePermParallel(int inc)
而不是 computePerm(int inc)
时,性能更差:而不是大约。 12 秒,我需要等待大约。 19 秒。所以非并行版本比并行版本快得多,即使有如此大量的对象。不知道为什么。
有些事情对您不利..
9400 万个简单对象大约需要 2-4GB 的 RAM 用于该数据 - 您是否有足够的 RAM/为此配置......
持续重新分配/复制 ArrayList 支持 - 您可以使用 ArrayList 的 ensureCapacity 方法来帮助解决这个问题(只要 Array 运行s 超出容量,后备存储必须增长并复制数据 - 需要一些成本但没有收益)。
内存分配在 Java 中很慢 .. 如果您可以将数据表示为 3 个 double [not Double] 数组已经预先调整大小 - 您可以大大减少内存数量分配(3 个对象分配 vs 94+ 百万)——你只需要执行一些代码然后初始化数据..时间应该快得多(尽管你可能 运行 性能比你预期的还要差 -如果它导致 CPU 缓存被丢弃)。实际尝试预先创建 1 个 9400 万行数组,看看需要多长时间...
不必要的临时对象 - 在 arrayA() 的 for 循环中每次调用 arrayB() 和 arrayC() 都会导致构建一个新的临时结果,然后将其丢弃。但是在这个例子中你可以这样做 - 然后只有 3 个调用(和临时集),而不是 "nof B" * "nof C" * "nof A" + "nof B" * "nof A" + 。 .. [如果结果需要在每个循环上有所不同——那么请忽略这一点,但正如所写的那样,它读起来很好,但 运行 很糟糕。
所以改变这个 -
for (var a : arrayA().toArray()) {
for (var b : arrayB().toArray()) {
for (var c : arrayC().toArray()) {
对此-
final int[] aArray = arrayA().toArray();
final double[] bArray = arrayB().toArray();
final double[] cArray = arrayC().toArray();
for (var a : aArray) {
for (var b : bArray) {
for (var c : cArray) {
也许你会把它降低一个数量级 - 但我怀疑至少还有 1 到 2 秒 - 你正在使用大量内存 - 你真的需要吗(这听起来像是一个缺陷特殊情况)??
为了让您了解内存与 CPU 的相对成本,您可以测试创建所有对象,然后循环并设置所有值并计算差异的时间。
对您的代码的一些建议
不断扩大 ArrayList 会导致大量副本和浪费时间。由于您已经预先知道大小,因此在初始化期间将其传递给您的 ArrayList。您可以使用 var perms = new ArrayList<Perm>(94478400);
对此进行测试。通过在数组列表 perms.ensureCapacity(94478400);
上设置调用以下方法可以实现相同的目的
在您的情况下,使用 Streams 并没有真正的优势,因为您创建了一个包含所有元素的流。然后将该流更改为列表。当您在收集之前应用某种过滤时,Stream 会有好处。在您的情况下,您只是从 Stream 重新分配到 List。您可以直接将其添加到列表中(使用正确的初始值参数)
ForkJoinPool
: 运行 并行的事情在你的场景中并没有真正的帮助。如果您必须 运行 一些昂贵的计算,这将有所帮助。在您的情况下,您只需创建一个新的 Perm
对象并将其放入权限列表中。最大的问题是 ArrayList
不是线程安全的。像现在这样使用它会导致您丢失数据。解决方案是让 ForkJob
return 一个临时列表,您在完成后将其合并到完整列表中。或者使用像 CopyOnWriteArrayList
这样的线程安全列表。这两种解决方案都会创建更多包含数据的列表,并导致数据被复制。因此,与其改善 运行 时间,很可能会使它变得更糟。
我必须改进以下代码的性能,但我不知道要改进什么(或如何)。
目前,代码需要大约。 12 秒创建 94.478.400 个对象。
我想,我可以通过使用 ForkJoinPool
或 ParallelArray
之类的东西来加快速度,但我不知道如何或在哪里将这种并行化放入我的代码中。
运行 主要方法:
import java.util.ArrayList;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class ParallelArray {
// class to hold the permutations
public static class Perm {
public Perm(double a, double b, double c) {
this.a = a;
this.b = b;
this.c = c;
}
double a;
double b;
double c;
public String toString() {
return "a=" + a + " b" + b + " c" + c;
}
}
public static void main(String[] args) {
var start = System.currentTimeMillis();
var perms = new ArrayList<Perm>();
// create first permutations
var perm1 = computePerm(2).collect(Collectors.toList());
// create second permutations
var perm2 = computePerm(-2).collect(Collectors.toList());
// now I permutate again with perm1 and perm2:
// iterating is fast
for (var p1 : perm1) {
for (var p2 : perm2) {
// but this operation needs too long
// can I speed this up by using fork-join or parallelArray?
perms.add(new Perm(p1.a + p2.a, p1.b + p2.b, p1.c + p2.c));
}
}
var end = System.currentTimeMillis();
// all the operations needs approx. 12 seconds
System.out.println("diff: " + (end - start) + " millis");
// it returns 94.478.400 objects
System.out.println("objects: " + perms.size());
}
public static Stream<Perm> computePerm(int inc) {
// I use a stream builder (to increase perfomance?)
Stream.Builder<Perm> all = Stream.builder();
// iterating is fast
for (var a : arrayA().toArray()) {
for (var b : arrayB().toArray()) {
for (var c : arrayC().toArray()) {
// but this operation needs too long
// can I speed this up by using fork-join or parallelArray?
all.add(new Perm(a + inc, b + inc, c + inc));
}
}
}
return all.build();
}
public static IntStream arrayA() {
return IntStream.rangeClosed(1, 30);
}
public static DoubleStream arrayB() {
return DoubleStream.iterate(0, d -> d + 0.1).limit(18);
}
public static DoubleStream arrayC() {
return DoubleStream.iterate(1, d -> d - 0.1).limit(18);
}
}
编辑:
玩了一圈之后,我使用了 computePerm(int c) 的替代方法,它使用 parallelStream:
public static Stream<Perm> computePermParallel(int inc) {
Stream<Perm> all = arrayA().parallel().boxed().flatMap(a ->
{
return arrayB().parallel().boxed().flatMap(b ->
{
return arrayC().parallel().mapToObj(c ->
{
return new Perm(a + inc, b + inc, c + inc);
});
});
});
return all;
}
然而,当使用 computePermParallel(int inc)
而不是 computePerm(int inc)
时,性能更差:而不是大约。 12 秒,我需要等待大约。 19 秒。所以非并行版本比并行版本快得多,即使有如此大量的对象。不知道为什么。
有些事情对您不利..
9400 万个简单对象大约需要 2-4GB 的 RAM 用于该数据 - 您是否有足够的 RAM/为此配置......
持续重新分配/复制 ArrayList 支持 - 您可以使用 ArrayList 的 ensureCapacity 方法来帮助解决这个问题(只要 Array 运行s 超出容量,后备存储必须增长并复制数据 - 需要一些成本但没有收益)。
内存分配在 Java 中很慢 .. 如果您可以将数据表示为 3 个 double [not Double] 数组已经预先调整大小 - 您可以大大减少内存数量分配(3 个对象分配 vs 94+ 百万)——你只需要执行一些代码然后初始化数据..时间应该快得多(尽管你可能 运行 性能比你预期的还要差 -如果它导致 CPU 缓存被丢弃)。实际尝试预先创建 1 个 9400 万行数组,看看需要多长时间...
不必要的临时对象 - 在 arrayA() 的 for 循环中每次调用 arrayB() 和 arrayC() 都会导致构建一个新的临时结果,然后将其丢弃。但是在这个例子中你可以这样做 - 然后只有 3 个调用(和临时集),而不是 "nof B" * "nof C" * "nof A" + "nof B" * "nof A" + 。 .. [如果结果需要在每个循环上有所不同——那么请忽略这一点,但正如所写的那样,它读起来很好,但 运行 很糟糕。
所以改变这个 -
for (var a : arrayA().toArray()) {
for (var b : arrayB().toArray()) {
for (var c : arrayC().toArray()) {
对此-
final int[] aArray = arrayA().toArray();
final double[] bArray = arrayB().toArray();
final double[] cArray = arrayC().toArray();
for (var a : aArray) {
for (var b : bArray) {
for (var c : cArray) {
也许你会把它降低一个数量级 - 但我怀疑至少还有 1 到 2 秒 - 你正在使用大量内存 - 你真的需要吗(这听起来像是一个缺陷特殊情况)??
为了让您了解内存与 CPU 的相对成本,您可以测试创建所有对象,然后循环并设置所有值并计算差异的时间。
对您的代码的一些建议
不断扩大 ArrayList 会导致大量副本和浪费时间。由于您已经预先知道大小,因此在初始化期间将其传递给您的 ArrayList。您可以使用
上设置调用以下方法可以实现相同的目的var perms = new ArrayList<Perm>(94478400);
对此进行测试。通过在数组列表perms.ensureCapacity(94478400);
在您的情况下,使用 Streams 并没有真正的优势,因为您创建了一个包含所有元素的流。然后将该流更改为列表。当您在收集之前应用某种过滤时,Stream 会有好处。在您的情况下,您只是从 Stream 重新分配到 List。您可以直接将其添加到列表中(使用正确的初始值参数)
ForkJoinPool
: 运行 并行的事情在你的场景中并没有真正的帮助。如果您必须 运行 一些昂贵的计算,这将有所帮助。在您的情况下,您只需创建一个新的Perm
对象并将其放入权限列表中。最大的问题是ArrayList
不是线程安全的。像现在这样使用它会导致您丢失数据。解决方案是让ForkJob
return 一个临时列表,您在完成后将其合并到完整列表中。或者使用像CopyOnWriteArrayList
这样的线程安全列表。这两种解决方案都会创建更多包含数据的列表,并导致数据被复制。因此,与其改善 运行 时间,很可能会使它变得更糟。