如何使用数学将 NumPy 函数的性能提高 50%

news2024/12/24 9:03:01

一、说明

        2D 傅里叶变换是本世纪最重要的计算机科学算法之一。它已在我们的日常生活中得到应用,从Instagram过滤器到MP3文件的处理。

        普通用户最常用的实现,有时甚至是在不知不觉中,是 NumPy 的改编。然而,尽管它很受欢迎,但他们的算法并不是最有效的。通过一些简单的操作和 2015 年的一篇文章,我们在性能上击败了 NumPy 算法 30-60%。当前实现的核心问题是一个简单的事实,即它最初是从性能弱算法派生的。

二、NumPy实现的算法

        从本质上讲,NumPy实现的算法将常规的一维FFT依次应用于二维,这显然不能成为最优解。

        相反,在2015年,两位俄罗斯科学家提出了他们的算法版本,将一维蝶蝶变换的想法应用于二维信号。我们通过添加我们的想法有效地实现了他们的基本算法概念。

        在构建了本文中的朴素算法后,我们继续进行优化,如下所示:

void _fft2d( /* Square matrix of size N */ ) {
  // base case {
  if (N == 1) return;
  // } base case 

  int n = N >> 1;
    
  /* pseudo code {
  
  ...Creating 4 temprorary matrices here...
    
  // X(x, y, i, j)
  // x, y -- indexing over temporary submatricies
  // i, j -- indexing over rows, columns in each submatrix
 
  _fft2d(&X(0, 0), root * root, ...);
  _fft2d(&X(0, 1), root * root, ...);
  _fft2d(&X(1, 0), root * root, ...);
  _fft2d(&X(1, 1), root * root, ...);
  } pseudo code */

  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
      auto x00 = X(0, 0, i, j);
      auto x10 = X(1, 0, i, j) * /* W[i] */;
      auto x01 = X(0, 1, i, j) * /* W[j] */;
      auto x11 = X(1, 1, i, j) * /* W[i] * W[j] */;
      X(0, 0, i, j) = x00 + x10 + x01 + x11;
      X(0, 1, i, j) = x00 + x10 - x01 - x11;
      X(1, 0, i, j) = x00 - x10 + x01 - x11;
      X(1, 1, i, j) = x00 - x10 - x01 + x11;
    }
  }
}

任何递归算法都可以通过增加基本情况的大小来增强。以下是我们的处理方式:

void _fft2d( /* Square matrix of size N */ ) {
  // base case {
  if (N == 1) return;
  if (N == 2) {
#define Y(y, x) (V[(y)*rowsize + (x)])
    auto x00 = Y(0, 0);
    auto x10 = Y(1, 0);
    auto x01 = Y(0, 1);
    auto x11 = Y(1, 1);
    Y(0, 0) = x00 + x10 + x01 + x11;
    Y(0, 1) = x00 + x10 - x01 - x11;
    Y(1, 0) = x00 - x10 + x01 - x11;
    Y(1, 1) = x00 - x10 - x01 + x11;
    return;
  }
  // } base case 

  // ...
}

        进一步的逻辑步骤是消除在每个递归步骤中创建四个不必要的临时子矩阵,而支持单个子矩阵。为此,我们使用了 algorithmica 文章中的概念,并将其修改为二维矩阵。此功能还有助于我们减少不必要的分配并增加缓存命中次数。

// Computing values for rev_bits[n]
auto revbits = [](size_t *v, size_t n) {
  int lg_n = log2(n);
  forn(i, n) {
    int revi = 0;
    forn(l, lg_n) revi |= ((i >> l) & 1) << (lg_n - l - 1);
    v[i] = revi;
  }
};

size_t *rev_n = new size_t[N], *rev_m = new size_t[M];
revbits(rev_n, N), revbits(rev_m, M);  

// Transforming matrix
forn(i, N) {
  int rev_i = rev_n[i];
  forn(j, M) {
    if ((i < rev_i) || ((i == rev_i) && (j < rev_m[j])))
      std::swap(V[i * M + j], V[rev_i * M + rev_m[j]]);
  }
}

        我们的下一个挑战是预先计算团结的根源:

int mxdim = std::max(N, M);
const int lg_dim = log2(mxdim);
auto W = new fft_type[mxdim];
auto rooti = std::polar(1., (inverse ? 2 : -2) * fft::pi / mxdim);

