【问题标题】:PyFFTW slower than SciPy FFT?PyFFTW 比 SciPy FFT 慢?
【发布时间】:2015-06-02 12:21:39
【问题描述】:

我尝试了用户在 Stackoverflow 上提出的解决方案:henry-gomersall 来重复加速基于 FFT 的卷积,但得到了不同的结果。

import numpy as np
import pyfftw
import scipy.signal
import timeit

class CustomFFTConvolution(object):

    def __init__(self, A, B, threads=1):

        shape = (np.array(A.shape) + np.array(B.shape))-1

        if np.iscomplexobj(A) and np.iscomplexobj(B):
            self.fft_A_obj = pyfftw.builders.fftn(
                    A, s=shape, threads=threads)
            self.fft_B_obj = pyfftw.builders.fftn(
                    B, s=shape, threads=threads)
            self.ifft_obj = pyfftw.builders.ifftn(
                    self.fft_A_obj.get_output_array(), s=shape,
                    threads=threads)

        else:
            self.fft_A_obj = pyfftw.builders.rfftn(
                    A, s=shape, threads=threads)
            self.fft_B_obj = pyfftw.builders.rfftn(
                    B, s=shape, threads=threads)
            self.ifft_obj = pyfftw.builders.irfftn(
                    self.fft_A_obj.get_output_array(), s=shape,
                    threads=threads)

    def __call__(self, A, B):

        fft_padded_A = self.fft_A_obj(A)
        fft_padded_B = self.fft_B_obj(B)

        return self.ifft_obj(fft_padded_A * fft_padded_B)

N = 200

A = np.random.rand(N, N, N)
B = np.random.rand(N, N, N)

start_time = timeit.default_timer()

C = scipy.signal.fftconvolve(A,B,"same")
print timeit.default_timer() - start_time

custom_fft_conv_nthreads = CustomFFTConvolution(A, B, threads=1)
C = custom_fft_conv_nthreads(A, B)
print timeit.default_timer() - start_time

PyFFTW 约为。比其他用户体验不同的 SciPy FFT 慢 7 倍。这段代码有什么问题? Python 2.7.9,PyFFTW 0.9.2。

【问题讨论】:

    标签: python scipy pyfftw


    【解决方案1】:

    你没有做你认为你在做的事,你认为你在做你不应该做的事。

    你没有做你认为你正在做的事情,因为你上面的代码只定义了一次 start_time(所以你对 pyfftw 的测试不仅包括耗时的 CustomFFTConvolution 对象的创建,还包括 scipy 卷积!)。

    你不应该做你认为你正在做的事情,因为你应该使用timeit 来测试这种事情。

    所以,有一些文件foo.py

    import numpy as np
    import pyfftw
    import scipy.signal
    
    class CustomFFTConvolution(object):
    
        def __init__(self, A, B, threads=1):
    
            shape = (np.array(A.shape) + np.array(B.shape))-1
    
            if np.iscomplexobj(A) and np.iscomplexobj(B):
                self.fft_A_obj = pyfftw.builders.fftn(
                        A, s=shape, threads=threads)
                self.fft_B_obj = pyfftw.builders.fftn(
                        B, s=shape, threads=threads)
                self.ifft_obj = pyfftw.builders.ifftn(
                        self.fft_A_obj.get_output_array(), s=shape,
                        threads=threads)
    
            else:
                self.fft_A_obj = pyfftw.builders.rfftn(
                        A, s=shape, threads=threads)
                self.fft_B_obj = pyfftw.builders.rfftn(
                        B, s=shape, threads=threads)
                self.ifft_obj = pyfftw.builders.irfftn(
                        self.fft_A_obj.get_output_array(), s=shape,
                        threads=threads)
    
        def __call__(self, A, B):
    
            fft_padded_A = self.fft_A_obj(A)
            fft_padded_B = self.fft_B_obj(B)
    
            return self.ifft_obj(fft_padded_A * fft_padded_B)
    
    N = 200
    
    A = np.random.rand(N, N, N)
    B = np.random.rand(N, N, N)
    

    在ipython中,你可以得到以下内容:

    In [1]: %run foo.py
    
    In [2]: timeit scipy.signal.fftconvolve(A,B,"same")
    1 loops, best of 3: 8.38 s per loop
    
    In [3]: custom_fft_conv_nthreads = CustomFFTConvolution(A, B, threads=1)
    
    In [4]: timeit custom_fft_conv_nthreads(A, B)
    1 loops, best of 3: 6.9 s per loop
    

    多线程:

    In [5]: custom_fft_conv_nthreads = CustomFFTConvolution(A, B, threads=4)
    
    In [6]: timeit custom_fft_conv_nthreads(A, B)
    1 loops, best of 3: 3.81 s per loop
    

    如果您通过在C = custom_fft_conv_nthreads(A, B) 之前插入start_time = timeit.default_timer() 来纠正您的代码以执行您认为它正在执行的操作,您会得到更接近预期的结果:

    10.8795630932
    8.31241607666
    

    【讨论】:

    • 我知道我在做什么。我从 timeit.default_timer() 返回的时间中减去 scipy.signal.fftconvolve 使用的时间,然后我估计“PyFFTW 大约慢了 7 倍”
    • 为了重现您的 cmets,我在 custom_fft_conv_nthreads 之前添加了 start_time。我在循环中测试我的代码:第一次运行:6.0643529892(Scipy fft convolve)35.0755908489(PyFFTW),第二次和另一个 6.10961890221(Scipy fft convolve)5.85012698174(PyFFTW)(稍微快一点)。不幸的是,在我的程序中,我使用更大的内核进行了一系列卷积,因此我无法使用 PyFFTW 加速。
    • 您编写的代码在计算 pyfftw 结果时包含了来自fftconvolve 的时间。您需要重新初始化它。其次,无论如何你都不应该这样做——这就是 timeit 的用途。 FFTW 旨在通过尝试各种选项然后以最快的速度工作 - 这就是在对象创建阶段需要时间的原因。可以使用花费更少时间的标志,我建议您阅读文档。几乎可以肯定的是,您可以为卷积使用单一尺寸(或少量尺寸),因此您无需计划每个循环
    • 感谢您的建议@HenryGomersall。我实际做的是:for _ in range(N): fftconvolve((1000,1000,100), kernel_n),其中内核为 (11,11,11); (13,13,13);等等。内核在每个循环的运行中长大。为了获得线性卷积,plan 必须在每个循环中改变它的大小。所以,总而言之,我不知道如何使用(如果可能的话)之前卷积的计划。
    • 哦,在那种情况下很容易。当您进行 FFT 卷积时,您必须有一对相同(完整)大小的中间数组。 pyfftw 通过使用将数据插入到数组的正确部分的包装类来处理此问题(shape 参数说明了这一点)。您可以手动执行此操作 - 只需为输出数组的大小创建一个 FFTW 对象,然后根据需要从不断增长的内核中填充它。如果内核一直在增长,您甚至不需要将之前的内核归零。然后它只是一个简单的复制并运行。
    猜你喜欢
    • 1970-01-01
    • 2015-11-16
    • 2019-10-10
    • 2018-02-22
    • 2014-06-16
    • 2021-03-09
    • 2020-10-09
    • 2012-07-22
    • 1970-01-01
    相关资源
    最近更新 更多