2016-02-15 44 views
5

Gần đây tôi đã bắt đầu đa luồng lerning trong java. Kể từ khi tôi đang viết một chương trình để tính toán số tại Đại học của tôi, tôi quyết định thực hiện một số nỗ lực đầu tiên bằng cách lập trình nhân ma trận đa luồng.Nhân ma trận đa luồng

Đây là mã của tôi. Hãy ghi nhớ rằng điều này đã được thực hiện như là một nỗ lực đầu tiên và không phải là rất sạch sẽ.

public class MultithreadingTest{ 

     public static void main(String[] args) { 
      // TODO Auto-generated method stub 
      double[][] matrix1 = randomSquareMatrix(2000); 
      double[][] matrix2 = randomSquareMatrix(2000); 

      matrixMultiplication(matrix1,matrix2,true); 
      matrixMultiplicationSingleThread(matrix1, matrix2); 
      try { 
       matrixMultiplicationParallel(matrix1,matrix2, true); 
      } catch (InterruptedException | ExecutionException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
      try { 
       matrixMultiplicationParallel2(matrix1,matrix2, true); 
      } catch (InterruptedException | ExecutionException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 

     } 

     public static double[][] randomSquareMatrix(int n){ 
      double[][] mat = new double[n][n]; 
      Random rand = new Random(); 
      for(int i=0; i<n; i++) for(int j=0; j<n; j++) mat[i][j]=rand.nextInt(10); 
      return mat; 
     } 
     public static void printSquareMat(double[][] mat){ 
      int n=mat.length; 
      for(int i=0; i<n; i++){ for(int j=0; j<n; j++) System.out.print(mat[i][j]+" "); System.out.print("\n");} 
      System.out.print("\n"); 
     } 

     public static void average(double[][] matrix) 
     { 
      int n=matrix.length; 
      double sum=0; 
      for(int i=0; i<n; i++) for(int j=0; j<n; j++) sum+=matrix[i][j]; 

      System.out.println("Average of all Elements of Matrix : "+(sum/(n*n))); 
     } 

     public static void matrixMultiplication(double[][] matrix1, double[][] matrix2, boolean printMatrix){ 

      int n=matrix1.length; 
      double[][] resultMatrix = new double[n][n]; 

      double startTime = System.currentTimeMillis(); 

      for(int i=0; i<n; i++)for(int j=0; j<n; j++)for(int k=0; k<n; k++) resultMatrix[i][j]+=matrix1[i][k]*matrix2[k][j]; 


      if (printMatrix && n<=5)for(int i=0; i<n; i++){for(int j=0; j<n; j++) System.out.print(resultMatrix[i][j]+" ");System.out.print("\n"); } 

      System.out.print("\n"); 
      System.out.println(((System.currentTimeMillis()-startTime)/1000)+ 
        " seconds for matrix of size "+n+" in main thread."); 
      average(resultMatrix); 
     } 

     public static void matrixMultiplicationSingleThread(double[][] m1, double[][] m2) 
     { 
      int n=m1.length; 
      double startTime = System.currentTimeMillis(); 
      Thread t = new Thread(new multiSingle(m1,m2)); 
      t.start(); 
      try { 
       t.join(); 
      } catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       System.out.println("Error"); 
       e.printStackTrace(); 
      } 
      System.out.print("\n"); 
      System.out.println(((System.currentTimeMillis()-startTime)/1000)+ 
        " seconds for matrix of size "+n+" in external Thread."); 

     } 

     public static void matrixMultiplicationParallel(double[][] matrix1, double[][] matrix2, boolean printMatrix) throws InterruptedException, ExecutionException{ 

      int n=matrix1.length; 
      double[][] resultMatrix=new double[n][n]; 
      double tmp; 
      ExecutorService exe = Executors.newFixedThreadPool(2); 
      Future<Double>[][] result = new Future[n][n]; 
      double startTime = System.currentTimeMillis(); 
      for(int i=0; i<n; i++) 
      { 
       for(int j=0; j<=i; j++) 
       { 
        tmp=matrix2[i][j]; 
        matrix2[i][j]=matrix2[j][i]; 
        matrix2[j][i]=tmp; 
       } 
      } 

      for(int i=0; i<n; i++) 
      { 
       for(int j=0; j<n; j++) 
       { 
        result[i][j] = exe.submit(new multi(matrix1[i],matrix2[j])); 
       } 
      } 

      exe.shutdown(); 
      exe.awaitTermination(1, TimeUnit.DAYS); 

      for(int i=0; i<n; i++) 
      { 
       for(int j=0; j<n; j++) 
       { 
        resultMatrix[i][j] = result[i][j].get(); 
       } 
      } 
      for(int i=0; i<n; i++) 
      { 
       for(int j=0; j<=i; j++) 
       { 
        tmp=matrix2[i][j]; 
        matrix2[i][j]=matrix2[j][i]; 
        matrix2[j][i]=tmp; 
       } 
      } 
      if (printMatrix && n<=5)for(int i=0; i<n; i++){for(int j=0; j<n; j++) System.out.print(resultMatrix[i][j]+" ");System.out.print("\n"); } 

      System.out.print("\n"); 
      System.out.println(((System.currentTimeMillis()-startTime)/1000)+ 
        " seconds for matrix of size "+n+" multithreaded with algorithm 1."); 
      average(resultMatrix); 
     } 

     public static void matrixMultiplicationParallel2(double[][] matrix1, double[][] matrix2, boolean printMatrix) throws InterruptedException, ExecutionException{ 

      int n=matrix1.length; 
      double[][] resultMatrix=new double[n][n]; 
      double tmp; 
      ExecutorService exe = Executors.newFixedThreadPool(2); 
      Future<Double>[][] result = new Future[n][n]; 
      double startTime = System.currentTimeMillis(); 


      for(int i=0; i<n; i++) 
      { 
       for(int j=0; j<n; j++) 
       { 
        result[i][j] = exe.submit(new multi2(i,j,matrix1,matrix2)); 
       } 
      } 

      exe.shutdown(); 

      exe.awaitTermination(1, TimeUnit.DAYS); 


      for(int i=0; i<n; i++) 
      { 
       for(int j=0; j<n; j++) 
       { 
        resultMatrix[i][j] = result[i][j].get(); 
       } 
      } 

      if (printMatrix && n<=5)for(int i=0; i<n; i++){for(int j=0; j<n; j++) System.out.print(resultMatrix[i][j]+" ");System.out.print("\n"); } 

      System.out.print("\n"); 
      System.out.println(((System.currentTimeMillis()-startTime)/1000)+ 
        " seconds for matrix of size "+n+" multithreaded with algorithm 2."); 
      average(resultMatrix); 
     } 

     public static class multi implements Callable<Double>{ 

      multi(double[] vec1, double[] vec2){ 
       this.vec1=vec1; this.vec2=vec2; 
      } 
      double result; 
      double[] vec1, vec2; 

      @Override 
      public Double call() { 
       result=0; 
       for(int i=0; i<vec1.length; i++) result+=vec1[i]*vec2[i]; 
       return result; 
      } 
     } 

     public static class multi2 implements Callable<Double>{ 

      multi2(int a, int b, double[][] vec1, double[][] vec2){ 
       this.a=a; this.b=b; this.vec1=vec1; this.vec2=vec2; 
      } 
      int a,b; 
      double result; 
      double[][] vec1, vec2; 

      @Override 
      public Double call() { 
       result=0; 
       for(int i=0; i<vec1.length; i++) result+=vec1[a][i]*vec2[i][b]; 
       return result; 
      } 
     } 

     public static class multiSingle implements Runnable{ 

      double[][] matrix1, matrix2; 

      multiSingle(double[][] m1, double[][] m2){ 
       matrix1=m1; 
       matrix2=m2; 
      } 
      public static void matrixMultiplication(double[][] matrix1, double[][] matrix2, boolean printMatrix){ 

       int n=matrix1.length; 
       double[][] resultMatrix = new double[n][n]; 

       for(int i=0; i<n; i++)for(int j=0; j<n; j++)for(int k=0; k<n; k++) resultMatrix[i][j]+=matrix1[i][k]*matrix2[k][j]; 

       MultithreadingTest.average(resultMatrix); 
      } 

      @Override 
      public void run() { 
       matrixMultiplication(matrix1, matrix2, false); 
      } 
     } 

    } 

Tôi có hai câu hỏi chung về đa luồng, tôi hy vọng không sao khi mở một chủ đề mới cho việc này.

