并行IntStream hack

时间:2014-05-19 17:14:30

标签: java multithreading optimization java-stream

这里有一些代码。

public void blur(final int x, final int y, final int w, final int h) {
    final Picture p = new Picture(this);
    IntStream.range(x, x + w).parallel().forEach(i
        -> IntStream.range(Y, Y + h).forEach(j
            -> {
                final Pixel pixel = this.getPixel(i, j);
                final java.util.List<Pixel> others
                = Arrays.asList(
                    p.getPixel(i - 1, j),
                    p.getPixel(i, j - 1),
                    p.getPixel(i, j + 1),
                    p.getPixel(i + 1, j),
                    p.getPixel(i - 1, j - 1),
                    p.getPixel(i + 1, j + 1),
                    p.getPixel(i - 1, j + 1),
                    p.getPixel(i + 1, j - 1),
                    pixel
                );
                pixel.setBlue((int) (others.stream()
                    .mapToInt(Pixel::getBlue).average().getAsDouble()));
                pixel.setRed((int) (others.stream()
                    .mapToInt(Pixel::getRed).average().getAsDouble()));
                pixel.setGreen((int) (others.stream()
                    .mapToInt(Pixel::getGreen).average().getAsDouble()));
        })
    );
}

某些语言为一系列整数提供并行for循环。 Java似乎没有,但我不觉得多线程正确的方式&#34; (比如fork-join等)

效率这么高吗?我发现这确实比标准for (int i ...代码更快。我应该将哪个循环(流)并行?这是一个很好的编码实践吗?

3 个答案:

答案 0 :(得分:2)

如果表现真的很重要。你应该关注:

  • 内圈,
  • 和记忆位置。

特别是,后者取决于内存中像素的布局。它可以对性能产生重大影响,无论它们是逐行还是逐列对齐(例如由于false sharing)。出于这个原因,我建议使用显式并行化。

想象一下,您有一个针对顺序执行进行了优化的方法:

void blurSequential(Picture source, int x, int y, int w, int h);

然后您可以轻松地将图像划分为图块,并在每个图块上独立执行顺序方法。以下代码显示了如何实现它。您必须使用异步执行机制替换asyncawait伪指令,例如使用 ExecutorService Future

void blurParallel(Picture source, int x, int y, int w, int h) {
    int processors = Runtime.getRuntime().availableProcessors();
    blurParallel(source, x, y, w, h, processors * 4);
}

void blurParallel(Picture source, int x, int y, int w, int h, int parallelism) {
    if (parallelism <= 1) {
         blurSequential(source, x, y, w, h);
    } else if (w >= THRESHOLD_WIDTH) {
         int m = w / 2;
         async blurParallel(source, x, y, m, h, parallelism / 2);
         blurParallel(source, x + m, y, w - m, h, parallelism / 2);
         await
    } else if (h >= THRESHOLD_HEIGHT) {
         int m = h / 2;
         async blurParallel(source, x, y, w, m, parallelism / 2);
         blurParallel(source, x, y + m, w, h - m, parallelism / 2);
         await
    } else {
         blurSequential(source, x, y, w, h);
    }
}

答案 1 :(得分:0)

首先,我认为这段代码过于混乱,因此隐藏了它实际上正在做的事情。因此,我提出以下代码,其余部分的流不是所有内容的解决方案。

private List<Pixel> getNeighbours(final Picture picture, final IntTuple intTuple) {
    List<Pixel> pixels = new ArrayList<>();
    for (int x = intTuple.x - 1; x <= intTuple.x + 1; x++) {
        for (int y = intTuple.y - 1; y <= intTuple.y + 1; y++) {
            pixels.add(picture.getPixel(x, y));
        }
    }
    return pixels;
}

private void averagePixel(final Picture picture, final IntTuple intTuple) {
    double red = 0d;
    double green = 0d;
    double blue = 0d;
    List<Pixel> neighbours = getNeighbours(picture, intTuple);
    for (Pixel pixel : neighbours) {
        red += pixel.getRed();
        green += pixel.getGreen();
        blue += pixel.getBlue();
    }
    Pixel pixel = picture.getPixel(intTuple.x, intTuple.y);
    pixel.setRed((int)(red / neighbours.size()));
    pixel.setGreen((int)(green / neighbours.size()));
    pixel.setBlue((int)(blue / neighbours.size()));
}

IntStream.range(x, x + w)
        .parallel()
        .boxed()
        .flatMap(xVal -> IntStream.range(y, y + h).parallel().mapToObj(yVal -> new IntTuple(xVal, yVal)))
        .forEach(tuple -> averagePixel(picture, tuple));

这是做什么的:

  1. 在宽度上获得并行IntSream
  2. 然后将其加到Stream<Integer>,因为没有flatMapToObj。这是我认为你可以通过ForkJoinPool获得性能的地方。
  3. 然后执行平面地图操作,将每个x值映射到(x,y)元组,如下所示:
    1. 在高度上获得平行IntStream
    2. IntStream映射到IntTuple。请注意,您仍然可以在平面地图操作开始时访问x值。
  4. 现在你有Stream<IntTuple>
  5. 最后,对所有元组应用averagePixel操作。
  6. 其他方法的特别说明:

    • getNeighbours在x和y坐标上使用双循环以避免手动编码。
    • averagePixel不会使用流,因此可以一次性计算红色,绿色和蓝色值。我不认为你可以通过这种方式这样做。

    我希望这会有所帮助,请注意,此代码未经测试,可能包含实现错误。

答案 2 :(得分:0)

我不知道班级图片。

使用BufferedImage时,Image,Raster可以获得整个像素值数组,例如BufferImage.getRGB。我假设这些基础数据用于图片。

不需要计算列表others上的平均值:可以立即求和&amp;增加一个柜台。粗略:

int counter = 0;
int sum = 0;
if (j > 0) {
    sum += source.getPixel(i, j - 1);
    ++counter;
}
...
int blurred = sum / counter;

始终是新数据结构(int[9]左右)的开销,并且进行并行是不值得的。