高性能的Python扩展(2)
admin
2023-07-31 01:42:38
0

简介

这篇文章是这系列文章的第二篇,我们的关注点在使用Numpy API为Python编写C扩展模块的过程。在第一部分中,我们建立了一个简单的N体模拟,并发现其瓶颈是计算体之间的相互作用力,这是一个复杂度为O(N^2)的操作。通过在C语言中实现一个时间演化函数,我们大概能以大约70倍来加速计算。

如果你还没有看过第一篇文章,你应该在继续看这篇文章之前先看一下。

在这篇文章中,我们将牺牲我们代码中的一些通用性来提升性能。

回顾

Wrold是存储N体状态的一个类。我们的模拟将演化一系列时间步长下的状态。

1234567891011121314151617181920212223242526272829303132333435363738 class World(object):    \”\”\”World is a structure that holds the state of N bodies and    additional variables.     threads : (int) The number of threads to use for multithreaded              implementations.    dt      : (float) The time-step.     STATE OF THE WORLD:      N : (int) The number of bodies in the simulation.    m : (1D ndarray) The mass of each body.    r : (2D ndarray) The position of each body.    v : (2D ndarray) The velocity of each body.    F : (2D ndarray) The force on each body.     TEMPORARY VARIABLES:     Ft : (3D ndarray) A 2D force array for each thread\’s local storage.    s  : (2D ndarray) The vectors from one body to all others.     s3 : (1D ndarray) The norm of each s vector.      NOTE: Ft is used by parallel algorithms for thread-local          storage. s and s3 are only used by the Python          implementation.    \”\”\”    def __init__(self, N, threads=1,                  m_min=1, m_max=30.0, r_max=50.0, v_max=4.0, dt=1e3):        self.threads = threads        self.N  = N        self.m  = np.random.uniform(m_min, m_max, N)        self.r  = np.random.uniform(r_max, r_max, (N, 2))        self.v  = np.random.uniform(v_max, v_max, (N, 2))        self.F  = np.zeros_like(self.r)        self.Ft = np.zeros((threads, N, 2))        self.s  = np.zeros_like(self.r)        self.s3 = np.zeros_like(self.m)        self.dt = dt

在开始模拟时,N体被随机分配质量m,位置r和速度v。对于每个时间步长,接下来的计算有:

1. 合力F,每个体上的合力根据所有其他体的计算。
2. 速度v,由于力的作用每个体的速度被改变。
3. 位置R,由于速度每个体的位置被改变。

访问宏

我们在第一部分创建的扩展模块使用宏来获取C语言中NumPy数组的元素。下面是这些宏中的一些宏的形式:

123 #define r(x0, x1) (*(npy_float64*)((PyArray_DATA(py_r) +                \\                                    (x0) * PyArray_STRIDES(py_r)[0] +   \\                                    (x1) * PyArray_STRIDES(py_r)[1])))

像这样使用宏,我们能使用像r(i, j)这样的简单标记来访问py_r数组中的元素。不管数组已经以某种形式被重塑或切片,索引值将匹配你在Python中看到的形式。

对于通用的代码,这就是我们想要的。在我们模拟的情况下,我们知道我们的数组的特点:它们是连续的,并且从未被切片或重塑。我们可以利用这一点来简化和提升我们代码的性能。

简单的C扩展 2

在本节中,我们将看到一个修改版本的C扩展,它摈弃了访问宏和NumPy数组底层数据的直接操作。本节中的代码src/simple2.c可在github上获得。

为了进行比较,之前的实现也可在这里获得。

演化函数

从文件的底部开始,我们可以看到,evolve函数与之前的版本一样,以相同的方式解析Python参数,但现在我们利用PyArray_DATA宏来获得一个纸箱底层的内存。我们将这个指针命名为npy_float64,作为double的一个别名。

