Làm thế nào để BLAS có được hiệu suất cao như vậy?


108

Vì tò mò, tôi quyết định so sánh chức năng nhân ma trận của riêng mình so với việc triển khai BLAS ... Tôi phải nói rằng ít ngạc nhiên nhất về kết quả:

Triển khai tùy chỉnh, 10 thử nghiệm của phép nhân ma trận 1000x1000:

Took: 15.76542 seconds.

Thực hiện BLAS, 10 thử nghiệm nhân ma trận 1000x1000:

Took: 1.32432 seconds.

Điều này đang sử dụng số dấu chấm động chính xác duy nhất.

Thực hiện của tôi:

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
    if ( ADim2!=BDim1 )
        throw std::runtime_error("Error sizes off");

    memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
    int cc2,cc1,cr1;
    for ( cc2=0 ; cc2<BDim2 ; ++cc2 )
        for ( cc1=0 ; cc1<ADim2 ; ++cc1 )
            for ( cr1=0 ; cr1<ADim1 ; ++cr1 )
                C[cc2*ADim2+cr1] += A[cc1*ADim1+cr1]*B[cc2*BDim1+cc1];
}

Tôi có hai câu hỏi:

  1. Cho rằng phép nhân ma trận cho biết: nxm * mxn yêu cầu n * n * m phép nhân, vì vậy trong trường hợp trên 1000 ^ 3 hoặc 1e9 phép toán. Làm cách nào để BLAS có thể thực hiện các hoạt động 10 * 1e9 trong 1,32 giây trên bộ xử lý 2.6Ghz của tôi? Ngay cả khi các phép ghép chỉ là một thao tác đơn lẻ và không có gì khác được thực hiện, nó sẽ mất ~ 4 giây.
  2. Tại sao quá trình triển khai của tôi chậm hơn nhiều?

17
BLAS đã được tối ưu hóa mặt này và mặt kia bởi các chuyên gia trong lĩnh vực này. Tôi cho rằng đó là lợi dụng việc SIMD floating point unit trên chip của bạn và chơi rất nhiều thủ thuật để cải thiện hành vi bộ nhớ đệm cũng ...
dmckee --- cựu điều hành kitten

3
Vẫn làm cách nào để bạn thực hiện các hoạt động 1E10 trên bộ xử lý 2,63E9 chu kỳ / giây trong 1,3 giây?
DeusAduro

9
Nhiều đơn vị thực thi, lớp lót ống và Nhiều dữ liệu lệnh đơn ((SIMD) có nghĩa là thực hiện cùng một thao tác trên nhiều cặp toán hạng cùng một lúc). Một số trình biên dịch có thể nhắm mục tiêu các đơn vị SIMD trên các chip thông thường nhưng bạn luôn phải bật một cách rõ ràng và điều đó rất hữu ích để biết tất cả hoạt động của nó ( en.wikipedia.org/wiki/SIMD ). Đảm bảo chống lại các lần bỏ sót bộ nhớ cache gần như chắc chắn là một phần khó.
dmckee --- mèo con người điều hành cũ

13
Giả định là sai. Có nhiều thuật toán tốt hơn được biết đến, xem Wikipedia.
MSalters

2
@DeusAduro: Trong câu trả lời của tôi cho Làm thế nào để viết một sản phẩm ma trận ma trận có thể cạnh tranh với Eigen? Tôi đã đăng một ví dụ nhỏ về cách triển khai một sản phẩm ma trận hiệu quả bộ nhớ cache.
Michael Lehn

Câu trả lời:


141

Một điểm khởi đầu tốt là cuốn sách tuyệt vời Khoa học về tính toán ma trận lập trình của Robert A. van de Geijn và Enrique S. Quintana-Ortí. Họ cung cấp một phiên bản tải xuống miễn phí.