// Computing look-up matrix for roots
auto cur_root = rooti;
W[0] = 1;
forn (i, mxdim - 1) W[i + 1] = W[i] * cur_root;

        我们怎么能用这样的数组过关?让我们注意到,在朴素实现中,在初始递归步骤中,我们经过一个根的数组。我们还传递到下一个递归级别,即此根的平方(W[2])。在下一个递归级别,我们传递相同的幂数组,但以 2 为增量。从这个观察中,我们可以推导出,在第 i 个递归级别上,我们将通过数组 W 的步骤是 2ⁱ。

        在此阶段,我们收到以下代码:

void _fft2d(
    fft_type *__restrict__ V,
    size_t N,
    size_t rowsize,
    fft_type *__restrict__ W,
    int step
  ) {
  // base case {
  if (N == 1) return;
  if (N == 2) {
#define Y(y, x) (V[(y)*rowsize + (x)])
    auto x00 = Y(0, 0);
    auto x10 = Y(1, 0);
    auto x01 = Y(0, 1);
    auto x11 = Y(1, 1);
    Y(0, 0) = x00 + x10 + x01 + x11;
    Y(0, 1) = x00 + x10 - x01 - x11;
    Y(1, 0) = x00 - x10 + x01 - x11;
    Y(1, 1) = x00 - x10 - x01 + x11;
    return;
  }
  // } base case 

  int n = N >> 1;

#define X(y, x, i, j) (V[((y)*n + (i)) * rowsize + ((x)*n) + j])
#define params n, rowsize, W, (step << 1)
  _fft2d(&X(0, 0, 0, 0), params);
  _fft2d(&X(0, 1, 0, 0), params);
  _fft2d(&X(1, 0, 0, 0), params);
  _fft2d(&X(1, 1, 0, 0), params);

  for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
      auto x00 = X(0, 0, i, j);
      auto x10 = X(1, 0, i, j) * W[step * i];
      auto x01 = X(0, 1, i, j) * W[step * j];
      auto x11 = X(1, 1, i, j) * W[step * (i + j)];
      X(0, 0, i, j) = x00 + x10 + x01 + x11;
      X(0, 1, i, j) = x00 + x10 - x01 - x11;
      X(1, 0, i, j) = x00 - x10 + x01 - x11;
      X(1, 1, i, j) = x00 - x10 - x01 + x11;
    }
  }
}

        原始算法还有另一个明显的缺点——它只处理维度等于 2 次方的平方矩阵。使用一些简单的修改,我们可以将其扩展到矩形矩阵。

void _plan(
    fft_type *__restrict__ V,
    size_t N,
    size_t M,
    size_t rowsize,
    fft_type *__restrict__ W,
    int step_i,
    int step_j
  ) {
  // Computing square matrix
  if (N == M) _fft2d(V, N, rowsize, W, step_i);
  // Performing vertical split
  else if (N > M) {
    int n = N >> 1;
#define Y(y, i, j) (V[((y)*n + (i)) * rowsize + j])
#define params n, M, rowsize, W, (step_i << 1), step_j
    _plan(&Y(0, 0, 0), params);
    _plan(&Y(1, 0, 0), params);

    forn (i, n) {
      forn (j, M) {
        auto y00 = Y(0, i, j);
        auto y10 = Y(1, i, j) * W[i * step_i];
        Y(0, i, j) = y00 + y10;
        Y(1, i, j) = y00 - y10;
      }
    }
  // Performing horizontal split
  } else { /* ...Analogical approach... */ }
}

        值得一提的是,NumPy在其算法中在FFT下进行了额外的分配,将其中的类型带到了np.complex128;如果我们避免这一步,我们可以获得大约 10% 的优势。我们最终也实现了多线程。

        作为可视化表示,我们可以提供带有运行时的表格,还可以提供显示我们关于 NumPy 的工作效率的图表:

        结果表

表示图形

三、结论

        俄罗斯数学家修改后的算法在效率方面超过了NumPy引擎盖下的“行和列”。一些逻辑操作,例如基本大小写增加,显着提高了我们的优化。

        至关重要的是,我们在实现过程中执行的步骤也可以用于其他算法,这在未来可能对您有所帮助。同样值得注意的是,虽然我们已经做出了坚实的努力,但仍然可以通过添加不同大小的填充矩阵来加强实现。这篇文章旨在分享源代码,这可能有助于改进各种项目中转换的计算。

        存储库链接可以在下面找到,或者您也可以使用终端直接导入包:

