Java - 非常小的图像上的多线程需要很长时间

标签 java multithreading

我一直在尝试一些多线程图像处理代码,这些代码读取图像并以两种方式将其转换为灰度 - 顺序,然后并行,这样我就可以比较两者之间的差异。

我做的一件事是制作一个绝对很小的图像,只有 4 x 4 像素,纯色。顺序版本通常运行时间约为 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.");
}
}

并行.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 上运行它一样(尽管我猜想在线程内写入文件并不是真正的最佳选择,应该避免)。

最佳答案

问题相当棘手,1.5秒很可能涉及锁定问题。

运行代码后:

  • 顺序:150ms
  • 并行:57 毫秒(2x2,4 个线程)

现在每个处理线程都会做很多事情:

  • 以像素级别访问图像(由于各种原因,这是一个资源密集型操作)
  • 写入文件
  • 执行 jpeg 压缩

我建议将文件写入和 JPEG 编码与实际处理隔离开来,然后重做测量。

如果您有 4 个线程,现在您会经历 4 次 JPEG 编码和 4 次并行文件写入,这可能会产生问题。我使用的是 SSD,所以文件写入没有什么区别,但在 HDD 上可能会产生影响。

请注意,使用比物理内核更多的线程并不会使并行操作更快,而只会增加额外的开销。 另请注意,如果您的图片太小,“并行”线程将无法并行工作。相反,当您刚刚开始线程 3 时,第一个线程已经完成。

尽管 AWT 对缓冲图像施加了锁定: Are parallel drawing operations possible with Java Graphics2d?这不会影响您的性能,因为您正在使用来自四个不同线程的四个不同的缓冲图像。

所以,你的想法可行。但如果计算速度很快的话,4个线程的性能提升就太少了。尽量不要测量您无法控制的操作(例如文件 io 性能可以是任何值,具体取决于您的硬件和当前虚拟内存条件)。

关于Java - 非常小的图像上的多线程需要很长时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35317789/

相关文章:

Java通过套接字发送字节[]...读取的长度错误

java - IntelliJ代理的设置不起作用(win7)

java - Servlet 中可关闭的非线程安全资源

multithreading - `vkCommandPool` 可以从主线程分配并移动到其他线程吗?

python - 向量化计算矩阵和向量之间的欧氏距离

c# - 如何避免手动启动的线程死亡?

python - "ValueError: sleep length must be non-negative"在线程中

java - RxJava 并行获取 Observables

java - 为什么java中的finalize()方法不在Finalizer类中?为什么它被定义在对象类中?

java - Java中的树遍历: Iterative or recursive?