BLAS được chia thành ba cấp độ:

  • Mức 1 xác định một tập hợp các hàm đại số tuyến tính chỉ hoạt động trên vectơ. Các chức năng này được hưởng lợi từ vectơ hóa (ví dụ như từ việc sử dụng SSE).

  • Các hàm cấp 2 là các phép toán vectơ-ma trận, ví dụ một số tích vectơ-ma trận. Các chức năng này có thể được thực hiện theo các chức năng Cấp 1. Tuy nhiên, bạn có thể tăng hiệu suất của các chức năng này nếu bạn có thể cung cấp một triển khai chuyên dụng sử dụng một số kiến ​​trúc đa xử lý với bộ nhớ dùng chung.

  • Các hàm cấp 3 là các hoạt động giống như sản phẩm ma trận-ma trận. Một lần nữa, bạn có thể triển khai chúng theo các chức năng Cấp 2. Nhưng các hàm Level 3 thực hiện các phép toán O (N ^ 3) trên dữ liệu O (N ^ 2). Vì vậy, nếu nền tảng của bạn có hệ thống phân cấp bộ đệm thì bạn có thể tăng hiệu suất nếu bạn cung cấp một triển khai chuyên dụng được tối ưu hóa bộ đệm / thân thiện với bộ đệm . Điều này được mô tả độc đáo trong cuốn sách. Sự thúc đẩy chính của các chức năng Level3 đến từ việc tối ưu hóa bộ nhớ cache. Sự gia tăng này vượt quá đáng kể mức tăng thứ hai từ tính năng song song và các tối ưu hóa phần cứng khác.

Nhân tiện, hầu hết (hoặc thậm chí tất cả) việc triển khai BLAS hiệu suất cao KHÔNG được triển khai trong Fortran. ATLAS được triển khai trong C. GotoBLAS / OpenBLAS được triển khai trong C và các phần quan trọng về hiệu suất của nó trong Assembler. Chỉ triển khai tham chiếu của BLAS được thực hiện trong Fortran. Tuy nhiên, tất cả các triển khai BLAS này cung cấp một giao diện Fortran để nó có thể được liên kết với LAPACK (LAPACK nhận được tất cả hiệu suất của nó từ BLAS).

Các trình biên dịch được tối ưu hóa đóng một vai trò nhỏ trong khía cạnh này (và đối với GotoBLAS / OpenBLAS, trình biên dịch không quan trọng chút nào).

Triển khai IMHO no BLAS sử dụng các thuật toán như thuật toán Coppersmith – Winograd hoặc thuật toán Strassen. Tôi không chắc chắn chính xác về lý do, nhưng đây là suy đoán của tôi:

  • Có thể không thể cung cấp triển khai tối ưu hóa bộ nhớ cache của các thuật toán này (tức là bạn sẽ mất nhiều thời gian hơn thì bạn sẽ thắng)
  • Các thuật toán này không ổn định về mặt số học. Vì BLAS là hạt nhân tính toán của LAPACK, đây là điều không nên làm.

Chỉnh sửa / Cập nhật:

Bài báo mới và mang tính đột phá cho chủ đề này là bài báo BLIS . Chúng được viết rất tốt. Đối với bài giảng của tôi "Khái niệm cơ bản về phần mềm cho máy tính hiệu suất cao", tôi đã triển khai sản phẩm ma trận-ma trận sau bài báo của họ. Trên thực tế, tôi đã triển khai một số biến thể của sản phẩm ma trận. Các biến thể đơn giản nhất được viết hoàn toàn bằng C đơn giản và có ít hơn 450 dòng mã. Tất cả các biến thể khác chỉ tối ưu hóa các vòng lặp

    for (l=0; l<MR*NR; ++l) {
        AB[l] = 0;
    }
    for (l=0; l<kc; ++l) {
        for (j=0; j<NR; ++j) {
            for (i=0; i<MR; ++i) {
                AB[i+j*MR] += A[i]*B[j];
            }
        }
        A += MR;
        B += NR;
    }

Hiệu suất tổng thể của sản phẩm ma trận chỉ phụ thuộc vào các vòng lặp này. Khoảng 99,9% thời gian được dành ở đây. Trong các biến thể khác, tôi đã sử dụng bản chất và mã trình hợp dịch để cải thiện hiệu suất. Bạn có thể xem hướng dẫn về tất cả các biến thể tại đây:

ulmBLAS: Hướng dẫn về GEMM (Sản phẩm Ma trận-Ma trận)

Cùng với các bài báo BLIS, khá dễ hiểu làm thế nào các thư viện như Intel MKL có thể đạt được hiệu suất như vậy. Và tại sao nó không quan trọng cho dù bạn sử dụng lưu trữ chính hàng hay cột!

Các điểm chuẩn cuối cùng ở đây (chúng tôi gọi là dự án của mình là ulmBLAS):

Điểm chuẩn cho ulmBLAS, BLIS, MKL, openBLAS và Eigen

