เร่งการคำนวณ FFT แบบกระจัดกระจาย

ฉันหวังว่าจะมีคนตรวจสอบโค้ดของฉันด้านล่างและเสนอคำแนะนำวิธีเร่งความเร็วส่วนระหว่าง tic และ toc ฟังก์ชันด้านล่างพยายามดำเนินการ IFFT เร็วกว่าฟังก์ชันในตัวของ Matlab เนื่องจาก (1) ถังขยะค่าสัมประสิทธิ์ fft เกือบทั้งหมดเป็นศูนย์ (เช่น ถังขยะ 10 ถึง 1000 จาก 10M ถึง 300M ถังขยะไม่เป็นศูนย์) และ (2 ) มีเพียงผลลัพธ์ IFFT ส่วนตรงกลางเท่านั้นที่ยังคงอยู่ (ผลลัพธ์แรกและสามสุดท้ายจะถูกละทิ้ง - ดังนั้นจึงไม่จำเป็นต้องคำนวณตั้งแต่แรก)

ตัวแปรอินพุตคือ:

fftcoef = complex fft-coef 1D array (10 to 1000 pts long)
bins = index of fft coefficients corresponding to fftcoef (10 to 1000 pts long)
DATAn = # of pts in data before zero padding and fft (in range of 10M to 260M)
FFTn = DATAn + # of pts used to zero pad before taking fft (in range of 16M to 268M) (e.g. FFTn = 2^nextpow2(DATAn))

ในปัจจุบัน โค้ดนี้ใช้เวลาสองสามลำดับของขนาดนานกว่าวิธีฟังก์ชัน ifft ของ Matlab ซึ่งจะคำนวณสเปกตรัมทั้งหมด จากนั้นจึงละทิ้ง 2/3 ของมัน ตัวอย่างเช่น หากข้อมูลอินพุตสำหรับ fftcoef และ bins เป็นอาร์เรย์ 9x1 (นั่นคือ เฉพาะค่าสัมประสิทธิ์ fft ที่ซับซ้อน 9 ต่อแถบด้านข้างเท่านั้น; 18 pts เมื่อพิจารณาแถบด้านข้างทั้งสอง) และ DATAn=32781534, FFTn=33554432 (เช่น 2^25) ดังนั้นวิธี ifft จะใช้เวลา 1.6 วินาที ในขณะที่ วนซ้ำด้านล่างใช้เวลามากกว่า 700 วินาที

ฉันหลีกเลี่ยงการใช้เมทริกซ์เพื่อ vectorize nn loop เนื่องจากบางครั้งขนาดอาเรย์สำหรับ fftcoef และ bins อาจยาวได้ถึง 1000 pts และเมทริกซ์ 260Mx1K จะมีขนาดใหญ่เกินไปสำหรับหน่วยความจำ เว้นแต่ว่ามันจะถูกทำลายด้วยวิธีใดวิธีหนึ่ง

คำแนะนำใด ๆ ที่ชื่นชมมาก! ขอบคุณล่วงหน้า.

function fn_fft_v1p0(fftcoef, bins, DATAn, FFTn)

fftcoef = [fftcoef; (conj(flipud(fftcoef)))];     % fft coefficients
bins = [bins; (FFTn - flipud(bins) +2)];          % corresponding fft indices for fftcoef array

ttrend = zeros( (round(2*DATAn/3) - round(DATAn/3) + 1), 1); % preallocate

start = round(DATAn/3)-1;

