CSS*_*782 5 c openmp cython visual-studio python-3.x
我有一个 C 函数,它执行一些 I/O 操作和解码,我想从 python 脚本调用它。
C 函数在由 Visual Studio 命令行 C 编译器编译时工作正常,并且在禁用多线程的情况下通过 Cython 调用时也工作正常。但是,当使用 OpenMP 多线程调用时,在前几百万个循环中运行良好,但在接下来的几百万个循环中 CPU 使用率慢慢下降,直到最终停止并且不会失败,但也不会继续计算。
C文件如下:
//block_reader.c
#include "block_reader.h" //contains block_data_t, decode_block, get_block_data
#include <stdio.h>
#include <stdlib.h>
#define NTHREADS 8
int decode_blocks(block_data_t *block_data_array, int num_blocks, int *values){
int block;
#pragma omp parallel for num_threads(NTHREADS)
for(block=0; block<num_blocks; block++){
decode_block(block_data_array[i], values);
}
}
int main(int argc, char *argv[]) {
int num_blocks = 250000, block_size = 4096;
block_data_t *block_data_array = get_block_data();
int *values = (long long *)malloc(num_blocks * block_size * sizeof(int));
int i, block;
for(i=0; i<1000; i++){
printf("experiment #%d\n", i+1);
decode_blocks(block_data_array, values)
}
}
}
Run Code Online (Sandbox Code Playgroud)
当在 Visual Studio x64 命令行上编译时cl /W3 -openmp block_reader.c block_helper.c zstd.lib,主函数循环一直进行实验 #1000,CPU 使用率始终为 90%(我的机器有 8 个逻辑线程,我不知道为什么它的上限为90% 在纯 C 中,当我从 openMP 编译指示中删除 num_threads(NTHREADS) 时,我遇到了同样的问题,但我并不真正担心它)。
然而,当我将它包装在 Cython 中并在 python 中循环时:
#block_reader_wrapper.pyx
from libc.stdlib cimport malloc
from libc.stdio cimport printf
cimport openmp
cimport block_reader_defns #contains block_data_t
import numpy as np
cimport numpy as np
cimport cython
@cython.boundscheck(False) # Deactivate bounds checking.
@cython.wraparound(False) # Deactivate negative indexing.
cpdef tuple read_blocks(block_data_array):
cdef np.ndarray[np.int32_t, ndim=1] values = np.zeros(size, dtype=np.int32_t)
cdef int[::1] values_view = values
decode_blocks(block_data_array, len(block_data_array), num_blocks, &values_view[0])
return values
cdef extern from "block_reader.h":
int decode_blocks(char**, b_metadata*, unsigned int, unsigned long long*, long long*, int*)
#setup.block_reader_wrapper.py
from setuptools import setup, Extension
from Cython.Build import cythonize
import numpy
ext_modules = [
Extension(
"block_reader_wrapper",
["block_reader_wrapper.pyx", "block_reader.c", "block_helper.c"],
libraries=["zstd"],
library_dirs=["{dir}/vcpkg/installed/x64-windows/lib"],
include_dirs=['{dir}/vcpkg/installed/x64-windows/include', numpy.get_include()],
extra_compile_args=['/openmp', '-O2'], #Have tried -O2, -O3 and no optimization
extra_link_args=['/openmp'], #always gets LINK : warning LNK4044: unrecognized option '/openmp'; ignored despite the docs asking for it https://cython.readthedocs.io/en/latest/src/userguide/parallelism.html
)
]
setup(
ext_modules = cythonize(ext_modules,
gdb_debug=True,
annotate=True,
)
)
#experiment.py
from block_reader_wrapper import read_blocks
from block_data_gen import get_block_data
for i in range(1000):
print("experiment", i+1)
read_blocks(get_block_data())
Run Code Online (Sandbox Code Playgroud)
我以 100% 的 CPU 使用率进行实验 #10(并且运行速度比 90% 上限的纯 C 快一点),但在实验 #11 - 实验 #16 之间,CPU 使用率以 1 个逻辑线程的增量缓慢下降资源,直到 CPU 使用率达到 1 个逻辑线程的底部,尽管我的任务管理器声称 python 使用了大约 20% 的 CPU 使用率,但该进程停止输出数据。内存使用率始终相当低 (~10%)。
我认为这一定与 Cython 的 OpenMP 链接有关,也许隐式限制了我可以传递给其工作线程的有效负载数量。
任何见解都将不胜感激,我需要它最终在 Windows和Ubuntu上工作,这就是我首先选择 openMP 的原因。
编辑1:根据 DavidW 的建议,我替换了:
cdef np.ndarray[np.int32_t, ndim=1] values = np.zeros(size, dtype=np.int32_t)
Run Code Online (Sandbox Code Playgroud)
和:
cdef array.array values, values_temp
values_temp = array.array('q', [])
values = array.clone(values_temp, size, zero=True)
Run Code Online (Sandbox Code Playgroud)
不幸的是这并没有解决问题。
编辑 2 和 3:在对“停止运行”的进程进行分析后,我发现很大一部分 CPU 时间都花在了等待上。free_base具体来说是malloc_base模块 ucrtbase.dll 中的函数
编辑 4:我用 ctypes 而不是 cython 重写了包装器,它利用了相同的 C -> Python API,所以存在同样的问题也许并不奇怪(尽管 ctypes 的停止速度大约是 Cython 的两倍) )。
VTune 总结:
Elapsed Time: 285419.416s
CPU Time: 22708.709s
Effective Time: 9230.924s
Spin Time: 13477.785s
Overhead Time: 0s
Total Thread Count: 10
Paused Time: 0s
Top Hotspots
Function Module CPU Time
free_base ucrtbase.dll 9061.852s
malloc_base ucrtbase.dll 8308.887s
NtWaitForSingleObject ntdll.dll 1283.721s
func@0x180020020 USER32.dll 820.759s
func@0x18001c630 tsc_block_reader.cp38-win_amd64.pyd 753.774s
[Others] N/A* 2479.716s
Effective CPU Utilization Histogram
Simultaneously Utilized Logical CPUs Elapsed Time Utilization threshold
0 279744.7901384001 Idle
1 5446.2851121 Poor
2 177.8078306 Poor
3 40.3033061 Poor
4 10.2292884 Poor
5 0 Poor
6 0 Poor
7 0 Ok
8 0 Ideal
Run Code Online (Sandbox Code Playgroud)
尽管它说大约 80% 的 CPU 时间处于空闲状态,但应该在 30 秒内完成的循环在 2 天后甚至没有完成,因此它远远超过 80% 的空闲时间。
看起来大部分空闲时间都花在ucrtbase.dll