Chỉnh sửa / Cập nhật khác:

Tôi cũng đã viết một số hướng dẫn về cách BLAS được sử dụng cho các bài toán đại số tuyến tính số như giải một hệ phương trình tuyến tính:

Hệ số hóa LU hiệu suất cao

(Ví dụ, phép phân tích LU này được Matlab sử dụng để giải hệ phương trình tuyến tính.)

Tôi hy vọng sẽ có thời gian để mở rộng hướng dẫn để mô tả và trình bày cách thực hiện song song khả năng mở rộng cao của phép phân tích nhân tử LU như trong PLASMA .

Ok, bạn bắt đầu: Mã hóa bộ nhớ cache tối ưu hóa LU song song thừa số

Tái bút: Tôi cũng đã thực hiện một số thử nghiệm về việc cải thiện hiệu suất của uBLAS. Nó thực sự khá đơn giản để tăng (vâng, chơi chữ :))) hiệu suất của uBLAS:

Thử nghiệm trên uBLAS .

Đây là một dự án tương tự với BLAZE :

Thử nghiệm trên BLAZE .


3
Liên kết mới tới “Điểm chuẩn cho ulmBLAS, BLIS, MKL, openBLAS và Eigen”: apfel.mathematik.uni-ulm.de/~lehn/ulmBLAS/#toc3
Ahmed Fasih

Hóa ra ESSL của IBM sử dụng một biến thể của thuật toán Strassen - ibm.com/support/knowledgecenter/en/SSFHY8/essl_welcome.html
ben-albrecht

2
hầu hết các liên kết đã chết
Aurélien Pierre

Có thể tìm thấy bản PDF của TSoPMC trên trang của tác giả, tại cs.utexas.edu/users/rvdg/tmp/TSoPMC.pdf
Alex Shpilkin

Mặc dù thuật toán Coppersmith-Winograd có độ phức tạp về thời gian khá tốt trên giấy, nhưng ký hiệu Big O ẩn một hằng số rất lớn, vì vậy nó chỉ bắt đầu khả thi đối với những ma trận lớn đến kỳ lạ.
DiehardTheTryhard

26

Vì vậy, trước hết BLAS chỉ là một giao diện gồm khoảng 50 chức năng. Có nhiều triển khai cạnh tranh của giao diện.

Đầu tiên tôi sẽ đề cập đến những thứ phần lớn không liên quan:

  • Fortran vs C, không có gì khác biệt
  • Các thuật toán ma trận nâng cao như Strassen, triển khai không sử dụng chúng vì chúng không giúp ích gì trong thực tế

Hầu hết các triển khai chia nhỏ mỗi phép toán thành các phép toán vectơ hoặc ma trận kích thước nhỏ theo cách ít nhiều rõ ràng hơn. Ví dụ: một phép nhân ma trận lớn 1000x1000 có thể được chia thành một chuỗi các phép nhân ma trận 50x50.

Các hoạt động kích thước nhỏ có kích thước cố định này (được gọi là hạt nhân) được mã hóa cứng trong mã lắp ráp dành riêng cho CPU bằng cách sử dụng một số tính năng CPU của mục tiêu của chúng:

  • Hướng dẫn kiểu SIMD
  • Mức độ hướng dẫn Song song
  • Nhận biết bộ nhớ cache

Hơn nữa, các hạt nhân này có thể được thực thi song song với nhau bằng cách sử dụng nhiều luồng (lõi CPU), theo mô hình thiết kế rút gọn bản đồ điển hình.

Hãy xem ATLAS là cách triển khai BLAS mã nguồn mở được sử dụng phổ biến nhất. Nó có nhiều hạt nhân cạnh tranh khác nhau và trong quá trình xây dựng thư viện ATLAS, nó chạy một cuộc cạnh tranh giữa chúng (một số thậm chí còn được tham số hóa, vì vậy cùng một hạt nhân có thể có các cài đặt khác nhau). Nó thử các cấu hình khác nhau và sau đó chọn những cấu hình tốt nhất cho hệ thống mục tiêu cụ thể.

(Mẹo: Đó là lý do tại sao nếu bạn đang sử dụng ATLAS, tốt hơn hết bạn nên xây dựng và điều chỉnh thư viện bằng tay cho máy cụ thể của mình sau đó sử dụng máy được tạo sẵn.)


