当前位置: 首页 > 面试题库 >

从Cython代码生成SIMD指令

淳于涛
2023-03-14
问题内容

我需要大致了解一下在高性能数字代码中使用Cython可以获得的性能。我感兴趣的一件事是找出优化的C编译器是否可以向量化Cython生成的代码。因此,我决定编写以下小示例:

import numpy as np
cimport numpy as np
cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef int f(np.ndarray[int, ndim = 1] f):
    cdef int array_length =  f.shape[0]
    cdef int sum = 0
    cdef int k
    for k in range(array_length):
        sum += f[k]
    return sum

我知道有Numpy函数可以完成这项工作,但是我想编写一个简单的代码来了解Cython的功能。事实证明,生成的代码是:

from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules = cythonize("sum.pyx"))

并致电:

python setup.py build_ext --inplace

生成一个看起来像这样的C代码循环:

for (__pyx_t_2 = 0; __pyx_t_2 < __pyx_t_1; __pyx_t_2 += 1) {
  __pyx_v_sum = __pyx_v_sum + (*(int *)((char *) 
    __pyx_pybuffernd_f.rcbuffer->pybuffer.buf +
    __pyx_t_2 * __pyx_pybuffernd_f.diminfo[0].strides)));
}

此代码的主要问题是,编译器在编译时不知道__pyx_pybuffernd_f.diminfo[0].strides数组的元素在内存中是否紧密在一起。没有这些信息,编译器将无法有效地向量化。

Cython有办法做到这一点吗?


问题答案:

您的代码中有两个问题(使用选项-a使其可见):

  1. numpy数组的索引效率不高
  2. 你忘intcdef sum=0

考虑到这一点,我们得到:

cpdef int f(np.ndarray[np.int_t] f):  ##HERE
    assert f.dtype == np.int
    cdef int array_length =  f.shape[0]
    cdef int sum = 0                  ##HERE
    cdef int k
    for k in range(array_length):
        sum += f[k]
    return sum

对于循环,以下代码:

int __pyx_t_5;
int __pyx_t_6;
Py_ssize_t __pyx_t_7;
....
__pyx_t_5 = __pyx_v_array_length;
for (__pyx_t_6 = 0; __pyx_t_6 < __pyx_t_5; __pyx_t_6+=1) {
   __pyx_v_k = __pyx_t_6;
   __pyx_t_7 = __pyx_v_k;
   __pyx_v_sum = (__pyx_v_sum + (*__Pyx_BufPtrStrided1d(__pyx_t_5numpy_int_t *, __pyx_pybuffernd_f.rcbuffer->pybuffer.buf, __pyx_t_7, __pyx_pybuffernd_f.diminfo[0].strides)));

}

这样做还不错,但是对于优化器而言,却不如人类编写的普通代码那么容易。正如您已经指出的那样,__pyx_pybuffernd_f.diminfo[0].strides在编译时未知,这会阻止矢量化。

但是,使用类型化的内存视图时,您会得到更好的结果,即:

cpdef int mf(int[::1] f):
    cdef int array_length =  len(f)
...

这导致了不透明的C代码-至少是我的编译器可以更好地优化:

 __pyx_t_2 = __pyx_v_array_length;
  for (__pyx_t_3 = 0; __pyx_t_3 < __pyx_t_2; __pyx_t_3+=1) {
    __pyx_v_k = __pyx_t_3;
    __pyx_t_4 = __pyx_v_k;
    __pyx_v_sum = (__pyx_v_sum + (*((int *) ( /* dim=0 */ ((char *) (((int *) __pyx_v_f.data) + __pyx_t_4)) ))));
  }

这里最关键的是,我们要让cython清楚,内存是连续的,即int[::1]int[:]numpy数组所看到的相比,stride!=1必须考虑可能的情况。

在这种情况下,用Cython生成的C代码导致同一汇编作为代码我会写。正如crisb指出的那样,相加-march=native会导致向量化,但是在这种情况下,两个函数的汇编器将再次略有不同。

但是,以我的经验,编译器通常会遇到一些问题,无法优化由cython创建的循环,并且/或者容易遗漏一个细节,从而阻止生成真正好的C代码。因此,我处理工作循环的策略是用纯C语言编写它们,并使用cython来包装/访问它们-
通常会更快一些,因为也可以使用专用的编译器标志来捕获此代码而不会影响整个Cython-模块。



 类似资料:
  • 我正在根据参考指南学习使用Map结构。日食验证是 面向Web开发人员的Eclipse Java EE IDE。版本:开普勒服务版本2 pom。xml如下所示,与参考指南相同 它表示在构建项目时将生成实现代码。但是,它似乎不会在目标/生成的源文件夹下生成

  • 问题内容: 所以我想通过cython从c调用一些python代码。我设法从c调用cython代码。而且我还可以从cython调用python代码。但是,当我将它们全部加在一起时,会丢失一些东西。 这是我的python代码(): 这是我的cython“ bridge”(): 这是c代码(): 运行此命令时,出现以下异常: 我怀疑缺少的部分: 我还没打电话 我还没有 Cython没有产生任何东西- 不

  • 我需要一个示例代码来学习如何从clang::ASTContext生成C代码。 我用c代码创建了ast,并在AST中做了一些更改,现在我想再次生成代码。

  • 我有一个由以下字符|分隔的csv文件。该文件有三列;一个是url(COL1),另一个是小文本(COL3),最后一个是图像位置(COL2)。我需要一个脚本,用csv文件中的数据给我这样的html代码: 我该怎么做?

  • 大多数Blockly应用程序需要将块转换为代码以执行。本页描述如何将代码生成器添加到自定义块。 首先,转到generators/目录并选择与您要生成的语言(JavaScript,Python,PHP,Lua,Dart等)相对应的子目录。假设您的代码块不适合现有类别,请创建一个新的JavaScript文件。这个新的JavaScript文件需要包含在<script ...>编辑器的HTML文件中的标记

  • Jboot 内置了一个简易的代码生成器,可以用来生成model层和Service层的基础代码,在生成代码之前,请先配置jboot.properties关于数据库相关的配置信息,Jboot 代码生成器会通过该配置去链接数据库。 jboot.datasource.type=mysql jboot.datasource.url=jdbc:mysql://127.0.0.1:3306/jbootdemo