tic;
for nn = start+1 : round(2*DATAn/3)  % loop over desired time indices
  % sum over all fft indices having non-zero coefficients
  arg = 2*pi*(bins-1)*(nn-1)/FFTn;
  ttrend(nn-start) = sum( fftcoef.*( cos(arg) + 1j*sin(arg)); 
end
toc;

end

person ggkmath    schedule 25.01.2011    source แหล่งที่มา
comment
ดู fftw.org/pruned.html สำหรับการวิเคราะห์ศักยภาพในการประหยัด มันอาจจะไม่คุ้มค่า   -  person mtrw    schedule 25.01.2011
comment
คุณกำลังดูการดำเนินการ length(bins)*(2*DATAn/3) ดีกว่า DATAn*lg(DATAn) สำหรับแนวทาง FFT หาก 2*length(bins)/3 > lg(DAtan) (เนื่องจาก FFTW จัดการขนาดการแปลงที่ไม่ใช่กำลังของ 2 ฉันจึงไม่สนใจการเติมศูนย์) สำหรับกรณีของถังขยะ 10 ถังและจุดเอาท์พุต 2^25 จุด นั่นคือ '20/3 › 25' ซึ่งเป็นปัจจัยที่เป็นไปได้คือการปรับปรุง 3 ครั้ง ทันทีที่คุณได้รับ coeffs FFT ถึง 75 คุณจะสูญเสียความได้เปรียบ และคุณต้องเขียนโค้ดอัลกอริธึมเป็นภาษา C และดูแลรักษามัน   -  person mtrw    schedule 25.01.2011
comment
ขอบคุณ mtrw ฉันตรวจสอบลิงก์ด้านบนเมื่อสองสามวันก่อน เดิมทีมันทำให้ฉันมีความหวังเพราะมันระบุว่า: ด้วยเหตุนี้ฉันจึงไม่แนะนำให้พิจารณาการตัด 1d FFT เว้นแต่คุณต้องการ 1% ของเอาต์พุตหรือน้อยกว่า (และ/หรือหาก 1% หรือน้อยกว่าของอินพุตของคุณไม่ใช่ศูนย์) ในกรณีของฉัน น้อยกว่า 0.00001% ของอินพุตของฉัน (สัมประสิทธิ์ถึง IDFT) ไม่ใช่ศูนย์ ฉันคิดว่านี่ควรเป็นเหตุผลหลักในการเพิ่มความเร็ว มากกว่าปัจจัยของการปรับปรุง 3 ข้อที่คุณอ้างถึงข้างต้น   -  person ggkmath    schedule 25.01.2011


คำตอบ (2)


คุณต้องจำไว้ว่า Matlab ใช้ไลบรารี fft ที่คอมไพล์แล้ว (http://www.fftw.org/ ) สำหรับฟังก์ชัน fft ซึ่งนอกจากจะทำงานได้เร็วกว่าสคริปต์ Matlab แล้ว ยังได้รับการปรับให้เหมาะสมสำหรับการใช้งานหลายๆ กรณีอีกด้วย ขั้นตอนแรกอาจเป็นการเขียนโค้ดของคุณด้วย c/c++ และคอมไพล์เป็นไฟล์ mex ที่คุณสามารถใช้ได้ภายใน Matlab นั่นจะช่วยเร่งความเร็วโค้ดของคุณอย่างน้อยลำดับความสำคัญ (อาจมากกว่านั้น)

นอกจากนั้น การเพิ่มประสิทธิภาพง่ายๆ อย่างหนึ่งที่คุณสามารถทำได้คือการพิจารณา 2 สิ่ง:

  1. คุณถือว่าอนุกรมเวลาของคุณมีค่าจริง ดังนั้นคุณจึงใช้สมมาตรของค่าสัมประสิทธิ์ fft ได้
  2. โดยทั่วไปอนุกรมเวลาของคุณจะยาวกว่าเวกเตอร์ coeffs fft ของคุณมาก ดังนั้นจึงเป็นการดีกว่าที่จะวนซ้ำแบบ bins แทนที่จะเป็นจุดเวลา (ดังนั้นจึงทำให้เวกเตอร์ที่ยาวกว่าเป็นเวกเตอร์)

สองจุดนี้ถูกแปลเป็นวงต่อไปนี้:

nn=(start+1 : round(2*DATAn/3))';
ttrend2 = zeros( (round(2*DATAn/3) - round(DATAn/3) + 1), 1);
tic;
for bn = 1:length(bins)
     arg = 2*pi*(bins(bn)-1)*(nn-1)/FFTn; 
     ttrend2 = ttrend2 +  2*real(fftcoef(bn) * exp(i*arg)); 
end
toc;

โปรดทราบว่าคุณต้องใช้การวนซ้ำนี้ ก่อน คุณจะขยาย bins และ fftcoef เนื่องจากความสมมาตรได้ถูกนำมาพิจารณาแล้ว การวนซ้ำนี้ใช้เวลา 8.3 วินาทีในการรันด้วยพารามิเตอร์จากคำถามของคุณ ในขณะที่พีซีของฉันใช้เวลา 141.3 วินาทีในการรันด้วยโค้ดของคุณ

person Itamar Katz    schedule 25.01.2011
comment
สวัสดี Itamar Katz คำแนะนำที่ยอดเยี่ยม ฉันได้รับตัวเลขที่ใกล้เคียงกับสิ่งที่คุณแสดงด้านบนสำหรับการปรับปรุงโดยใช้คำแนะนำ #2 ด้านบนและโค้ดที่ให้มา ฉันไม่แน่ใจว่าจะเขียนโค้ดคำแนะนำ #1 ด้านบนของคุณอย่างไร (เพื่อใช้ประโยชน์จากความสมมาตร) คุณช่วยอธิบายเพิ่มเติมได้ไหม? - person ggkmath; 25.01.2011
comment
โอ้ ฉันเข้าใจแล้ว คุณได้รวมข้อเสนอแนะ 1 ไว้แล้วโดยรวมโค้ด 2*real ด้านบน จากนั้นฉันก็แสดงความคิดเห็นสองบรรทัดต้นฉบับด้านบนสำหรับ fftcoef=[...] และ bins=[...] เคล็ดลับดีๆ ลดเวลาลงครึ่งหนึ่ง จากนั้นไม่มีเหตุผลที่จะขยาย bins และ fftcoef - person ggkmath; 25.01.2011
comment
ใช่แล้ว ความสมมาตรมาจากข้อเท็จจริงที่ว่าสำหรับข้อมูลที่มีค่าจริง ค่า coeff ที่ k คือคอนจูเกตเชิงซ้อนของค่าสัมพัทธ์ที่ (N-k) ดังนั้นคุณจึงสามารถรวมแต่ละเทอมและค่าสังยุคของมันจะเป็น 2*real(...) - person Itamar Katz; 25.01.2011
comment
ยอดเยี่ยม! ขอบคุณมาก! ฉันไม่เคยร่วมงานกับ MEX มาก่อน มันง่ายไหมที่จะเขียนโค้ดตัวอย่างว่ามันจะเป็นอย่างไร - person ggkmath; 25.01.2011
comment
ฉันจะไม่บอกว่ามันง่าย แต่ก็ไม่ได้ยากเกินไปเช่นกัน - ขึ้นอยู่กับประสบการณ์ของคุณแน่นอน Matlab มีเอกสารประกอบที่ดี ดังนั้นจึงเป็นจุดเริ่มต้นที่ดี - person Itamar Katz; 25.01.2011

ฉันได้โพสต์คำถาม / คำตอบที่ เร่งการตัดแต่ง FFTW เพื่อหลีกเลี่ยงช่องว่างภายในขนาดใหญ่ ซึ่งแก้ปัญหาสำหรับกรณี C ++ โดยใช้ FFTW คุณสามารถใช้โซลูชันนี้ได้โดยใช้ประโยชน์จาก mex-files

person Vitality    schedule 21.11.2016