Câu hỏi này là phần mở rộng của hai cuộc thảo luận được đưa ra gần đây trong phần trả lời " C ++ vs Fortran cho HPC ". Và đó là một chút thách thức hơn là một câu hỏi ...
Một trong những lập luận thường được nghe nhất ủng hộ Fortran là các trình biên dịch chỉ tốt hơn. Vì hầu hết các trình biên dịch C / Fortran đều có chung một mặt sau, mã được tạo cho các chương trình tương đương về mặt ngữ nghĩa ở cả hai ngôn ngữ nên giống hệt nhau. Tuy nhiên, người ta có thể lập luận rằng C / Fortran dễ dàng hơn / ít hơn cho trình biên dịch để tối ưu hóa.
Vì vậy, tôi quyết định thử một thử nghiệm đơn giản: tôi đã nhận được một bản sao của daxpy.f và daxpy.c và biên dịch chúng với gfortran / gcc.
Bây giờ daxpy.c chỉ là bản dịch f2c của daxpy.f (mã được tạo tự động, xấu như heck), vì vậy tôi đã lấy mã đó và làm sạch nó một chút (gặp daxpy_c), về cơ bản có nghĩa là viết lại vòng lặp trong cùng như
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
Cuối cùng, tôi đã viết lại nó (nhập daxpy_cvec) bằng cú pháp vectơ của gcc:
#define vector(elcount, type) __attribute__((vector_size((elcount)*sizeof(type)))) type
vector(2,double) va = { da , da }, *vx, *vy;
vx = (void *)dx; vy = (void *)dy;
for ( i = 0 ; i < (n/2 & ~1) ; i += 2 ) {
vy[i] += va * vx[i];
vy[i+1] += va * vx[i+1];
}
for ( i = n & ~3 ; i < n ; i++ )
dy[i] += da * dx[i];
Lưu ý rằng tôi sử dụng các vectơ có độ dài 2 (tất cả SSE2 cho phép) và tôi xử lý hai vectơ cùng một lúc. Điều này là do trên nhiều kiến trúc, chúng ta có thể có nhiều đơn vị nhân hơn chúng ta có các phần tử vectơ.
Tất cả các mã được biên dịch bằng gfortran / gcc phiên bản 4.5 với các cờ "-O3 -Wall -msse2 -march = local -ffast-math -fomit-frame-con trỏ -malign-double -fstrict-aliasing". Trên máy tính xách tay của tôi (CPU Intel Core i5, M560, 2.67GHz) tôi đã nhận được đầu ra sau:
pedro@laika:~/work/fvsc$ ./test 1000000 10000
timing 1000000 runs with a vector of length 10000.
daxpy_f took 8156.7 ms.
daxpy_f2c took 10568.1 ms.
daxpy_c took 7912.8 ms.
daxpy_cvec took 5670.8 ms.
Vì vậy, mã Fortran ban đầu mất hơn 8.1 giây, quá trình dịch tự động mất 10,5 giây, việc triển khai C ngây thơ thực hiện trong 7.9 và mã được mã hóa rõ ràng thực hiện trong 5.6, ít hơn một chút.
Đó là Fortran chậm hơn một chút so với triển khai C ngây thơ và chậm hơn 50% so với triển khai C được vector hóa.
Vì vậy, đây là câu hỏi: Tôi là một lập trình viên C bản địa và vì vậy tôi khá tự tin rằng tôi đã làm tốt mã đó, nhưng mã Fortran đã được chạm vào lần cuối vào năm 1993 và do đó có thể hơi lỗi thời. Vì tôi không cảm thấy thoải mái khi viết mã ở Fortran như những người khác ở đây, mọi người có thể làm việc tốt hơn, tức là cạnh tranh hơn so với bất kỳ hai phiên bản C nào không?
Ngoài ra, có ai có thể thử bài kiểm tra này với icc / ifort không? Cú pháp vectơ có thể sẽ không hoạt động, nhưng tôi sẽ tò mò muốn xem phiên bản C ngây thơ hoạt động ở đó như thế nào. Bất cứ ai cũng có xlc / xlf nằm xung quanh.
Tôi đã tải lên các nguồn và Makefile tại đây . Để có được thời gian chính xác, hãy đặt CPU_TPS trong test.c thành số Hz trên CPU của bạn. Nếu bạn tìm thấy bất kỳ cải tiến cho bất kỳ phiên bản nào, xin vui lòng gửi chúng ở đây!
Cập nhật:
Tôi đã thêm mã kiểm tra của stali vào các tệp trực tuyến và bổ sung mã đó bằng phiên bản C. Tôi đã sửa đổi các chương trình để thực hiện 1000'000 vòng trên các vectơ có độ dài 10.000 để phù hợp với thử nghiệm trước đó (và vì máy của tôi không thể phân bổ các vectơ có độ dài 1'000'000, như bản gốc của stali mã). Vì các số bây giờ nhỏ hơn một chút, tôi đã sử dụng tùy chọn -par-threshold:50
để làm cho trình biên dịch có nhiều khả năng song song hơn. Phiên bản icc / ifort được sử dụng là 12.1.2 20111128 và kết quả như sau
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_c
3.27user 0.00system 0:03.27elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_f
3.29user 0.00system 0:03.29elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_c
4.89user 0.00system 0:02.60elapsed 188%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_f
4.91user 0.00system 0:02.60elapsed 188%CPU
Tóm lại, kết quả là, cho tất cả các mục đích thực tế, giống hệt nhau cho cả phiên bản C và Fortran và cả hai mã đều song song tự động. Lưu ý rằng thời gian nhanh so với thử nghiệm trước đó là do sử dụng số học dấu phẩy động chính xác đơn!
Cập nhật:
Mặc dù tôi thực sự không thích gánh nặng chứng minh ở đâu, tôi đã mã hóa lại ví dụ nhân ma trận của stali trong C và thêm nó vào các tệp trên web . Dưới đây là kết quả của vòng lặp ba lần cho một và hai CPU:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
triple do time 3.46421700000000
3.63user 0.06system 0:03.70elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_c 2500
triple do time 3.431997791385768
3.58user 0.10system 0:03.69elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
triple do time 5.09631900000000
5.26user 0.06system 0:02.81elapsed 189%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_c 2500
triple do time 2.298916975280899
4.78user 0.08system 0:02.62elapsed 184%CPU
Lưu ý rằng cpu_time
trong Fortran kiểm tra thời gian CPU chứ không phải thời gian đồng hồ treo tường, vì vậy tôi đã kết thúc các cuộc gọi time
để so sánh chúng với 2 CPU. Không có sự khác biệt thực sự giữa các kết quả, ngoại trừ việc phiên bản C làm tốt hơn một chút trên hai lõi.
Bây giờ đối với matmul
lệnh, tất nhiên chỉ có ở Fortran vì nội tại này không có sẵn trong C:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
matmul time 23.6494780000000
23.80user 0.08system 0:23.91elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
matmul time 26.6176640000000
26.75user 0.10system 0:13.62elapsed 197%CPU
Ồ Điều đó hoàn toàn khủng khiếp. Bất cứ ai cũng có thể tìm ra những gì tôi đang làm sai, hoặc giải thích tại sao nội tại này vẫn là một điều tốt?
Tôi đã không thêm các dgemm
cuộc gọi vào điểm chuẩn vì chúng là các cuộc gọi thư viện cho cùng chức năng trong Intel MKL.
Đối với các thử nghiệm trong tương lai, bất cứ ai cũng có thể đề xuất một ví dụ được biết là chậm hơn C trong Fortran?
Cập nhật
Để xác minh tuyên bố của stali rằng matmul
nội tại là "một thứ tự phóng đại" nhanh hơn sản phẩm ma trận rõ ràng trên các ma trận nhỏ hơn, tôi đã sửa đổi mã của chính mình để nhân ma trận có kích thước 100x100 bằng cả hai phương pháp, mỗi lần 10.000 lần. Kết quả, trên một và hai CPU, như sau:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 10000 100
matmul time 3.61222500000000
triple do time 3.54022200000000
7.15user 0.00system 0:07.16elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 10000 100
matmul time 4.54428400000000
triple do time 4.31626900000000
8.86user 0.00system 0:04.60elapsed 192%CPU
Cập nhật
Grisu đúng khi chỉ ra rằng, không cần tối ưu hóa, gcc chuyển đổi các hoạt động trên các số phức thành các lệnh gọi hàm thư viện trong khi gfortran sắp xếp chúng theo một vài hướng dẫn.
Trình biên dịch C sẽ tạo cùng một mã nhỏ gọn nếu tùy chọn -fcx-limited-range
được đặt, tức là trình biên dịch được hướng dẫn bỏ qua các luồng trên / dưới tiềm năng trong các giá trị trung gian. Tùy chọn này bằng cách nào đó được đặt mặc định trong gfortran và có thể dẫn đến kết quả không chính xác. Buộc -fno-cx-limited-range
trong gfortran không thay đổi bất cứ điều gì.
Vì vậy, đây thực sự là một đối số chống lại việc sử dụng gfortran cho các phép tính số: Các thao tác trên các giá trị phức tạp có thể vượt quá / chảy ngay cả khi kết quả chính xác nằm trong phạm vi dấu phẩy động. Đây thực sự là một tiêu chuẩn của Fortran. Trong gcc, hoặc trong C99 nói chung, mặc định là thực hiện mọi thứ một cách nghiêm ngặt (đọc tuân thủ theo chuẩn IEEE-754) trừ khi có quy định khác.
Nhắc nhở: Xin lưu ý rằng câu hỏi chính là liệu trình biên dịch Fortran có tạo ra mã tốt hơn trình biên dịch C hay không. Đây không phải là nơi để thảo luận về giá trị chung của ngôn ngữ này so với ngôn ngữ khác. Điều tôi sẽ thực sự quan tâm là nếu bất kỳ ai cũng có thể tìm ra cách dỗ gfortran để tạo ra một daxpy hiệu quả như trong C bằng cách sử dụng vector hóa rõ ràng vì điều này minh họa cho các vấn đề phải dựa vào trình biên dịch dành riêng cho tối ưu hóa SIMD, hoặc một trong trường hợp trình biên dịch Fortran thực hiện đối tác C của nó.