我一直在玩一些多线程图像处理代码,这些代码读取图像并将其转换为灰度2种方式 - 顺序,然后并行,所以我可以比较两者之间的差异。
我做的一件事是制作一张绝对微小的图像,只有4 x 4px,一种纯色。顺序版本通常在大约20毫秒内运行,而(4线程)并行版本有时会这样做,但有时它似乎会“卡住”并且需要非常长的时间,有时长达1.5秒。这似乎不会发生(?)少于4个线程,所以我只是想知道是什么导致它减速这么多?我有一些想法,主要是因为为非常小的图像设置多个线程的开销可能不值得,但1.5秒是一个很长的等待时间,比任何线程创建都要多开销。
以下是源代码:
PixelsManipulation.java(主类):
public final class PixelsManipulation{
private static Sequential sequentialGrayscaler = new Sequential();
public static void main(String[] args) throws FileNotFoundException, IOException, InterruptedException {
File file = new File("src/pixelsmanipulation/hiresimage.jpg");
FileInputStream fis = new FileInputStream(file);
BufferedImage image = ImageIO.read(fis); //reading the image file
int rows = 2; // 2 rows and 2 cols will split the image into quarters
int cols = 2;
int chunks = rows * cols; // 4 chunks, one for each quarter of the image
int chunkWidth = image.getWidth() / cols; // determines the chunk width and height
int chunkHeight = image.getHeight() / rows;
int count = 0;
BufferedImage imgs[] = new BufferedImage[chunks]; // Array to hold image chunks
for (int x = 0; x < rows; x++) {
for (int y = 0; y < cols; y++) {
//Initialize the image array with image chunks
imgs[count] = new BufferedImage(chunkWidth, chunkHeight, image.getType());
// draws the image chunk
Graphics2D gr = imgs[count++].createGraphics(); // Actually create an image for us to use
gr.drawImage(image, 0, 0, chunkWidth, chunkHeight, chunkWidth * y, chunkHeight * x, chunkWidth * y + chunkWidth, chunkHeight * x + chunkHeight, null);
gr.dispose();
}
}
//writing mini images into image files
for (int i = 0; i < imgs.length; i++) {
ImageIO.write(imgs[i], "jpg", new File("img" + i + ".jpg"));
}
System.out.println("Mini images created");
// Start threads with their respective quarters (chunks) of the image to work on
// I have a quad-core machine, so I can only use 4 threads on my CPU
Parallel parallelGrayscaler = new Parallel("thread-1", imgs[0]);
Parallel parallelGrayscaler2 = new Parallel("thread-2", imgs[1]);
Parallel parallelGrayscaler3 = new Parallel("thread-3", imgs[2]);
Parallel parallelGrayscaler4 = new Parallel("thread-4", imgs[3]);
// Sequential:
long startTime = System.currentTimeMillis();
sequentialGrayscaler.ConvertToGrayscale(image);
long stopTime = System.currentTimeMillis();
long elapsedTime = stopTime - startTime;
System.out.println("Sequential code executed in " + elapsedTime + " ms.");
// Multithreaded (parallel):
startTime = System.currentTimeMillis();
parallelGrayscaler.start();
parallelGrayscaler2.start();
parallelGrayscaler3.start();
parallelGrayscaler4.start();
// Main waits for threads to finish so that the program doesn't "end" (i.e. stop measuring time) before the threads finish
parallelGrayscaler.join();
parallelGrayscaler2.join();
parallelGrayscaler3.join();
parallelGrayscaler4.join();
stopTime = System.currentTimeMillis();
elapsedTime = stopTime - startTime;
System.out.println("Multithreaded (parallel) code executed in " + elapsedTime + " ms.");
}
}
Parallel.java:
// Let each of the 4 threads work on a different quarter of the image
public class Parallel extends Thread{//implements Runnable{
private String threadName;
private BufferedImage myImage; // Calling it "my" image because each thread will have its own unique quarter of the image to work on
private int width, height; // Image params
Parallel(String name, BufferedImage image){
threadName = name;
System.out.println("Creating "+ threadName);
myImage = image;
width = myImage.getWidth();
height = myImage.getHeight();
}
public void run(){
System.out.println("Running " + threadName);
// Pixel by pixel (for our quarter of the image)
for (int j = 0; j < height; j++){
for (int i = 0; i < width; i++){
// Traversing the image and converting the RGB values (doing the same thing as the sequential code but on a smaller scale)
Color c = new Color(myImage.getRGB(i,j));
int red = (int)(c.getRed() * 0.299);
int green = (int)(c.getGreen() * 0.587);
int blue = (int)(c.getBlue() * 0.114);
Color newColor = new Color(red + green + blue, red + green + blue, red + green + blue);
myImage.setRGB(i,j,newColor.getRGB()); // Write the new value for that pixel
}
}
File output = new File("src/pixelsmanipulation/"+threadName+"grayscale.jpg"); // Put it in a "lower level" folder so we can see it in the project view
try {
ImageIO.write(newImage, "jpg", output);
} catch (IOException ex) {
Logger.getLogger(Parallel.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println("Thread " + threadName + " exiting. ---");
}
}
编辑:以下是执行的示例日志:
Creating thread-1
Creating thread-2
Creating thread-3
Creating thread-4
Sequential code executed in 5 ms.
Running thread-2
Running thread-1
Running thread-3
Thread thread-1 exiting. ---
Thread thread-2 exiting. ---
Thread thread-3 exiting. ---
Running thread-4
Thread thread-4 exiting. ---
Multithreaded (parallel) code executed in 5 ms.
奇怪的是我似乎无法复制延迟,我现在在与我最初工作的机器不同的机器上。这可能是处理器的某种不同(两者都是四核)?我会尝试从原始机器上获取日志。
编辑2:正如Gee Bee所说,这很可能是由于这样一个事实,即缓慢似乎只发生在HDD而不是SSD上,因为我正在写入文件内部线程,这在HDD上通常较慢。取出文件编写代码可以使线程运行得更快,并且只需在SSD上运行它(虽然我想在线程内写入文件并不是最佳选择,应该避免)。答案 0 :(得分:1)
问题非常棘手,1.5秒很可能涉及锁定问题。
运行代码后:
现在每个处理线程都做了很多事情:
我建议从实际处理中隔离文件写入和JPEG编码,然后重新测量。
如果你有4个线程,现在你会遇到4次JPEG编码,4次并行文件写入,这可能会产生问题。我在SSD上,因此文件写入没有区别,但在硬盘上它可以产生影响。
请注意,使用比物理核心更多的线程不会使并行操作更快,但只会增加额外开销。 另请注意,如果图片太小,“并行”线程不能并行工作。相反,第一个线程已经完成,而你只是开始线程3。
虽然AWT强制锁定bufferedimage: Are parallel drawing operations possible with Java Graphics2d?这不会影响您的性能,因为您使用了来自四个不同线程的四个不同的缓冲图像。
所以,你的想法会奏效。但是,如果计算速度很快,则4个线程的性能提升太少。尽量不要测量你无法控制的操作(例如文件io性能可以是任何东西,具体取决于你的硬件和当前虚拟内存条件)。