【问题标题】:Parallel IntStream hack并行 IntStream hack
【发布时间】:2015-12-22 23:57:23
【问题描述】:

这是一些代码。

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 似乎没有,但我不喜欢以“正确的方式”处理多线程(比如 fork-join 等)

这样有效吗?我发现这确实比标准的for (int i ... 代码快。我应该并行哪个循环(流)?这是一种好的编码习惯吗?

【问题讨论】:

  • 另外一个问题,这会是过早的优化吗?如果它需要 2 秒而不是 1 秒并不重要,为什么要以清晰为代价让它快速运行呢?
  • @Bartvbl 不。这是 10 秒而不是 100 秒的问题。此外,我真的不觉得它为时过早本身
  • 哇,是的。这绝对值得。别管我:)
  • 我倾向于说你应该坚持使用常规的 for 循环而不是新的流方法,因为它只会增加混乱。现在我确实意识到您的目标是(并行)效率,所以我建议您为此使用 fork-join 框架,因为目前它看起来很混乱。
  • 这看起来与您想要实现的完全一样:docs.oracle.com/javase/tutorial/essential/concurrency/…(至少是一个很好的起点)

标签: java multithreading optimization java-stream


【解决方案1】:

如果性能真的很重要。你应该关注:

  • 内循环,
  • 和内存局部性。

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

假设您有一个针对顺序执行进行了优化的方法:

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

然后你就可以很方便的把图片分割成小块,在每个小块上独立执行顺序方法。以下代码显示了如何实现。您必须用异步执行机制替换 asyncawait 伪指令,例如与 ExecutorServiceFuture

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);
    }
}

【讨论】:

    【解决方案2】:

    首先,我认为这段代码太杂乱,因此隐藏了它实际在做什么。因此,我提出以下代码,余下的内容是流并不是所有问题的解决方案。

    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&lt;Integer&gt;,因为没有flatMapToObj。这是我相信您可以通过ForkJoinPool 获得性能的地方。
    3. 然后执行平面映射操作,将每个 x 值映射到 (x, y) 元组,如下所示:
      1. 在高度上获得平行的IntStream
      2. IntStream 映射到IntTuple。请注意,您仍然可以在平面地图操作开始时访问 x 值。
    4. 现在你有一个Stream&lt;IntTuple&gt;
    5. 最后对所有元组应用averagePixel 操作。

    其他方法的特别说明:

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

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

    【讨论】:

      【解决方案3】:

      我不知道图片类。

      当使用 BufferedImage、Image、Raster 时,可能会得到整个像素值数组,例如 BufferImage.getRGB。我假设图片中使用了这些基础数据。

      不需要计算列表others 的平均值:可以立即求和并增加一个计数器。粗略:

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

      总是一个新的数据结构(int[9]左右)的开销,做并行是不值得的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-14
        • 2016-04-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多