ATLAS không còn là cách triển khai BLAS mã nguồn mở được sử dụng phổ biến nhất. Nó đã bị vượt qua bởi OpenBLAS (một nhánh của GotoBLAS) và BLIS (một cấu trúc lại của GotoBLAS).
Robert van de Geijn

1
@ ulaff.net: Điều đó có thể. Điều này đã được viết cách đây 6 năm. Tôi nghĩ rằng việc triển khai BLAS nhanh nhất hiện tại (tất nhiên là trên Intel) là Intel MKL, nhưng nó không phải là mã nguồn mở.
Andrew Tomazos

14

Đầu tiên, có những thuật toán nhân ma trận hiệu quả hơn thuật toán bạn đang sử dụng.

Thứ hai, CPU của bạn có thể thực hiện nhiều hơn một lệnh cùng một lúc.

CPU của bạn thực hiện 3-4 lệnh mỗi chu kỳ và nếu các đơn vị SIMD được sử dụng, mỗi lệnh sẽ xử lý 4 phao hoặc 2 lệnh gấp đôi. (tất nhiên con số này cũng không chính xác, vì CPU thường chỉ có thể xử lý một lệnh SIMD mỗi chu kỳ)

Thứ ba, mã của bạn không phải là tối ưu:

  • Bạn đang sử dụng con trỏ thô, có nghĩa là trình biên dịch phải cho rằng chúng có thể là bí danh. Bạn có thể chỉ định các từ khóa hoặc cờ cụ thể cho trình biên dịch để thông báo cho trình biên dịch biết rằng chúng không phải là bí danh. Ngoài ra, bạn nên sử dụng các loại khác với con trỏ thô, chúng sẽ giải quyết vấn đề.
  • Bạn đang xóa bộ nhớ cache bằng cách thực hiện duyệt qua từng hàng / cột của ma trận đầu vào. Bạn có thể sử dụng tính năng chặn để thực hiện nhiều công việc nhất có thể trên một khối ma trận nhỏ hơn, phù hợp với bộ nhớ cache của CPU, trước khi chuyển sang khối tiếp theo.
  • Đối với các tác vụ số thuần túy, Fortran khá bất khả chiến bại và C ++ phải mất rất nhiều thời gian để đạt được tốc độ tương tự. Nó có thể được thực hiện và có một số thư viện chứng minh nó (thường sử dụng các mẫu biểu thức), nhưng nó không phải là tầm thường và nó không chỉ xảy ra.

Cảm ơn, tôi đã thêm mã hạn chế chính xác theo đề xuất của Justicle, không thấy nhiều cải thiện, tôi thích ý tưởng theo khối. Vì tò mò, mà không biết kích thước bộ nhớ cache của CPU làm thế nào để có một mã tối ưu đúng?
DeusAduro

2
Bạn không. Để có được mã tối ưu, bạn cần biết kích thước bộ nhớ cache của CPU. Tất nhiên nhược điểm của điều này là bạn đang mã hóa mã của mình một cách hiệu quả để có hiệu suất tốt nhất trên một họ CPU.
jalf

2
Ít nhất thì vòng lặp bên trong ở đây tránh được tải xếp chồng lên nhau. Có vẻ như điều này được viết cho một ma trận đã được chuyển vị. Đó là lý do tại sao nó "chỉ" chậm hơn BLAS một bậc! Nhưng đúng vậy, nó vẫn hoạt động mạnh vì thiếu tính năng chặn bộ nhớ cache. Bạn có chắc Fortran sẽ giúp được nhiều không? Tôi nghĩ rằng tất cả những gì bạn đạt được ở đây là restrict(không có răng cưa) là mặc định, không giống như trong C / C ++. (Và tiếc là ISO C ++ không có restricttừ khóa, vì vậy bạn phải sử dụng __restrict__trên các trình biên dịch cung cấp nó dưới dạng phần mở rộng).
Peter Cordes

11

Tôi không biết cụ thể về việc triển khai BLAS nhưng có những thuật số hiệu quả hơn cho Phép nhân ma trận có độ phức tạp tốt hơn O (n3). Một điều biết rõ là Thuật toán Strassen


8
Thuật toán Strassen không được sử dụng ở dạng số vì hai lý do: 1) Nó không ổn định. 2) Bạn tiết kiệm được một số tính toán nhưng điều đó đi kèm với cái giá là bạn có thể khai thác cấu trúc phân cấp bộ nhớ cache. Trong thực tế, bạn thậm chí còn lỏng lẻo hiệu suất.
Michael Lehn

