【问题标题】:OpenMP, Python, C Extension, Memory Access and the evil GILOpenMP、Python、C 扩展、内存访问和邪恶的 GIL
【发布时间】:2014-02-03 09:29:37
【问题描述】:

所以我目前正在尝试为一些 2d ndarray 做类似 A**b 的事情,并为 Python 并行做一个 double b 。我想使用 OpenMP 使用 C 扩展来做到这一点(是的,我知道,有 Cython 等,但在某些时候,我总是遇到那些“高级”方法的麻烦......)。

这是我的 gaussian.so 的 gaussian.c 代码:

void scale(const double *A, double *out, int n) {
    int i, j, ind1, ind2;
    double power, denom;
    power = 10.0 / M_PI;
    denom = sqrt(M_PI);

    #pragma omp parallel for
    for (i = 0; i < n; i++) {
        for (j = i; j < n; j++) {
            ind1 = i*n + j;
            ind2 = j*n + i;
            out[ind1] = pow(A[ind1], power) / denom;
            out[ind2] = out[ind1];
        }
    }

(A 是一个双方矩阵,out 具有相同的形状,n 是行/列数)所以重点是更新一些对称距离矩阵 - ind2 是 ind1 的转置索引。

我使用gcc -shared -fopenmp -o gaussian.so -lm gaussian.c 编译它。我直接通过 Python 中的 ctypes 访问该函数:

test = c_gaussian.scale
test.restype = None
test.argtypes = [ndpointer(ctypes.c_double,
                           ndim=2,
                           flags='C_CONTIGUOUS'), # array of sample
                 ndpointer(ctypes.c_double,
                           ndim=2,
                           flags='C_CONTIGUOUS'), # array of sampl
                 ctypes.c_int # number of samples
                 ]

只要我注释#pragma 行,'test' 函数就可以顺利运行 - 否则它会以错误号 139 结束。

A = np.random.rand(1000, 1000) + 2.0
out = np.empty((1000, 1000))
test(A, out, 1000)

当我将内部循环更改为仅打印 ind1 和 ind2 时,它会顺利并行运行。当我只访问 ind1 位置并将 ind2 单独放置(甚至并行)时,它也可以工作!我在哪里搞砸了内存访问?我该如何解决这个问题?

谢谢!

更新:嗯,我猜这会遇到 GIL,但我还不确定......

更新:好的,我现在很确定,这是邪恶的 GIL 在这里杀死我,所以我更改了示例:

我现在有 gil.c:

#include <Python.h>
#define _USE_MATH_DEFINES
#include <math.h>

void scale(const double *A, double *out, int n) {
    int i, j, ind1, ind2;
    double power, denom;
    power = 10.0 / M_PI;
    denom = sqrt(M_PI);
    Py_BEGIN_ALLOW_THREADS
    #pragma omp parallel for
    for (i = 0; i < n; i++) {
        for (j = i; j < n; j++) {
            ind1 = i*n + j;
            ind2 = j*n + i;
            out[ind1] = pow(A[ind1], power) / denom;
            out[ind2] = out[ind1];
        }
    }
    Py_END_ALLOW_THREADS
}

使用gcc -shared -fopenmp -o gil.so -lm gil.c -I /usr/include/python2.7 -L /usr/lib/python2.7/ -lpython2.7和对应的Python文件编译:

import ctypes
import numpy as np
from numpy.ctypeslib import ndpointer
import pylab as pl

path = '../src/gil.so'
c_gil = ctypes.cdll.LoadLibrary(path)

test = c_gil.scale
test.restype = None
test.argtypes = [ndpointer(ctypes.c_double,
                           ndim=2,
                           flags='C_CONTIGUOUS'),
                 ndpointer(ctypes.c_double,
                           ndim=2,
                           flags='C_CONTIGUOUS'),
                 ctypes.c_int
                 ]

n = 100
A = np.random.rand(n, n) + 2.0
out = np.empty((n,n))

test(A, out, n)

这给了我

Fatal Python error: PyEval_SaveThread: NULL tstate

Process finished with exit code 134

现在不知何故,它似​​乎无法保存当前线程 - 但 API 文档在这里没有详细介绍,我希望在编写 C 函数时可以忽略 Python,但这似乎很混乱: (有什么想法吗?我觉得这很有帮助:GIL

【问题讨论】:

    标签: python c openmp gil


    【解决方案1】:

    您的问题比您想象的要简单得多,并且不涉及 GIL。当您通过ind2 访问out[] 时,您正在对out[] 进行越界访问,因为j 很容易变得大于n。原因很简单,您没有对并行区域应用任何数据共享子句,并且除 i 之外的所有变量都保持共享(根据 OpenMP 中的默认设置),因此会受到数据竞争的影响 - 在这种情况下,由不同的线程。太大的jind1 来说问题不大,但对ind2 则不然,因为太大的值乘以n 会变得太大。

    只需将jind1ind2 设为私有即可:

    #pragma omp parallel for private(j,ind1,ind2)
    for (i = 0; i < n; i++) {
        for (j = i; j < n; j++) {
            ind1 = i*n + j;
            ind2 = j*n + i;
            out[ind1] = pow(A[ind1], power) / denom;
            out[ind2] = out[ind1];
        }
    }
    

    更好的是,在使用它们的范围内声明它们。这会自动将它们设为私有:

    #pragma omp parallel for
    for (i = 0; i < n; i++) {
        int j;
        for (j = i; j < n; j++) {
            int ind1 = i*n + j;
            int ind2 = j*n + i;
            out[ind1] = pow(A[ind1], power) / denom;
            out[ind2] = out[ind1];
        }
    }
    

    【讨论】:

    • 哈利路亚,欢迎来到并行计算的世界 x) 非常感谢!现在按计划工作!
    猜你喜欢
    • 2011-04-04
    • 1970-01-01
    • 2017-03-13
    • 2010-10-11
    • 1970-01-01
    • 2018-04-08
    • 1970-01-01
    • 2011-04-28
    • 1970-01-01
    相关资源
    最近更新 更多