MATLAB的内置conv算法
本文目录% 已读
1489 字
7 分钟
MATLAB的内置conv算法
MATLAB 的 conv/conv2/convn 本身并不用 FFT;它们实现的是“滑动求和”的直接卷积(time-domain)并在底层用高度优化的 C 循环与并行机制来提速。加速主要来自:多线程/向量化的内部实现、对 GPU/tall/distributed 数组的原生支持;当核或信号很大时,应改用频域卷积(FFT、overlap-add/save)或能自动切换算法的函数/工具。
MATLAB的内置算法为何更“快”
直接法 + 内部并行:conv 等函数按定义做逐点乘加(O(M·N)),但实现是编译代码并带隐式多线程优化;对中小尺寸核,这通常比 FFT 更快。 另外有第三方测评指出 MATLAB 内部对 conv* 采用滑窗并配合隐式多线程(非官方,但与实测一致)。
-
直接法 + 内部并行:conv 等函数按定义做逐点乘加(O(M·N)),但实现是编译代码并带隐式多线程优化;对中小尺寸核,这通常比 FFT 更快。 另外有第三方测评指出 MATLAB 内部对 conv* 采用滑窗并配合隐式多线程(非官方,但与实测一致)。
-
GPU / Tall / Distributed:把输入放到 gpuArray 上可让 conv 在 GPU 上执行;也支持 thread-based、tall 和分布式数组,用更多核/更多内存堆栈来提速或放大规模。
如何测出 conv(直接法) vs. FFT 卷积(含 overlap-add) 的分界点
function bench_conv_vs_fft()% BENCH_CONV_VS_FFT% 在你的硬件/版本上基准比较:% 1) conv(直接卷积,CPU/GPU)% 2) 一次性 FFT 卷积(CPU/GPU)% 3) Overlap-Add FFT 卷积(CPU/GPU)%% 结果:打印出“FFT 比 conv 更快”的近似分界核大小,并画出时间对比图。% 你可以修改“用户参数”以匹配你的真实数据规模。
%% ==== 用户参数 ====mode = "sweepKernel"; % "sweepKernel" 固定信号长度扫核大小;或 "sweepSignal" 固定核扫信号长度N_fixed = 2^18; % 固定信号长度(sweepKernel 模式下使用)K_list = [3 5 9 15 31 63 127 255 511 1023 2047]; % 扫描的核长度(奇数常见,可按需改)K_fixed = 255; % 固定核长度(sweepSignal 模式下使用)N_list = round(logspace(3,6,10)); % 扫描的信号长度
use_overlap_add = true; % 是否包含 overlap-add(分块 FFT)方案OA_block_target = 1<<14; % 期望块长的量级(会自动取最近的合适 FFT 点)
rng(0); % 固定随机种子,保证可复现nRepsSafety = 1; % 大问题规模时,避免超长基准,timeit 自带多次测量
hasGPU = exist('gpuDeviceCount','file') && gpuDeviceCount>0;
%% ==== 生成尺寸序列 ====if mode=="sweepKernel" N_vec = N_fixed*ones(size(K_list)); K_vec = K_list(:); x_label = 'Kernel length K'; x_ticks = K_list;elseif mode=="sweepSignal" N_vec = N_list(:); K_vec = K_fixed*ones(size(N_list)); x_label = 'Signal length N'; x_ticks = N_list;else error('mode 必须是 "sweepKernel" 或 "sweepSignal"');endnCases = numel(N_vec);
%% ==== 结果数组 ====t_conv_cpu = nan(nCases,1);t_fft_cpu = nan(nCases,1);t_oa_cpu = nan(nCases,1);t_conv_gpu = nan(nCases,1);t_fft_gpu = nan(nCases,1);t_oa_gpu = nan(nCases,1);
%% ==== 主循环 ====for i = 1:nCases N = N_vec(i); K = K_vec(i);
% 随机双精度数据(更接近实际) x = randn(N,1); h = randn(K,1);
% -------- CPU:conv 直接法 -------- t_conv_cpu(i) = timeit(@() conv(x,h,'full'), nRepsSafety);
% -------- CPU:一次性 FFT 卷积 -------- t_fft_cpu(i) = timeit(@() localFFTConv(x,h), nRepsSafety);
% -------- CPU:Overlap-Add -------- if use_overlap_add t_oa_cpu(i) = timeit(@() localOverlapAdd(x,h,OA_block_target), nRepsSafety); end
% -------- GPU(若可用)-------- if hasGPU xg = gpuArray(x); hg = gpuArray(h);
t_conv_gpu(i) = gputimeit(@() conv(xg,hg,'full'));
t_fft_gpu(i) = gputimeit(@() localFFTConv(xg,hg));
if use_overlap_add t_oa_gpu(i) = gputimeit(@() localOverlapAdd(xg,hg,OA_block_target)); end
% 清理 GPU 内存压力 reset(gpuDevice); end
% 正确性抽检(避免静默数值问题) % 仅在 CPU 上做一次检查,允许 FFT 有轻微浮点误差 if i==1 y_ref = conv(x,h,'full'); y_f = localFFTConv(x,h); err_f = norm(y_ref - y_f, inf) / max(1, norm(y_ref, inf)); assert(err_f < 1e-9, '一次性 FFT 卷积结果误差过大:%g', err_f);
if use_overlap_add y_oa = localOverlapAdd(x,h,OA_block_target); err_oa = norm(y_ref - y_oa, inf) / max(1, norm(y_ref, inf)); assert(err_oa < 1e-9, 'Overlap-Add 卷积结果误差过大:%g', err_oa); end end
fprintf('Case %d/%d: N=%d, K=%d | conv=%.4gs, fft=%.4gs, oa=%.4gs%s\n', ... i, nCases, N, K, t_conv_cpu(i), t_fft_cpu(i), t_oa_cpu(i), ... tern(hasGPU, sprintf(' | GPU conv=%.4gs, fft=%.4gs, oa=%.4gs', t_conv_gpu(i), t_fft_gpu(i), t_oa_gpu(i)), ''));end
%% ==== 统计并打印分界点(CPU/GPU) ====fprintf('\n=== 分界点(以“快过 10%%”为准)===\n');if mode=="sweepKernel" idx_cpu_fft = find(t_fft_cpu./t_conv_cpu < 0.9, 1, 'first'); idx_cpu_oa = find(t_oa_cpu./t_conv_cpu < 0.9, 1, 'first'); if ~isempty(idx_cpu_fft), fprintf('CPU:一次性 FFT 在 K ≈ %d 起更快\n', x_ticks(idx_cpu_fft)); end if use_overlap_add && ~isempty(idx_cpu_oa), fprintf('CPU:Overlap-Add 在 K ≈ %d 起更快\n', x_ticks(idx_cpu_oa)); end if hasGPU idx_gpu_fft = find(t_fft_gpu./t_conv_gpu < 0.9, 1, 'first'); idx_gpu_oa = find(t_oa_gpu./t_conv_gpu < 0.9, 1, 'first'); if ~isempty(idx_gpu_fft), fprintf('GPU:一次性 FFT 在 K ≈ %d 起更快\n', x_ticks(idx_gpu_fft)); end if use_overlap_add && ~isempty(idx_gpu_oa), fprintf('GPU:Overlap-Add 在 K ≈ %d 起更快\n', x_ticks(idx_gpu_oa)); end endelse idx_cpu_fft = find(t_fft_cpu./t_conv_cpu < 0.9, 1, 'first'); idx_cpu_oa = find(t_oa_cpu./t_conv_cpu < 0.9, 1, 'first'); if ~isempty(idx_cpu_fft), fprintf('CPU:一次性 FFT 在 N ≈ %d 起更快\n', x_ticks(idx_cpu_fft)); end if use_overlap_add && ~isempty(idx_cpu_oa), fprintf('CPU:Overlap-Add 在 N ≈ %d 起更快\n', x_ticks(idx_cpu_oa)); end if hasGPU idx_gpu_fft = find(t_fft_gpu./t_conv_gpu < 0.9, 1, 'first'); idx_gpu_oa = find(t_oa_gpu./t_conv_gpu < 0.9, 1, 'first'); if ~isempty(idx_gpu_fft), fprintf('GPU:一次性 FFT 在 N ≈ %d 起更快\n', x_ticks(idx_gpu_fft)); end if use_overlap_add && ~isempty(idx_gpu_oa), fprintf('GPU:Overlap-Add 在 N ≈ %d 起更快\n', x_ticks(idx_gpu_oa)); end endend
%% ==== 画图(CPU / 可选 GPU) ====figure('Name','conv vs FFT benchmark'); hold on; grid on;if mode=="sweepKernel" xval = K_vec;else xval = N_vec;end
% CPUplot(xval, t_conv_cpu, '-', 'DisplayName','CPU conv');plot(xval, t_fft_cpu, '-', 'DisplayName','CPU FFT');if use_overlap_add plot(xval, t_oa_cpu, '-', 'DisplayName','CPU FFT (overlap-add)');end
% GPUif hasGPU plot(xval, t_conv_gpu, '--', 'DisplayName','GPU conv'); plot(xval, t_fft_gpu, '--', 'DisplayName','GPU FFT'); if use_overlap_add plot(xval, t_oa_gpu, '--', 'DisplayName','GPU FFT (overlap-add)'); endend
set(gca,'XScale','log','YScale','log');xlabel(x_label); ylabel('Time (s)');legend('Location','best');title(sprintf('N (fixed or sweep) / K (fixed or sweep): N=%s, K=%s', num2str(N_fixed), num2str(K_fixed)));
end % main function
%% ====== 辅助函数 ======
function y = localFFTConv(x,h)% 一次性 FFT 卷积(full 长度)n = numel(x) + numel(h) - 1;nfft = 2^nextpow2(n);y = ifft( fft(x, nfft) .* fft(h, nfft) );y = y(1:n);% 保持原类型(支持 gpuArray)if ~isa(y, 'gpuArray') y = real(y); % CPU 上去除数值噪声的虚部endend
function y = localOverlapAdd(x,h,OA_block_target)% Overlap-Add(分块 FFT),full 长度Nx = numel(x); M = numel(h);L = max(1, OA_block_target); % 期望块长nfft = 2^nextpow2(L + M - 1); % 实际 FFT 长度L = nfft - M + 1; % 真正每块放入的样本数H = fft(h, nfft);y = zeros(Nx + M - 1, 1, 'like', x); % 和 x 同类型(支持 gpuArray)
pos = 1;while pos <= Nx Lb = min(L, Nx - pos + 1); xb = x(pos : pos + Lb - 1); Yb = ifft( fft(xb, nfft) .* H ); y(pos : pos + Lb + M - 2) = y(pos : pos + Lb + M - 2) + Yb(1 : Lb + M - 1); pos = pos + Lb;end
if ~isa(y, 'gpuArray'), y = real(y); endend
function s = tern(cond, a, b)if cond, s = a; else, s = b; endend文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
MATLAB的内置conv算法
https://blog.haihengyang.com/posts/paper/matlab-convolution/相关文章智能推荐
1
常用MATLAB函数及示例
MATLAB常用 MATLAB 函数、计算示例及仿真图,整理雷达与信号处理中的实践方法。
2
《合成孔径雷达成像算法与实现》学习记录
雷达与电磁《合成孔径雷达成像算法与实现》学习记录,涵盖 SAR 基础、信号处理与成像算法。
3
Otsu 算法
图像处理Otsu 阈值分割的基本思想、类间方差公式与算法推导。
4
VS Code Remote-SSH 卡在 Waiting for server log... 的故障排查与处理
开发环境Remote-SSH 停留在 Waiting for server log 时的排查过程与 VS Code Server 故障处理。
5
机场场景模型电磁参数参考
雷达与电磁机场场景中跑道、油罐、机库等目标的等效材料定义、电磁参数及参考来源。
随机文章随机推荐