4
Để triển khai thực tế Thuật toán Strassen được xây dựng chặt chẽ dựa trên mã nguồn thư viện BLAS, có một ấn phẩm gần đây: " Strassen Algorithm Reloaded " trong SC16, đạt được hiệu suất cao hơn BLAS, ngay cả đối với kích thước bài toán 1000x1000.
Jianyu Huang

4

Hầu hết các đối số cho câu hỏi thứ hai - trình hợp ngữ, chia thành các khối, v.v. (nhưng không phải thuật toán N ^ 3, chúng thực sự quá phát triển) - đóng một vai trò nào đó. Nhưng tốc độ thấp của thuật toán của bạn về cơ bản là do kích thước ma trận và sự sắp xếp không may của ba vòng lặp lồng nhau. Ma trận của bạn quá lớn nên chúng không vừa trong bộ nhớ đệm. Bạn có thể sắp xếp lại các vòng lặp sao cho càng nhiều càng tốt sẽ được thực hiện trên một hàng trong bộ nhớ cache, bằng cách này giúp giảm đáng kể việc làm mới bộ nhớ cache (việc chia BTW thành các khối nhỏ có hiệu ứng tương tự, tốt nhất nếu các vòng lặp trên các khối được sắp xếp giống nhau). Sau đây là một mô hình triển khai cho ma trận vuông. Trên máy tính của tôi, thời gian tiêu thụ của nó là khoảng 1:10 so với việc triển khai tiêu chuẩn (như của bạn). Nói cách khác: không bao giờ lập trình nhân ma trận dọc theo dấu "

    void vector(int m, double ** a, double ** b, double ** c) {
      int i, j, k;
      for (i=0; i<m; i++) {
        double * ci = c[i];
        for (k=0; k<m; k++) ci[k] = 0.;
        for (j=0; j<m; j++) {
          double aij = a[i][j];
          double * bj = b[j];
          for (k=0; k<m; k++)  ci[k] += aij*bj[k];
        }
      }
    }

Một nhận xét nữa: Việc triển khai này thậm chí còn tốt hơn trên máy tính của tôi so với việc thay thế tất cả bằng cblas_dgemm theo quy trình BLAS (hãy thử nó trên máy tính của bạn!). Nhưng nhanh hơn nhiều (1: 4) là gọi trực tiếp dgemm_ của thư viện Fortran. Tôi nghĩ rằng quy trình này trên thực tế không phải là Fortran mà là mã của trình hợp dịch (tôi không biết có gì trong thư viện, tôi không có nguồn). Tôi hoàn toàn không rõ là tại sao cblas_dgemm không nhanh như vậy vì theo hiểu biết của tôi, nó chỉ là một trình bao bọc cho dgemm_.


3

Đây là một tốc độ thực tế. Để biết ví dụ về những gì có thể được thực hiện với trình lắp ráp SIMD qua mã C ++, hãy xem một số ví dụ về hàm ma trận của iPhone - chúng nhanh hơn 8 lần so với phiên bản C và thậm chí không được lắp ráp "tối ưu hóa" - chưa có ống lót và ở đó là các hoạt động ngăn xếp không cần thiết.

Ngoài ra mã của bạn không phải là " giới hạn đúng " - làm thế nào trình biên dịch biết rằng khi nó sửa đổi C, nó không sửa đổi A và B?


Chắc chắn nếu bạn đã gọi hàm như mmult (A ..., A ..., A); bạn chắc chắn sẽ không nhận được kết quả mong đợi. Một lần nữa, mặc dù tôi không cố gắng đánh bại / triển khai lại BLAS, chỉ thấy nó thực sự nhanh như thế nào, vì vậy việc kiểm tra lỗi không được lưu ý, chỉ là chức năng cơ bản.
DeusAduro

3
Xin lỗi, nói rõ hơn, điều tôi đang nói là nếu bạn đặt "hạn chế" trên con trỏ của mình, bạn sẽ nhận được mã nhanh hơn nhiều. Điều này là do mỗi khi bạn modifiy C, trình biên dịch không phải tải lại A và B - tăng tốc đáng kể vòng lặp bên trong. Nếu bạn không tin tôi, hãy kiểm tra việc tháo gỡ.
Bài báo

