2011-12-21 40 views
10

Tôi là người mới bắt đầu trong lập trình và hiện đang cố gắng làm việc trên một dự án yêu cầu triển khai Fast Fourier Transform.Cải thiện tốc độ thực hiện FFT

Tôi đã cho đến nay được quản lý để thực hiện những điều sau:

Có ai có bất kỳ lựa chọn thay thế và gợi ý để cải thiện tốc độ của chương trình mà không mất đi độ chính xác.

short FFTMethod::FFTcalc(short int dir,long m,double *x,double *y) 
{ 
long n,i,i1,j,k,i2,l,l1,l2; 
double c1,c2,tx,ty,t1,t2,u1,u2,z; 

/* Calculate the number of points */ 
n = 1; 
for (i=0;i<m;i++) 
    n *= 2; 

/* Do the bit reversal */ 
i2 = n >> 1; 
j = 0; 
for (i=0;i<n-1;i++) { 
    if (i < j) { 
    tx = x[i]; 
    ty = y[i]; 
    x[i] = x[j]; 
    y[i] = y[j]; 
    x[j] = tx; 
    y[j] = ty; 
    } 
    k = i2; 
    while (k <= j) { 
    j -= k; 
    k >>= 1; 
    } 
    j += k; 
} 

/* Compute the FFT */ 
c1 = -1.0; 
c2 = 0.0; 
l2 = 1; 
for (l=0;l<m;l++) { 
    l1 = l2; 
    l2 <<= 1; 
    u1 = 1.0; 
    u2 = 0.0; 
    for (j=0;j<l1;j++) { 
    for (i=j;i<n;i+=l2) { 
     i1 = i + l1; 
     t1 = u1 * x[i1] - u2 * y[i1]; 
     t2 = u1 * y[i1] + u2 * x[i1]; 
     x[i1] = x[i] - t1; 
     y[i1] = y[i] - t2; 
     x[i] += t1; 
     y[i] += t2; 
    } 
    z = u1 * c1 - u2 * c2; 
    u2 = u1 * c2 + u2 * c1; 
    u1 = z; 
    } 
    c2 = sqrt((1.0 - c1)/2.0); 
    if (dir == 1) 
    c2 = -c2; 
    c1 = sqrt((1.0 + c1)/2.0); 
    } 

/* Scaling for forward transform */ 
if (dir == 1) { 
    for (i=0;i<n;i++) { 
     x[i] /= n; 
     y[i] /= n; 
    } 
} 


    return(1); 
} 
+4

