[英]Why is multithreading slowing down
我正在尝试将RGB值的字节数组保存为png图像,如下所示:
byte[] imgArray = ...;
int canvasSize = 512;
ColorModel c = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), null, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
Image image = Toolkit.getDefaultToolkit().createImage(
new MemoryImageSource(canvasSize, canvasSize, c, imgArray, 0, canvasSize));
BufferedImage bimage = new BufferedImage(canvasSize, canvasSize, BufferedImage.TYPE_BYTE_GRAY);
// Draw the image on to the buffered image
Graphics2D bGr = bimage.createGraphics();
bGr.drawImage(image, 0, 0, null); //This is what takes all the time
bGr.dispose();
ImageIO.write(bimage, "PNG", new File(uniqueFileName));
我正在使用FixedThreadpool同时保存多个图像。 我使用的线程越多(最多我的计算机上的可用核心数),保存过程所需的时间就越长。 在6个线程上运行所需的时间几乎是在一个线程上运行的两倍。
为什么多线程需要这么长时间? 内存交换? 我可以避免这个问题吗?
另外,如果我有更好的方法从阵列中保存png,请告诉我。
编辑显示图片被保存为不同的图像,而不是相互覆盖。
我认为这是由不同类型的优化引起的。 您试图在一个路径中一次保存多个图像 - 这意味着需要排队保存操作 - 这是一个IO绑定任务,而不是CPU绑定。 多个保存线程在这里可能不是很有帮助。 同样在非常小的(就CPU功率要求而言)操作中,委托线程来完成工作可能只会产生额外的开销,从而导致完成任务所需的时间延长,而不是缩短。 希望这可以帮助 :)
让我们说你的持久存储(硬盘,usb stick,ssd)以50MB / s的速度写入。 如果你写50MB,那么无论线程/核心数是多少,它总是需要1秒。 这被称为带宽瓶颈。
实际上还会有其他瓶颈。 内存,CPU或最常见的寻道时间。 寻求给定块的硬盘需要几毫秒。 同时写入多个文件将导致更多搜索,从而可能减慢所有写入速度。 (大)缓冲区可能有帮助。
最初我还认为主要原因可能是concurrent write
操作。 由于写入量小于2 MB,因此磁盘I / O通常没有瓶颈。 经过一番调查,我找到了原因。 在您的情况下, ImageIO
正在使用一个同步的方法( sun.java2d.cmm.lcms.LCMSTransform.doTransform
)。
我使用这个小代码来确认您发现的行为并找到锁定条件。
package sub.optimal.jai;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.MemoryImageSource;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.imageio.ImageIO;
public class ThreadedOutput implements Callable<Boolean> {
private final String fileName;
private ThreadedOutput(String name) {
this.fileName = name;
}
@Override
public Boolean call() throws Exception {
Thread.currentThread().setName("convert: " + fileName);
return this.storeImage();
}
public boolean storeImage() throws IOException {
byte[] imgArray = new byte[512 * 512];
int canvasSize = 512;
int value = 0;
for (int i = 0; i < imgArray.length; i++) {
imgArray[i] = (byte) value;
value = (++value & 0xFF);
}
ColorModel colorModel = new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_GRAY), null, false,
false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
Image image = Toolkit.getDefaultToolkit().createImage(
new MemoryImageSource(canvasSize, canvasSize, colorModel,
imgArray, 0, canvasSize)
);
BufferedImage bimage = new BufferedImage(canvasSize, canvasSize,
BufferedImage.TYPE_BYTE_GRAY);
Graphics2D bGr = bimage.createGraphics();
bGr.setPaintMode();
System.out.printf("start %s%n", fileName);
long start = System.currentTimeMillis();
bGr.drawImage(image, 0, 0, null);
long end = System.currentTimeMillis();
System.out.printf("duration drawimage: %s %d%n", fileName, end-start);
bGr.dispose();
return ImageIO.write(bimage, "PNG", new File("/tmp/" + fileName));
}
public static void main(String[] args) throws Exception {
System.out.println("CPUs: " + Runtime.getRuntime()
.availableProcessors());
ExecutorService executor = Executors.newFixedThreadPool(8);
List<ThreadedOutput> callables = new ArrayList<>();
callables.add(new ThreadedOutput("file1.png"));
callables.add(new ThreadedOutput("file2.png"));
callables.add(new ThreadedOutput("file3.png"));
callables.add(new ThreadedOutput("file4.png"));
callables.add(new ThreadedOutput("file5.png"));
callables.add(new ThreadedOutput("file6.png"));
callables.add(new ThreadedOutput("file7.png"));
callables.add(new ThreadedOutput("file8.png"));
System.out.println("execute creation in sequence");
long start = System.currentTimeMillis();
for (ThreadedOutput callable : callables) {
callable.call();
}
long end = System.currentTimeMillis();
System.out.printf("duration in sequence: %d%n", end - start);
System.out.println("execute creation in parallel");
start = System.currentTimeMillis();
executor.invokeAll(callables);
executor.shutdown();
end = System.currentTimeMillis();
System.out.printf("duration in threads: %d%n", end - start);
}
}
执行以下示例输出生成的代码
CPUs: 4
execute creation in sequence
start file1.png
duration drawimage: file1.png 1021
start file2.png
duration drawimage: file2.png 1021
start file3.png
duration drawimage: file3.png 1230
start file4.png
duration drawimage: file4.png 1056
start file5.png
duration drawimage: file5.png 1046
start file6.png
duration drawimage: file6.png 835
start file7.png
duration drawimage: file7.png 983
start file8.png
duration drawimage: file8.png 952
duration in sequence: 8549
execute creation in parallel
start file1.png
start file4.png
start file2.png
start file3.png
start file6.png
start file8.png
start file5.png
start file7.png
duration drawimage: file6.png 18889
duration drawimage: file1.png 19147
duration drawimage: file8.png 19204
duration drawimage: file5.png 19353
duration drawimage: file7.png 19435
duration drawimage: file3.png 19498
duration drawimage: file2.png 19582
duration drawimage: file4.png 19591
duration in threads: 19612
在八个并行线程中运行创建的速度要慢得多。
在打印execute creation in parallel
行execute creation in parallel
后创建进程的线程转储(使用jstack $pid_of_example
)时,您将找到类似于
"convert: file1.png" #13 prio=5 os_prio=0 tid=...
java.lang.Thread.State: RUNNABLE
at sun.java2d.cmm.lcms.LCMS.colorConvert(Native Method)
at sun.java2d.cmm.lcms.LCMSTransform.doTransform(LCMSTransform.java:161)
- locked <0x00000000c463a080> (a sun.java2d.cmm.lcms.LCMSTransform)
"convert: file2.png" #14 prio=5 os_prio=0 tid=...
java.lang.Thread.State: BLOCKED (on object monitor)
at sun.java2d.cmm.lcms.LCMSTransform.doTransform(LCMSTransform.java:140)
- waiting to lock <0x00000000c463a080> (a sun.java2d.cmm.lcms.LCMSTransform)
形成线程转储,您可以看到线程#13
锁定监视器locked <0x00000000c463a080>
并且线程#14
正在等待锁定此监视器waiting to lock <0x00000000c463a080>
。
如果您的数组imgArray
已经将灰色信息保存为您想要将其写入图像文件,那么您可以直接编写数据(正如haraldk已经提到的那样) 。
而不是将您的图像绘制成另一个图像
bGr.drawImage(image, 0, 0, null);
你直接写光栅图像信息
WritableRaster raster = bimage.getRaster();
raster.setDataElements(0, 0, canvasSize, canvasSize, imgArray);
应用该更改后的执行时间是
duration in sequence: 607
duration in threads: 134
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.