@DeusAduro: Đây không phải là kiểm tra lỗi - có thể trình biên dịch không thể tối ưu hóa quyền truy cập vào mảng B [] trong vòng lặp bên trong vì nó có thể không phát hiện ra rằng con trỏ A và C không bao giờ có bí danh là B mảng. Nếu có răng cưa, giá trị trong mảng B có thể thay đổi trong khi vòng lặp bên trong đang thực thi. Việc đưa quyền truy cập vào giá trị B [] ra khỏi vòng lặp bên trong và đặt nó vào một biến cục bộ có thể cho phép trình biên dịch tránh các truy cập liên tục vào B [].
Michael Burr

1
Rất tiếc, trước tiên tôi đã thử sử dụng từ khóa '__restrict' trong VS 2008, áp dụng cho A, B và C. Điều này không cho thấy kết quả thay đổi. Tuy nhiên, việc di chuyển quyền truy cập đến B, từ vòng lặp trong cùng sang vòng lặp bên ngoài đã cải thiện thời gian ~ 10%.
DeusAduro

1
Xin lỗi, tôi không chắc về VC, nhưng với GCC, bạn cần bật -fstrict-aliasing. Cũng có lời giải thích tốt hơn về "hạn chế" ở đây: cellperformance.beyond3d.com/articles/2006/05/…
Justicle

2

Đối với mã gốc trong MM nhân, tham chiếu bộ nhớ cho hầu hết các hoạt động là nguyên nhân chính gây ra hiệu suất kém. Bộ nhớ đang chạy chậm hơn 100-1000 lần so với bộ nhớ cache.

Hầu hết việc tăng tốc độ đến từ việc sử dụng các kỹ thuật tối ưu hóa vòng lặp cho chức năng lặp ba lần này trong MM nhân. Hai kỹ thuật tối ưu hóa vòng lặp chính được sử dụng; mở và chặn. Đối với việc giải nén, chúng tôi mở hai vòng ngoài cùng và chặn nó để sử dụng lại dữ liệu trong bộ nhớ cache. Bỏ cuộn vòng ngoài giúp tối ưu hóa truy cập dữ liệu tạm thời bằng cách giảm số lượng tham chiếu bộ nhớ đến cùng một dữ liệu tại các thời điểm khác nhau trong toàn bộ hoạt động. Chặn chỉ mục vòng lặp ở một số cụ thể, giúp giữ lại dữ liệu trong bộ nhớ cache. Bạn có thể chọn tối ưu hóa cho bộ đệm L2 hoặc bộ đệm L3.

https://en.wikipedia.org/wiki/Loop_nest_optimization


-24

Vì nhiều lý do.

Đầu tiên, trình biên dịch Fortran được tối ưu hóa cao và ngôn ngữ cho phép chúng hoạt động như vậy. C và C ++ rất lỏng lẻo về mặt xử lý mảng (ví dụ trường hợp các con trỏ tham chiếu đến cùng một vùng bộ nhớ). Điều này có nghĩa là trình biên dịch không thể biết trước những gì phải làm, và buộc phải tạo mã chung. Trong Fortran, các trường hợp của bạn được sắp xếp hợp lý hơn và trình biên dịch có quyền kiểm soát tốt hơn những gì xảy ra, cho phép anh ta tối ưu hóa nhiều hơn (ví dụ: sử dụng các thanh ghi).

Một điều khác là Fortran lưu trữ nội dung theo cột, trong khi C lưu trữ dữ liệu theo hàng. Tôi không kiểm tra mã của bạn, nhưng hãy cẩn thận về cách bạn thực hiện sản phẩm. Trong C, bạn phải quét hàng một cách khôn ngoan: bằng cách này, bạn quét mảng của mình dọc theo bộ nhớ liền kề, giảm bỏ sót bộ nhớ cache. Lỗi bộ nhớ cache là nguồn đầu tiên của sự kém hiệu quả.

Thứ ba, nó phụ thuộc vào việc triển khai blas mà bạn đang sử dụng. Một số triển khai có thể được viết bằng trình hợp dịch và được tối ưu hóa cho bộ xử lý cụ thể mà bạn đang sử dụng. Phiên bản netlib được viết bằng fortran 77.