Trừ khi bạn cần tự viết nó cho mục đích hiểu, FFTW (http://www.fftw.org/) là một thư viện tuyệt vời. Đó là một việc thực hiện tự điều chỉnh, siêu nhanh và đáng tin cậy và bạn có thể gọi nó từ C++ tốt (xem http://www.fftw.org/faq/section2.html#cplusplus) –

+0

Tôi thích FFTReal rất nhiều. http://ldesoras.free.fr/prod.html –

+2

Tại sao bạn viết thực hiện của riêng bạn thay vì sử dụng một trong vô số thư viện trên mạng, có khả năng tất cả nhanh hơn, được kiểm tra tốt hơn, chính xác hơn và có nhiều tính năng hơn? – PlasmaHH

Trả lời

20

Gần đây tôi đã tìm thấy tệp PDF tuyệt vời này trên số Construction of a high performance FFTs của Eric Postpischil. Đã phát triển một số FFTs bản thân mình tôi biết làm thế nào khó khăn là để cạnh tranh với các thư viện thương mại. Tôi tin rằng bạn đang làm tốt nếu FFT của bạn chỉ chậm hơn 4 lần so với Intel hoặc FFTW, chứ không phải 40x! Tuy nhiên, bạn có thể cạnh tranh và đây là cách thực hiện.

Để tóm tắt bài viết đó, tác giả nói rằng các FFT của Radix2 là đơn giản nhưng không hiệu quả, cấu trúc hiệu quả nhất là FFT radix4. Một phương pháp hiệu quả hơn nữa là Radix8 tuy nhiên điều này thường không phù hợp với các thanh ghi trên một CPU nên Radix4 được ưu tiên hơn.

FFT có thể được xây dựng theo từng giai đoạn, để tính toán điểm FFT 1024, bạn có thể thực hiện 10 giai đoạn của FFT Radix2 (như 2^10 - 1024) hoặc 5 giai đoạn của Radix4 FFT (4^5 = 1024) . Bạn thậm chí có thể tính toán điểm FFT 1024 trong các giai đoạn 8 * 4 * 4 * 4 * 2 nếu bạn chọn.Ít giai đoạn hơn có nghĩa là ít lần đọc và ghi vào bộ nhớ (nút cổ chai cho hiệu suất FFT là băng thông bộ nhớ) do đó lựa chọn động 4, 8 hoặc cao hơn là phải. Giai đoạn Radix4 có hiệu quả đặc biệt vì tất cả các khối lượng đều có mã số 1 + 0i, 0 + 1i, -1 + 0i, 0-1i và Radix4 có thể được viết hoàn toàn trong bộ đệm.

Thứ hai, mỗi giai đoạn trong FFT không giống nhau. Giai đoạn đầu tiên có trọng số bằng 1 + 0i. không có điểm tính toán trọng lượng này và thậm chí nhân với nó vì nó là một phức nhân nhân với 1, vì vậy giai đoạn đầu tiên có thể được thực hiện mà không có trọng lượng. Giai đoạn cuối cùng cũng có thể được xử lý khác nhau và có thể được sử dụng để thực hiện Decimation in Time (đảo ngược bit). Tài liệu của Eric Postpischil bao gồm tất cả những điều này.

Trọng số có thể được precomputed và được lưu trữ trong một bảng. Các phép tính Sin/cos mất khoảng 100-150 chu kỳ trên mỗi phần cứng x86 vì vậy precomputing chúng có thể tiết kiệm 10-20% tổng thời gian tính toán khi truy cập bộ nhớ trong trường hợp này nhanh hơn so với tính toán CPU. Sử dụng các thuật toán nhanh để tính toán sự chân thành trong một lần là đặc biệt có ích (Lưu ý rằng cos bằng sqrt (1.0 - sin * sin), hoặc sử dụng tra cứu bảng, cos chỉ là một sự dịch pha của sin). Cuối cùng khi bạn có triển khai FFT siêu sắp xếp hợp lý, bạn có thể sử dụng vector hóa SIMD để tính toán điểm nổi 4x hoặc gấp đôi hoạt động điểm nổi trên mỗi chu kỳ bên trong thói quen bướm để cải thiện tốc độ 100-300% khác. Lấy tất cả những điều trên bạn sẽ có cho mình một FFT khá mượt mà và nhanh chóng!

Để tiếp tục, bạn có thể thực hiện tối ưu hóa nhanh chóng bằng cách cung cấp các triển khai khác nhau của các giai đoạn FFT được nhắm mục tiêu đến các kiến ​​trúc bộ vi xử lý cụ thể. Kích thước bộ nhớ cache, số lượng đăng ký, bộ lệnh SSE/SSE2/3/4 vv khác nhau trên mỗi máy để chọn một kích thước phù hợp với tất cả các phương pháp tiếp cận thường bị đánh bại bởi các thói quen nhắm mục tiêu. Trong FFTW, ví dụ nhiều FFTs kích thước nhỏ hơn được tối ưu hóa cao chưa được kiểm tra (không có vòng lặp) nào được triển khai cho một kiến ​​trúc cụ thể. Bằng cách kết hợp các cấu trúc nhỏ hơn (chẳng hạn như các thói quen RadixN), bạn có thể chọn thói quen nhanh nhất và tốt nhất cho công việc trong tầm tay.

+0

Cảm ơn rất nhiều. Bạn đã được rất hữu ích. Tôi sẽ thử thực hiện các thay đổi. – sagarn

+3

Điều chỉnh hiệu suất là một chút nghệ thuật đen.Tôi sẽ đề nghị tạo một ứng dụng thử nghiệm chạy nhiều lần lặp lại của các phương thức FFT khác nhau và so sánh chúng, cộng với so sánh độ chính xác kết quả và tốc độ của phép biến đổi để triển khai FFT đã biết (ví dụ, FFTW). Thay vì hoàn toàn thay đổi triển khai, hãy giữ nó nhưng tạo ra các triển khai mới và so sánh chúng. Bạn sẽ ngạc nhiên về điều gì và không tăng hiệu suất. Ví dụ. giảm số lượng nhân có thể không có tác dụng lớn như việc đảm bảo bạn thực hiện RAM của bạn đọc tuần tự và càng ít lần càng tốt! –

+0

Nếu nhận xét hữu ích cho bạn, vui lòng bỏ phiếu. Cảm ơn! :-) –