pip3 install git+https://github.com/2D-FFT-Project/2d-fft

参考资源:

包含源代码的存储库

  • FFT, 算法
  • 二维快速傅里叶变换:Cooley-Tukey算法模拟中的蝴蝶,V. S. Tutatchikov为IEEE,2016年

亚历山大·莱文

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/911628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CDH集群离线配置python3环境,并安装pyhive、impyla、pyspark

背景&#xff1a; 项目需要对数仓千万级数据进行分析、算法建模。因数据安全&#xff0c;数据无法大批量导出&#xff0c;需在集群内进行分析建模&#xff0c;但CDH集群未安装python3 环境&#xff0c;需在无网情况下离线配置python3环境及一系列第三方库。 采取策略&#xf…

python分析实战(4)--获取某音热榜

1. 分析需求 打开某音热搜&#xff0c;选择需要获取的热榜如图 查找包含热搜内容的接口返回如图 将url地址保存 2. 开发 定义请求头 headers {Cookie: 自己的cookie,Accept: application/json, text/plain, */*,Accept-Encoding: gzip, deflate,Host: www.douyin.com,…

vue3+element下拉多选框组件

<!-- 下拉多选 --> <template><div class"select-checked"><el-select v-model"selected" :class"{ all: optionsAll, hidden: selectedOptions.data.length < 2 }" multipleplaceholder"请选择" :popper-app…

C++信息学奥赛1129:统计数字字符个数

这段代码的功能是计算一个输入字符串中的数字字符个数。 解析注释后的代码如下&#xff1a; #include<bits/stdc.h> using namespace std; int main() {string arr; // 定义字符串变量arr&#xff0c;用来存储输入的字符串getline(cin, arr); // 通过getline函数输入完…

企业文件透明加密软件——「天锐绿盾」数据防泄密管理软件系统

PC访问地址&#xff1a; 首页 一、文档透明加密软件 文档透明加密功能&#xff1a;在不影响单位内部员工对电脑任何正常操作的前提下&#xff0c;文档在复制、新建、修改时被系统强制自动加密。文档只能在单位内部电脑上正常使用&#xff0c;在外部电脑上使用是乱码或无法打…

前端通信(渲染、http、缓存、异步、跨域)自用笔记

SSR/CSR&#xff1a;HTML拼接&#xff1f;网页源码&#xff1f;SEO/交互性 SSR &#xff08;server side render&#xff09;服务端渲染&#xff0c;是指由服务侧&#xff08;server side&#xff09;完成页面的DOM结构拼接&#xff0c;然后发送到浏览器&#xff0c;为其绑定状…

Qt+C++串口调试接收发送数据曲线图

程序示例精选 QtC串口调试接收发送数据曲线图 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<QtC串口调试接收发送数据曲线图>>编写代码&#xff0c;代码整洁&#xff0c;规则&…

为何lazada、亚马逊、速卖通卖家都选择自养账号测评?

无论是做亚马逊还是shopee、Lazada、速卖通、wish、煤炉、拼多多Temu、敦煌网、eBay、Etsy、Newegg、美客多、Allegro、阿里国际、poshmark、沃尔玛、joom、OZON等平台。如果想要销量好&#xff0c;免不了进行补单测评的&#xff0c;因为不管对于哪一个平台的店铺新产品而言&am…

探工业互联网的下一站!腾讯云助力智造升级

引言 数字化浪潮正深刻影响着传统工业形态。作为第四次工业革命的重要基石&#xff0c;工业互联网凭借其独特的价值快速崛起&#xff0c;引领和推动着产业变革方向。面对数字化时代给产业带来的机遇与挑战&#xff0c;如何推动工业互联网的规模化落地&#xff0c;加速数字经济…

开利网络受邀参与御盛马术庄园发展专委会主题会议

近日&#xff0c;开利网络受邀参与深度合作客户御盛马术庄园组织的首届发展专委会主体会议&#xff0c;就马术庄园发展方向进行沟通&#xff0c;数字化也是重要议题之一。目前&#xff0c;御盛马术庄园已经完成数字化系统的初步搭建&#xff0c;将通过线上线下相结合的方式搭建…