Ngoài ra, bạn đang thực hiện rất nhiều thao tác, hầu hết chúng lặp đi lặp lại và thừa. Tất cả những phép nhân để có được chỉ số đều có hại cho hiệu suất. Tôi không thực sự biết điều này được thực hiện như thế nào trong BLAS, nhưng có rất nhiều thủ thuật để ngăn chặn các hoạt động tốn kém.

Ví dụ: bạn có thể làm lại mã của mình theo cách này

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
if ( ADim2!=BDim1 ) throw std::runtime_error("Error sizes off");

memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
int cc2,cc1,cr1, a1,a2,a3;
for ( cc2=0 ; cc2<BDim2 ; ++cc2 ) {
    a1 = cc2*ADim2;
    a3 = cc2*BDim1
    for ( cc1=0 ; cc1<ADim2 ; ++cc1 ) {
          a2=cc1*ADim1;
          ValT b = B[a3+cc1];
          for ( cr1=0 ; cr1<ADim1 ; ++cr1 ) {
                    C[a1+cr1] += A[a2+cr1]*b;
           }
     }
  }
} 

Hãy thử nó, tôi chắc chắn bạn sẽ tiết kiệm được một số thứ.

Ở câu hỏi số 1 của bạn, lý do là phép nhân ma trận có tỷ lệ là O (n ^ 3) nếu bạn sử dụng một thuật toán tầm thường. Có những thuật toán mở rộng quy mô tốt hơn nhiều .


36
Câu trả lời này là hoàn toàn sai, xin lỗi. Việc triển khai BLAS không được viết bằng fortran. Mã performence-critical được viết bằng assembly và những cái phổ biến nhất hiện nay được viết bằng C ở trên đó. Ngoài ra BLAS cũng chỉ định thứ tự hàng / cột như một phần của giao diện và việc triển khai có thể xử lý bất kỳ sự kết hợp nào.
Andrew Tomazos

10
Vâng, câu trả lời này là hoàn toàn sai. Thật không may, nó đầy những điều phi lý thông thường, ví dụ như BLAS yêu cầu nhanh hơn vì Fortran. Có 20 (!) Xếp hạng tích cực là một điều tồi tệ. Giờ đây, điều vô nghĩa này thậm chí còn lan rộng hơn nữa vì sự phổ biến của Stackoverflow!
Michael Lehn

12
Tôi nghĩ rằng bạn đang nhầm lẫn giữa triển khai tham chiếu chưa được tối ưu hóa với triển khai sản xuất. Việc triển khai tài liệu tham khảo chỉ là để xác định giao diện và hành vi của thư viện và được viết bằng Fortran vì lý do lịch sử. Nó không phải để sử dụng trong sản xuất. Trong sản xuất, người ta sử dụng các triển khai được tối ưu hóa thể hiện hành vi giống như triển khai tham chiếu. Tôi đã nghiên cứu phần bên trong của ATLAS (hỗ trợ Octave - Linux "MATLAB") mà tôi có thể xác nhận đầu tiên được viết bằng C / ASM trong nội bộ. Việc triển khai thương mại cũng gần như chắc chắn.
Andrew Tomazos

5
@KyleKanos: Vâng, đây là nguồn của ATLAS: sourceforge.net/projects/math-atlas/files/Stable/3.10.1 Theo tôi biết, nó là cách triển khai BLAS di động mã nguồn mở được sử dụng phổ biến nhất. Nó được viết bằng C / ASM. Các nhà sản xuất CPU hiệu suất cao như Intel, cũng cung cấp các triển khai BLAS đặc biệt được tối ưu hóa cho chip của họ. Tôi đảm bảo ở các phần cấp thấp của thư viện Intels được viết bằng (duuh) x86, và tôi khá chắc chắn rằng các phần cấp trung bình sẽ được viết bằng C hoặc C ++.
Andrew Tomazos

9
@KyleKanos: Bạn đang bối rối. Netlib BLAS là triển khai tham chiếu. Triển khai tham chiếu chậm hơn nhiều so với triển khai được tối ưu hóa (xem so sánh hiệu suất ). Khi ai đó nói rằng họ đang sử dụng netlib BLAS trên một cụm, điều đó không có nghĩa là họ thực sự đang sử dụng triển khai tham chiếu netlib. Điều đó sẽ chỉ là ngớ ngẩn. Nó chỉ có nghĩa là họ đang sử dụng một lib có cùng giao diện với netlib blas.
Andrew Tomazos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.