我试图将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,请告诉我。
编辑显示图片被保存为不同的图像,而不是相互覆盖。
答案 0 :(得分:5)
我认为这是由不同类型的优化引起的。您试图在一个路径中一次保存多个图像 - 这意味着需要 排队保存操作 - 这是一个IO绑定任务,不受CPU限制。多个保存线程在这里可能不是很有帮助。同样在非常小的(就CPU功率要求而言)操作中,委托线程来完成工作可能只会产生额外的开销,从而导致完成任务所需的时间延长,而不是缩短。希望这有帮助:)
答案 1 :(得分:3)
让我们说你的持久存储(硬盘,usb stick,ssd)以50MB / s的速度写入。如果你写50MB,那么无论线程/核心数是多少,它总是需要1秒。这被称为带宽瓶颈。
实际上还会有其他瓶颈。内存,CPU或最常见的寻道时间。寻求给定块的硬盘需要几毫秒。同时写入多个文件将导致更多搜索,从而可能减慢所有写入速度。 (大)缓冲区可能有帮助。
答案 2 :(得分:2)
最初我还认为主要原因可能是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
在八个并行线程中运行创建的速度要慢得多。
在打印行jstack $pid_of_example
后创建流程的线程转储(使用execute creation in parallel
)时,您会发现类似于
"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