4

Trong khi tôi không thể cung cấp cho bạn một gợi ý hiệu suất ngay bây giờ, tôi muốn đưa ra một số lời khuyên để tối ưu hóa của bạn mà là quá dài cho một lời nhận xét:

  1. Nếu bạn chưa có làm như vậy, viết một số kiểm tra tính chính xác cho mã của bạn ngay bây giờ. Các thử nghiệm đơn giản như "thực hiện FFT của mảng này và xem kết quả có phù hợp với các kết quả tôi đã cung cấp hay không", nhưng trước khi bạn tối ưu hóa mã, bạn cần một thử nghiệm đơn vị vững chắc và tự động xác nhận mã được tối ưu hóa của bạn là chính xác.
  2. Sau đó, hãy lập hồ sơ cho mã của bạn để xem nút cổ chai thực sự ở đâu. Trong khi tôi nghi ngờ vòng lặp bên trong nhất là for (i=j;i<n;i+=l2) {, việc nhìn thấy tốt hơn là tin tưởng.
0

Điều này có vẻ là việc triển khai FFT cơ bản-2 cơ bản ngay trong sách giáo khoa cũ. Có rất nhiều giấy tờ hàng chục năm về việc tối ưu hóa FFT theo nhiều cách khác nhau, tùy thuộc vào nhiều yếu tố. Chẳng hạn, dữ liệu của bạn có nhỏ hơn bộ nhớ cache CPU không?

Nhập: Ví dụ, nếu vectơ dữ liệu cộng với một bảng hệ số sẽ khớp với DCache CPU và/hoặc nếu số nhân chậm hơn nhiều so với bộ nhớ truy cập trên CPU của bạn, sau đó precomputing một bảng các yếu tố twiddle có thể làm giảm tổng chu kỳ đếm để sử dụng lặp lại của FFT. Nhưng nếu không, precomputing thực sự có thể chậm hơn. Điểm chuẩn. YMMV.

+0

Có bạn là đúng @ hotpaw2, tôi gọi một cuốn sách được gọi là Bí quyết số trong C khi tôi tìm thấy nó là nơi tốt nhất để bắt đầu. Tuy nhiên đây chỉ là nỗ lực đầu tiên của nó và tôi có rất nhiều tối ưu để làm trước khi hoàn thành dự án. Có dữ liệu nhỏ hơn CPU cache. – sagarn

4

Có một vài điều tôi có thể khuyên cố gắng:

  1. Đừng trao đổi các yếu tố đầu vào, thay vì tính toán chỉ số bit bị đảo ngược. Điều này sẽ giúp bạn tiết kiệm một số bộ nhớ đọc và ghi.
  2. Tính toán hệ số nếu bạn đang thực hiện nhiều FFT cùng kích thước. Điều này sẽ tiết kiệm một số tính toán.
  3. Sử dụng radix-4 FFT thay vì radix-2. Điều này sẽ dẫn đến ít lần lặp hơn trong vòng lặp bên trong.

Câu trả lời cuối cùng có thể được tìm thấy bằng cách lược tả mã.

+0

cảm ơn @Alex. Tôi sẽ cố gắng làm điều này. – sagarn

+0

Nếu tôi hiểu bạn đúng, (1) là một ý tưởng tồi. Bạn đang tiết kiệm một số hoạt động bộ nhớ nhưng bạn cũng ngẫu nhiên nhiều hơn nữa trong số đó là tồi tệ hơn nhiều bởi vì nó phá hủy những lợi thế của CPU lưu trữ trong vòng lặp chính. –

+0

@JonHarrop: không hoán đổi "ngẫu nhiên" quá? Bạn chắc chắn sẽ truy cập cùng một dữ liệu anyway * và * không theo thứ tự hoặc tại thời điểm hoán đổi hoặc sau đó nếu không có hoán đổi. –

Các vấn đề liên quan