ประสิทธิภาพ PyFFTW บนอาร์เรย์หลายมิติ

Oct 29 2020

ฉันมีอาร์เรย์ nD พูดถึงขนาด: (144, 522720) และฉันต้องการคำนวณ FFT

PyFFTWดูเหมือนจะช้ากว่าnumpyและscipyไม่คาดคิด

ฉันทำอะไรผิดอย่างเห็นได้ชัด?

ด้านล่างนี้คือรหัสของฉัน

import numpy
import scipy      
import pyfftw
import time

n1 = 144
n2 = 522720
loops = 2

pyfftw.config.NUM_THREADS = 4
pyfftw.config.PLANNER_EFFORT = 'FFTW_ESTIMATE'
# pyfftw.config.PLANNER_EFFORT = 'FFTW_MEASURE'

Q_1 = pyfftw.empty_aligned([n1, n2], dtype='float64')
Q_2 = pyfftw.empty_aligned([n1, n2], dtype='complex_')
Q_ref = pyfftw.empty_aligned([n1, n2], dtype='complex_')

# repeat a few times to see if pyfft planner helps
for i in range(0,loops):
    Q_1 = numpy.random.rand(n1,n2)

    s1 = time.time()
    Q_ref = numpy.fft.fft(Q_1, axis=0)
    print('NUMPY - elapsed time: ', time.time() - s1, 's.')

    s1 = time.time()
    Q_2 = scipy.fft.fft(Q_1, axis=0)
    print('SCIPY - elapsed time: ', time.time() - s1, 's.')
    print('Equal = ', numpy.allclose(Q_2, Q_ref))

    s1 = time.time()
    Q_2 = pyfftw.interfaces.numpy_fft.fft(Q_1, axis=0)
    print('PYFFTW NUMPY - elapsed time = ', time.time() - s1, 's.')
    print('Equal = ', numpy.allclose(Q_2, Q_ref))

    s1 = time.time()
    Q_2 = pyfftw.interfaces.scipy_fftpack.fft(Q_1, axis=0)
    print('PYFFTW SCIPY - elapsed time = ', time.time() - s1, 's.')
    print('Equal = ', numpy.allclose(Q_2, Q_ref))

    s1 = time.time()
    fft_object = pyfftw.builders.fft(Q_1, axis=0)
    Q_2 = fft_object()
    print('FFTW PURE Elapsed time = ', time.time() - s1, 's')
    print('Equal = ', numpy.allclose(Q_2, Q_ref))

คำตอบ

2 HenryGomersall Oct 30 2020 at 09:47

ประการแรกหากคุณเปิดแคชก่อนที่คุณจะวนรอบหลักอินเทอร์เฟซจะทำงานได้ดีตามที่คาดไว้:

pyfftw.interfaces.cache.enable()
pyfftw.interfaces.cache.set_keepalive_time(30)

เป็นที่น่าสนใจว่าแม้จะมีภูมิปัญญาที่ควรจัดเก็บ แต่การสร้างpyfftwวัตถุก็ยังค่อนข้างช้าเมื่อปิดแคช ไม่ว่านี่เป็นวัตถุประสงค์ของแคช ในกรณีของคุณคุณต้องทำให้แคชคงอยู่นานมากเพราะลูปของคุณยาวมาก

ประการที่สองไม่ใช่การเปรียบเทียบที่ยุติธรรมที่จะรวมเวลาก่อสร้างของการfft_objectทดสอบขั้นสุดท้าย หากคุณย้ายออกนอกตัวจับเวลาการโทรfft_objectเป็นมาตรการที่ดีกว่า

ประการที่สามก็ยังน่าสนใจที่จะเห็นว่าแม้จะมีการแคชเปิดการเรียกร้องให้ช้ากว่าการเรียกร้องให้numpy_fft scipy_fftเนื่องจากไม่มีความแตกต่างที่ชัดเจนในเส้นทางรหัสฉันจึงแนะนำว่าเป็นปัญหาในการแคช นี่คือประเภทของปัญหาที่timeitพยายามบรรเทา นี่คือรหัสเวลาที่ฉันเสนอซึ่งมีความหมายมากกว่า:

import numpy
import scipy
import pyfftw
import timeit

n1 = 144
n2 = 522720

pyfftw.config.NUM_THREADS = 4
pyfftw.config.PLANNER_EFFORT = 'FFTW_MEASURE'

Q_1 = pyfftw.empty_aligned([n1, n2], dtype='float64')

pyfftw.interfaces.cache.enable()
pyfftw.interfaces.cache.set_keepalive_time(30)

times = timeit.repeat(lambda: numpy.fft.fft(Q_1, axis=0), repeat=5, number=1)
print('NUMPY fastest time = ', min(times))

times = timeit.repeat(lambda: scipy.fft.fft(Q_1, axis=0), repeat=5, number=1)
print('SCIPY fastest time = ', min(times))