123456789101112131415161718192021222324252627282930313233343536373839404142434445 static PyObject *evolve(PyObject *self, 迎加入翻译组。

简介

这篇文章是这系列文章的第二篇,我们的关注点在使用Numpy API为Python编写C扩展模块的过程。在第一部分中,我们建立了一个简单的N体模拟,并发现其瓶颈是计算体之间的相互作用力,这是一个复杂度为O(N^2)的操作。通过在C语言中实现一个时间演化函数,我们大概能以大约70倍来加速计算。

如果你还没有看过第一篇文章,你应该在继续看这篇文章之前先看一下。

在这篇文章中,我们将牺牲我们代码中的一些通用性来提升性能。

回顾

Wrold是存储N体状态的一个类。我们的模拟将演化一系列时间步长下的状态。

1234567891011121314151617181920212223242526272829303132333435363738 class World(object):    \”\”\”World is a structure that holds the state of N bodies and    additional variables.     threads : (int) The number of threads to use for multithreaded              implementations.    dt      : (float) The time-step.     STATE OF THE WORLD:      N : (int) The number of bodies in the simulation.    m : (1D ndarray) The mass of each body.    r : (2D ndarray) The position of each body.    v : (2D ndarray) The velocity of each body.    F : (2D ndarray) The force on each body.     TEMPORARY VARIABLES:     Ft : (3D ndarray) A 2D force array for each thread\’s local storage.    s  : (2D ndarray) The vectors from one body to all others.     s3 : (1D ndarray) The norm of each s vector.      NOTE: Ft is used by parallel algorithms for thread-local          storage. s and s3 are only used by the Python          implementation.    \”\”\”    def __init__(self, N, threads=1,                  m_min=1, m_max=30.0, r_max=50.0, v_max=4.0, dt=1e3):        self.threads = threads        self.N  = N        self.m  = np.random.uniform(m_min, m_max, N)        self.r  = np.random.uniform(r_max, r_max, (N, 2))        self.v  = np.random.uniform(v_max, v_max, (N, 2))        self.F  = np.zeros_like(self.r)        self.Ft = np.zeros((threads, N, 2))        self.s  = np.zeros_like(self.r)        self.s3 = np.zeros_like(self.m)        self.dt = dt

在开始模拟时,N体被随机分配质量m,位置r和速度v。对于每个时间步长,接下来的计算有:

1. 合力F,每个体上的合力根据所有其他体的计算。
2. 速度v,由于力的作用每个体的速度被改变。
3. 位置R,由于速度每个体的位置被改变。

访问宏

我们在第一部分创建的扩展模块使用宏来获取C语言中NumPy数组的元素。下面是这些宏中的一些宏的形式:

123 #define r(x0, x1) (*(npy_float64*)((PyArray_DATA(py_r) +                \\                                    (x0) * PyArray_STRIDES(py_r)[0] +   \\                                    (x1) * PyArray_STRIDES(py_r)[1])))

像这样使用宏,我们能使用像r(i, j)这样的简单标记来访问py_r数组中的元素。不管数组已经以某种形式被重塑或切片,索引值将匹配你在Python中看到的形式。

对于通用的代码,这就是我们想要的。在我们模拟的情况下,我们知道我们的数组的特点:它们是连续的,并且从未被切片或重塑。我们可以利用这一点来简化和提升我们代码的性能。

简单的C扩展 2

在本节中,我们将看到一个修改版本的C扩展,它摈弃了访问宏和NumPy数组底层数据的直接操作。本节中的代码src/simple2.c可在github上获得。

为了进行比较,之前的实现也可在这里获得。

演化函数

从文件的底部开始,我们可以看到,evolve函数与之前的版本一样,以相同的方式解析Python参数,但现在我们利用PyArray_DATA宏来获得一个纸箱底层的内存。我们将这个指针命名为npy_float64,作为double的一个别名。

12345678910111213141516171819202122232425262728293031323334353637383940414243

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
python清除字符串里非数字... 本文实例讲述了python清除字符串里非数字字符的方法。分享给大家供大家参考。具体如下: impor...