以下 cython 3.0.8 代码正在带有 -O3 编译标志的支持 AVX2 的机器上编译和运行,但在从下面的 python main 使用时不会使用任何 SIMD 指令。
import numpy as np
from libc.math cimport fabs
cpdef inline double[:,:] go(double[:] stream, double[:] query):
matrix = np.empty((len(stream), len(query)))
cdef double [:, :] matrix_c = matrix
stream = np.asarray(stream)
query = np.asarray(query)
cdef int i, j
cdef int stream_len = len(stream)
for i in range(stream_len):
for j in range(len(query)):
matrix_c[i, j] = fabs(stream[i] - query[j])
return matrix
使用
perf
命令运行 python main,当 i
和 j
范围分别超过 100000 和 20 时,对 SIMD 指令的调用少于 10 次:
import timeit
import numpy as np
import go
if __name__ == '__main__':
np.random.seed(42)
stream = np.random.rand(100000)
query = np.linspace(0, 1, 20)
print(f'mean runtime is {np.mean(timeit.repeat(lambda: go(stream, query), number=10, repeat=100))}')
您对如何修改它以允许底层编译器 (x86_64-linux-gnu-gcc) 利用 SIMD 向量运算有什么建议吗?
为了检查是否正在使用 SIMD,我使用以下命令,但如果您推荐一种方法,我很乐意使用不同的方法:
perf stat -e instructions:u,fp_arith_inst_retired.128b_packed_single:u,fp_arith_inst_retired.256b_packed_single:u,fp_arith_inst_retired.128b_packed_double:u,fp_arith_inst_retired.256b_packed_double:u -- python -m main.py
确实 perf 显示了 150 万次 SIMD 使用...
这是 cython 3.0.x,因此某些语法对某些人来说可能看起来很新。我正在 Ubuntu 上编译和运行,这是我第一次使用 cython 以及寻求显式强制使用 SIMD。尝试
-march=native
没有什么区别。
我附上了 cython 构建期间进行的 gcc 调用:
x86_64-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=格式安全 -g -fwrapv -O2 -fPIC -I/.venv3.10/include -I/usr/include/python3.10 -c ....c -o build/temp.linux-x86_64-cpython-310/....o - O3 -三月=本地人
x86_64-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -g -fwrapv -O2 build/temp.linux-x86_64-cpython-310/。 ...o -L/usr/lib/x86_64-linux-gnu -o build/lib.linux-x86_64-cpython-310/....cpython-310-x86_64-linux-gnu.so -O3 -march=本地人
使用
-fopt-info
标志确实会在现在更长的构建输出(数千行)中产生 24 个循环实例,但是它们与我很难将其与我自己的代码逻辑关联起来的代码行相关,例如cython 处理其内存视图的代码行。输出线如:
main.c:14454:3:优化:使用 32 字节向量循环向量化
我想如果我自己用 C 语言编写这个,我可以更直接地将优化消息与我的代码关联起来。
好吧,毕竟是用SIMD,我只是疏忽了选择让perf命令监控哪些指令。由于我的代码使用双精度,这是演示 SIMD 数量的正确 perf 命令:
perf stat -e instructions:u,fp_arith_inst_retired.128b_packed_single:u,fp_arith_inst_retired.256b_packed_single:u,fp_arith_inst_retired.128b_packed_double:u,fp_arith_inst_retired.256b_packed_double:u -- python -m main.py
当然,在这种特定情况下,监视
*_single
指令是多余的,但在其他情况下可能会派上用场(当涉及非双浮点类型时)。
我应该以某种方式追踪构建的汇编代码实际使用的策略,以了解它的效率,或者更具对比性地关闭该优化,以比较它对平均运行时间的加速程度。
感谢@PeterCordes 对此提供的所有帮助。