  1. Có cách nào để viết mã mà không có lớp bổ sung cho các chủ đề triển khai runnable hoặc có thể gọi được không? Tôi đã xem xét các cách tiếp cận bằng cách sử dụng các lớp bên trong vô danh và lambdas, nhưng theo như tôi có thông tin chi tiết, tôi không thể chuyển các tham số cho luồng theo cách này vì run() và call() không thực hiện bất kỳ tham số nào. là cuối cùng. Nhưng giả sử tôi viết một lớp cho các hoạt động ma trận, tôi không muốn viết một lớp bổ sung cho mọi hoạt động mà tôi muốn chạy trong một luồng.
  2. Giả sử rằng lớp của tôi thực hiện nhiều thao tác đa luồng, tạo một nhóm luồng mới và tắt nó theo mọi phương thức sẽ lãng phí rất nhiều tài nguyên, tôi đoán vậy. Vì vậy, tôi muốn tạo một hồ bơi thread là thành viên ob lớp của tôi, instantiating nó khi cần thiết và sử dụng invokeAll. Nhưng điều gì sẽ xảy ra nếu đối tượng của tôi bị xóa? Tôi sẽ gặp vấn đề vì tôi không bao giờ tắt hồ bơi thread? Trong C++ tôi sẽ sử dụng một destructor cho việc này. Hoặc gc có chăm sóc mọi thứ trong trường hợp này không?

Bây giờ tác quốc tế về mã của tôi trực tiếp:

tôi thực hiện phép nhân ma trận trong bốn cách khác nhau, như phương pháp chạy trong chủ đề chính của tôi, như phương pháp chạy trong một chủ đề mới, nhưng vẫn không đa luồng (để đảm bảo có sẽ không phải là bất kỳ nền taks trong chủ đề chính của tôi làm chậm nó xuống), và hai cách khác nhau của nhân ma trận đa luồng. Phiên bản đầu tiên chuyển đổi ma trận thứ hai, gửi phép nhân như vector-vector nhân và chuyển ma trận trở lại dạng ban đầu của nó. Phiên bản thứ hai lấy ma trận trực tiếp và additinaly có hai chỉ số để xác định hàng và cột của ma trận cho phép nhân vectơ-vector.

Đối với tất cả các phiên bản tôi đã đo thời gian cần thiết để nhân và tính toán giá trị trung bình af ma trận kết quả để xem liệu các kết quả có giống nhau hay không.

Tôi đã chạy mã này trên hai máy tính, cả hai cùng một JVM và cả Windows 10. Đầu tiên là Laptop, i5 thế hệ thứ 5, 2,6 Ghz lõi kép và thứ hai trong máy tính để bàn, i5 Thế hệ thứ 4, lõi tứ 4,2 Ghz.

Tôi dự kiến ​​Desktop-PC của tôi sẽ nhanh hơn nhiều. Tôi cũng dự kiến ​​các phiên bản đa luồng sẽ mất khoảng một nửa/quý thời gian của phiên bản ren signle, nhưng vẫn còn nhiều hơn kể từ khi có thêm công việc để tạo ra các chủ đề vv Và cuối cùng, tôi mong đợi phiên bản đa luồng thứ hai, mà không transpose một ma trận hai lần, để được nhanh hơn, vì có ít hoạt động hơn.

Sau khi chạy đoạn code tôi là một chút nhầm lẫn về kết quả, tôi hy vọng ai đó có thể giải thích cho tôi:

Đối với cả hai phương pháp đơn luồng Laptop của tôi cần khoảng 340s (đối với một kích thước ma trận của 3000). Vì vậy, tôi giả sử không có nhiệm vụ nền đắt tiền được thực hiện trong chủ đề chính của tôi. Desktop PC của tôi mặt khác cần 440s. Bây giờ câu hỏi là, tại sao máy tính xách tay của tôi, mà là defenitely chậm hơn, nhanh hơn nhiều? Ngay cả khi thế hệ thứ năm nhanh hơn thế hệ thứ 4, kể từ khi máy tính để bàn của tôi chạy với tốc độ gấp 1,6 lần tốc độ máy tính xách tay của tôi, tôi vẫn mong đợi nó sẽ nhanh hơn.Sự khác biệt giữa các thế hệ này không có gì lớn.

Đối với các phương pháp đa luồng, Máy tính xách tay của tôi cần khoảng 34 giây. Nếu đa luồng sẽ là hoàn hảo, thì nó không nên mất ít hơn một nửa. Tại sao nó trên hai chủ đề nhanh hơn mười lần? Cũng vậy với máy tính để bàn của tôi. Sử dụng bốn chủ đề nhân được thực hiện trong 16s thay vì 440s. Điều này giống như Desktop PC của tôi đang làm việc với tốc độ tương tự như máy tính xách tay của tôi, chỉ trên bốn thay vì hai chủ đề.

Bây giờ để so sánh giữa hai phương pháp đa luồng, phiên bản chuyển đổi một ma trận hai lần mất khoảng 34 giây trên Máy tính xách tay của tôi, phiên bản lấy ma trận trực tiếp mất khoảng 200 giây. Điều đó nghe có vẻ thực tế, vì nó nhiều hơn một nửa phương pháp luồng đơn. Nhưng tại sao nó chậm hơn nhiều so với phiên bản đầu tiên? Tôi sẽ giả định rằng transposing một ma trận hai lần sẽ chậm hơn thời gian bổ sung để có được các yếu tố ma trận? Có cái gì tôi đang thiếu hoặc đang làm việc với một ma trận thực sự chậm hơn nhiều so với làm việc với một vector?

Tôi hy vọng ai đó có thể trả lời những câu hỏi này. Xin lỗi vì đã viết một bài đăng dài như vậy.

Trân trọng Thorsten

Trả lời

2

Câu trả lời cho bí ẩn lớn này: Thời gian cần thiết để thực hiện phép nhân ma trận bị chi phối bởi thời gian di chuyển dữ liệu từ RAM sang bộ đệm CPU. Bạn có thể có 4 lõi, nhưng bạn chỉ có 1 bus RAM, vì vậy bạn sẽ không nhận được bất kỳ lợi ích nào bằng cách sử dụng nhiều lõi hơn (đa luồng) nếu tất cả chúng đều chặn nhau chờ truy cập bộ nhớ.

Thử nghiệm đầu tiên bạn nên thử là: Viết phiên bản đơn luồng bằng cách sử dụng phép nhân bản chuyển tiếp và nhân vectơ. Bạn sẽ thấy rằng nó là MUCH nhanh hơn - có lẽ là nhanh như phiên bản đa luồng với transpose.

Lý do tại sao phiên bản đơn luồng gốc quá chậm, là do nó phải tải khối bộ nhớ cache cho mỗi ô trong cột đang được nhân lên. Nếu bạn sử dụng ma trận transpose, thì tất cả các ô đó tuần tự trong bộ nhớ, và tải một khối sẽ cho bạn một bó của chúng. Vì vậy, nếu bạn muốn tối ưu hóa phép nhân, truy cập bộ nhớ tối ưu hóa đầu tiên cho hiệu quả bộ nhớ cache, THEN chia công việc giữa một vài luồng - không quá gấp đôi số chủ đề như bạn có lõi. Bất cứ điều gì chỉ lãng phí thời gian và tài nguyên với các công tắc ngữ cảnh, v.v.

liên quan đến câu hỏi khác của bạn:

1) Đó là thuận tiện để sử dụng lambdas rằng chụp biến từ phạm vi tạo ra chúng, như:

