2016-02-10 71 views
1

我一直在玩一些多線程圖像處理代碼,讀取圖像並將其轉換爲灰度2種方式 - 依次,然後並行,所以我可以比較兩者的區別。Java - 多線程在很長的時間很小的圖像

我做的一件事是做一個絕對小圖像,只有4 x 4px的一個純色。順序版本通常在大約20ms內運行,並且(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

嘗試取出文件寫入,看看它是如何影響你的結果。這很可能是每個線程花費最多的時間。另外,您可以添加更多的日誌語句(帶時間戳)來查看延遲的位置。 – pcarter

+0

不幸的是,我似乎無法複製延遲,現在我在不同的機器上,比我最初。這可能是處理器之間的差異(都是四核)嗎?我會嘗試從原始機器獲取日誌。 – Touchdown

回答

1

這個問題相當棘手,1.5秒很可能涉及鎖定問題。

  • 順序:

    運行代碼後150毫秒

  • 並行:57毫秒(2×2,4個線程)

現在每個處理線程做了很多事情:

  • 在像素級訪問圖像(由於各種原因,這是非常耗費資源的操作)
  • 寫入文件
  • 進行JPEG壓縮

我建議從實際處理的文件寫入加上JPEG編碼隔離和恢復您的測量。

如果你有4個線程,現在你遇到4次JPEG編碼,4次並行文件寫入,這可能會產生問題。我使用的是SSD,所以文件寫入沒有任何區別,但在HDD上可以產生影響。

請注意,使用比物理內核更多的線程不會使並行操作更快,但只會增加額外的開銷。 另請注意,如果圖片太小,「平行」線程不能並行工作。 Are parallel drawing operations possible with Java Graphics2d?,因爲你正在使用四種不同的bufferedimages從四個不同的線程,這並不影響你的表現:相反,而你只是tarting線3

雖然AWT強加一個BufferedImage鎖定的第一個線程已經完成。

所以,你的想法將工作。但是如果計算速度很快,那麼4個線程的性能改進就太小了。儘量不要測量你無法控制的操作(例如,根據你的硬件和當前的虛擬內存條件,文件IO性能可以是任何東西)。

+0

HDD和SSD之間的差異並不是我認爲的 - 我現在在SSD上,但原來的機器有一個硬盤,這可能會導致一些「看不見的」問題。我會把這個文件寫出來,並做一些其他的事情,看看我能夠加快速度。 – Touchdown