编写接口文档示例:从零开始,轻松掌握关键技巧

接口文档的编写是软件开发中至关重要的一环&#xff0c;本文将详细介绍如何编写接口文档示例&#xff0c;为您揭示从基础知识到高级技巧的全过程。通过实用的指导和比喻&#xff0c;让您轻松掌握编写接口文档示例的艺术。 在现代软件开发中&#xff0c;编写接口文档示例是确保项…

Linux 上 离线部署GeoScene Server Py3 运行时环境

默认安装ArcGIS Pro的时候&#xff0c;会自动部署上Python3环境&#xff0c;所以在windows上不需要考虑这个问题&#xff0c;但是linux默认并不部署Py3&#xff0c;因此需要单独部署&#xff0c;具体部署可以参考Linux 上 ArcGIS Server 的 Python 3 运行时—ArcGIS Server | A…

PAT(Advanced Level) Practice(with python)——1067 Sort with Swap(0, i)

Code # 输入有毒&#xff0c;需避坑 # N int(input()) L list(map(int,input().split())) N L[0] L L[1:] res 0 for i in range(1,N):while L[0]!0:# 把所有不在正常位置下的数换到正常t L[0]L[0],L[t] L[t],L[0]res1if L[i]!i:# 换完全后如果对应位置下的数不是目标…

【校招VIP】测试专业课之TCP/IP模型

考点介绍&#xff1a; 大厂测试校招面试里经常会出现TCP/IP模型的考察&#xff0c;TCP/IP协议是网络基础知识&#xff0c;但是在校招面试中很多同学在基础回答中不到位&#xff0c;或者倒在引申问题里&#xff0c;就丢分了。 『测试专业课之TCP/IP模型』相关题目及解析内容可点…

免费开源CRM:有哪些免费开源的CRM系统可供选择?

CRM系统是什么 CRM就是客户关系管理系统&#xff0c;简单来说&#xff0c;就是一个要做到集客户管理&#xff0c;产品进销存&#xff0c;订单跟进&#xff0c;数据分析&#xff0c;售后维护为一体的系统。而开源的CRM系统&#xff0c;最基本的含义是代码是公开的&#xff0c;任…

innovus添加pad的命令

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 innovus中添加pad需要使用addInst命令创建physical cell&#xff0c;因为pad没有逻辑功能&#xff0c;不存在与网表中&#xff0c;需要自己创建。 添加pad用addInst -inst $pa…

pr剪辑工具绿色版本

免费提供 提取码: 402s Pr提供了采集、剪辑、调色、美化音频、字幕添加、 输出、DVD刻录等一整套流程&#xff0c; 并和其他Adobe 软件高效集成&#xff0c;使您足以完成在编辑、 制作、工作上遇到的所有挑战&#xff0c;满足您创建高质量作品的要求。 Pr的版本选择 常用…

通过浏览器控制台使用js脚本进行浏览器操作(定时点击等)

进行此操作前我们首先需要了解js编程语言 --了解之后我们就可以去操作了 这里我们拿csdn评论举例子&#xff1b; 点开评论界面右键审查元素 此时我们需要找到输入框dom和评论按钮dom 点击元素之后点击箭头然后去界面上选中文本框核按钮 然后我们就可以知道这个文本框的id 同…

没人用还占空间 微软Win11系统将允许卸载更多内置软件

不出意外的话&#xff0c;下个月Win11系统就要迎来Win11 23H2升级了&#xff0c;这是第二个年度更新&#xff0c;带来永不合并任务栏、ZIP解压缩原生支持等新功能。 在推新的同时&#xff0c;微软还要除旧&#xff0c;Win11系统内置了很多应用&#xff0c;一些是集成到系统功能…

ARM汇编【2】:LOAD 和 STORE

ARM使用load-store指令进行内存访问&#xff0c;这意味着只有LDR和STR指令才能访问内存&#xff0c;虽然在X86上&#xff0c;大多数指令都可以对内存中的数据进行操作&#xff0c;但在ARM上&#xff0c;数据在进行操作之前必须从内存移动到寄存器中。这意味着&#xff0c;在ARM…