for(int i=0; i<n; i++) 
{ 
    for(int j=0; j<n; j++) 
    { 
     final double[] v1 = matrix1[i]; 
     final double[] v2 = matrix2[j]; 
     result[i][j] = exe.submit(() -> vecdot(v1,v2)); 
    } 
} 

2) Các GC sẽ chăm sóc nó. Bạn không cần phải tắt hồ sơ luồng một cách rõ ràng để giải phóng bất kỳ tài nguyên nào.

+0

Tôi đã thử phiên bản đơn luồng - transposed. 3 giây chậm hơn so với đa luồng trên máy tính xách tay của tôi –

1

Bạn phải cẩn thận để giảm thiểu chi phí của việc tạo ra các chủ đề. Một thẩm định tốt là sử dụng khung ForkJoin để phân tách vấn đề bằng cách sử dụng một nhóm luồng. Khung này

  • sử dụng lại một nhóm chủ đề hiện có.
  • chia nhỏ nhiệm vụ cho đến khi có đủ để giữ cho hồ bơi bận rộn nhưng không còn nữa.

Chỉ có một đơn vị Điểm nổi trên mỗi lõi để khả năng mở rộng của bạn sẽ dựa trên số lõi bạn có.

Tôi đề nghị bạn đọc Fork Join Matrix Multiplication in Java Tôi không thể tìm thấy nguồn gốc của mã này.

http://gee.cs.oswego.edu/dl/papers/fj.pdf

http://gee.cs.oswego.edu/dl/cpjslides/fj.pdf về cách sử dụng khuôn khổ ForkJoin.

+1

Xin chào, Cảm ơn bạn đã biết thông tin, tôi sẽ xem xét khuôn khổ này. Về bộ vi xử lý, những gì tôi đã phát hiện ra cho đến nay là thế hệ thứ 5 chủ yếu là phiên bản thu nhỏ của thế hệ thứ 4 (22nm-> 14nm), và rằng nó nên hiệu suất tốt hơn khoảng 5%. Nhưng cả hai đều có turbo tăng, và tốc độ đồng hồ tôi đã đưa ra là tốc độ tối đa với turbo tăng. Họ normaly chạy ở 2 và 3,4 GHz. –

+0

@ThorstenSchmitz Bạn có thể đúng, tôi đã tìm thấy Skylake (gen thứ 6) nhanh hơn tới 20% so với Haswell cho cùng một GHz. Tôi đã không cố gắng rộng rãi. –