times = timeit.repeat(
    lambda: pyfftw.interfaces.numpy_fft.fft(Q_1, axis=0), repeat=5, number=1)
print('PYFFTW NUMPY fastest time = ', min(times))

times = timeit.repeat(
    lambda: pyfftw.interfaces.scipy_fftpack.fft(Q_1, axis=0), repeat=5, number=1)
print('PYFFTW SCIPY fastest time = ', min(times))

fft_object = pyfftw.builders.fft(Q_1, axis=0)
times = timeit.repeat(lambda: fft_object(Q_1), repeat=5, number=1)
print('FFTW PURE fastest time = ', min(times))

บนเครื่องของฉันสิ่งนี้ให้ผลลัพธ์เช่น:

NUMPY fastest time =  0.6622681759763509
SCIPY fastest time =  0.6572431400418282
PYFFTW NUMPY fastest time =  0.4003451430471614
PYFFTW SCIPY fastest time =  0.40362057799939066
FFTW PURE fastest time =  0.324020683998242

คุณสามารถทำได้ดีขึ้นเล็กน้อยหากคุณไม่บังคับให้คัดลอกอาร์เรย์อินพุตเป็นประเภทข้อมูลที่ซับซ้อนโดยเปลี่ยนQ_1เป็นcomplex128:

NUMPY fastest time =  0.6483533839927986
SCIPY fastest time =  0.847397351055406
PYFFTW NUMPY fastest time =  0.3237176960101351
PYFFTW SCIPY fastest time =  0.3199474769644439
FFTW PURE fastest time =  0.2546963169006631

การscipyชะลอตัวที่น่าสนใจนั้นสามารถทำซ้ำได้

ที่กล่าวว่าหากอินพุตของคุณเป็นของจริงคุณควรทำการแปลงจริง (สำหรับการเร่งความเร็ว> 50% ด้วยpyfftw) และจัดการผลลัพธ์ที่ซับซ้อนของผลลัพธ์

สิ่งที่น่าสนใจเกี่ยวกับตัวอย่างนี้คือ (ฉันคิดว่า) แคชมีความสำคัญเพียงใดในผลลัพธ์ (ซึ่งฉันแนะนำว่าทำไมการเปลี่ยนไปใช้การแปลงจริงจึงมีประสิทธิภาพในการเร่งความเร็ว) คุณจะเห็นบางอย่างที่น่าทึ่งเช่นกันเมื่อคุณใช้เปลี่ยนขนาดอาร์เรย์เป็น 524288 (กำลังถัดไปของสองซึ่งคุณคิดว่าอาจจะเร่งความเร็ว แต่ไม่ทำให้ช้าลงอย่างมาก) ในกรณีนี้ทุกอย่างช้าลงเล็กน้อยscipyโดยเฉพาะอย่างยิ่ง สำหรับฉันแล้วรู้สึกว่าscipyไวต่อแคชมากกว่าซึ่งจะอธิบายการช้าลงเมื่อเปลี่ยนอินพุตเป็นcomplex128(522720 เป็นตัวเลขที่ดีสำหรับ FFTing ดังนั้นบางทีเราควรคาดหวังว่าจะมีการชะลอตัว)

สุดท้ายหากความเร็วเป็นรองจากความแม่นยำคุณสามารถใช้โฟลต 32 บิตเป็นประเภทข้อมูลได้เสมอ หากคุณรวมสิ่งนั้นเข้ากับการแปลงร่างจริงคุณจะได้รับการเร่งความเร็วที่ดีกว่า 10 เท่าจากค่าเริ่มต้นที่numpyดีที่สุดที่ระบุไว้ข้างต้น:

PYFFTW NUMPY fastest time =  0.09026529802940786
PYFFTW SCIPY fastest time =  0.1701313250232488
FFTW PURE fastest time =  0.06202622700948268

(numpy และ scipy ไม่เปลี่ยนแปลงมากเท่าที่ฉันคิดว่าพวกเขาใช้การลอยตัวแบบ 64 บิตภายใน)

แก้ไข: ฉันลืมไปว่าfftpackFFT จริงของ Scipy มีโครงสร้างผลลัพธ์แปลก ๆ ซึ่งpyfftwจำลองแบบด้วยการชะลอตัวบางส่วน นี้มีการเปลี่ยนแปลงที่จะมีสติมากขึ้นในโมดูล FFT ใหม่

อินเทอร์เฟซ FFT ใหม่ถูกนำไปใช้ใน pyFFTWและควรเป็นที่ต้องการ โชคไม่ดีที่มีปัญหากับเอกสารที่สร้างขึ้นใหม่ดังนั้นเอกสารจึงล้าสมัยไปนานและไม่แสดงอินเทอร์เฟซใหม่หวังว่าจะได้รับการแก้ไขแล้ว