【问题标题】:Simplest way to to write parallel C/C++ modules to be used in Python编写用于 Python 的并行 C/C++ 模块的最简单方法
【发布时间】:2018-06-19 23:02:51
【问题描述】:

短背景(没必要)

我一直在努力为Scikit-Learn 编写一个替代方案(需要较少资源?)mean shift C++ 模块。

在 C++ 方面,我一直在使用 nanoflann 库来构建和搜索 KD-Tree。

基本上我有两个 numpy 数组,我通过 Cython 将它们传递给我的 C++ MeanShift 函数,然后返回找到的集群中心列表。

事实证明它要快一点,大约 7 倍(我仍在积极研究它)。

我的问题:

我想并行化我的 C++ 代码中最昂贵的部分,比如用于收敛的 for 循环,但是,由于这个 C++ 模块将被导入 python,我希望以最安全的方式这样做& 简单的方法。

我考虑过使用 OpenMP,您有什么建议吗?

谢谢! 祝你有美好的一天。

编辑/编码 sn-p

谢谢@bivouac0,我现在可以编译整个了。

现在我正在与逻辑/技术方面作斗争。 让我给你写一个我想要并行化的代码。

我有一个std::vector<std::pair<size_t, double> > > matches 向量和一个相当大的double samples[N] 数组。 我想使用存储在matches 向量中的对的第一个元素来计算对更大samples 数组的访问索引(参见下面的代码): 这是执行此操作的方法:

typedef std::vector<std::pair<size_t, double> > searchResultPair; 
double* calcMean(size_t nMatches, searchResultPair matches,
    double* samples) {
/*
*/
double* returnArray = new double[3];
returnArray[0] = 0;
returnArray[1] = 0;
returnArray[2] = 0;
double x = y = z = 0;
for (size_t i = 0; i < nMatches; i = i + 1) {
    x = x + samples[3 * (matches[i].first)];
    y = y + samples[3 * (matches[i].first) + 1];
    z = z + samples[3 * (matches[i].first) + 2];
     }
returnArray[0] = x/nMatches;
returnArray[1] = y/nMatches;
returnArray[2] = z/nMatches;

return(returnArray);
}

有没有办法同时访问matches[i].first 变量?

我已尝试使用 #pragma omp parallel for reduction(+:x,y,z) num_threads(n_threads),但它会降低性能(1 线程 > 2 线程 > 4 线程 > 8 线程等等...)。

我的问题有任何意义吗?我在哪里弄错了吗? 管理并行 n_threads 团队以计算部分和 x,y,z 可能只是开销,因为 vector 中的元素是连续存储的...

我可以将上面的 for 循环分成 3 个部分并尝试并行化每个部分。 这是个好主意吗? 那边的计算嵌套在 while 中,嵌套在另一个 for 循环中,这是整个模块中最重要的方法。

【问题讨论】:

  • OMP 非常简单。我倾向于在大多数情况下将它用于并行 for 循环的包装器中,这为它们提供了类似于 TBB 的功能样式接口,例如:pfor(start_index, end_index, chunk_size, func);pfor_each(c.begin(), c.end(), chunk_size, func); 还可以轻松地在不同的并行库之间进行交换以比较它们的性能特征。
  • 我在 c++ 代码中使用了 OMP,它可以毫无问题地导入到 python 中。所有 OMP 指令都隐藏在 c++ 函数调用之后,因此您将它们导入 python 的事实有点无关紧要。您只需要确保在构建模块时应用正确的 c++ 编译器指令来启用 OMP。
  • @bivouac0 我尝试过使用 OMP,但遇到了 ImportError python-side (undefined symbol: GOMP_parallel)。我添加了 -fopenmp 标志,但我可能会误认为其他编译选项(甚至是编译器本身!)。有什么建议吗?
  • 如果没有实际数据,其他人很难帮助找到答案,但是我首先尝试在单个循环中使用omp parallel for,看看它是否会加速累积。然后我会查看omp sections 以并行化不同的 for 循环。您应该能够获得不错的加速,但有时需要进行一些试验才能获得正确的组合。尝试编写尽可能紧凑的代码。有时这有助于 OMP 编译一个快速的解决方案。如果您需要更多帮助,您可能需要发布项目的一部分,其中包含准备好运行的代码和数据。
  • 嘿@bivouac0,谢谢你的回答。我也在 CodeReview 上问过,这里是full code

标签: python c++ multithreading scikit-learn


【解决方案1】:

关于使用 OpenMP 编译 c++ 代码的上述问题,您可能只需要包含 gomp 库。这是一个适合我的简单 setup.py 脚本...

from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize, build_ext
# run as... python setup.py build_ext

ext = Extension("entity_lookup",    # name of extension
    ["src/entity_lookup.pyx", "src/EntityLookupImpl.cpp", "src/IndexDictImpl.cpp"],
    language="c++",     # this causes Pyrex/Cython to create C++ source
    #include_dirs=[...],                       
    libraries=['gomp'], # or include explicity with extra_link_args below                         
    cmdclass = {'build_ext': build_ext},
    #extra_link_args=['/usr/lib/x86_64-linux-gnu/libgomp.so.1'], # see above
    extra_compile_args=['-fopenmp', '-std=c++11']
)

setup(
    name = 'EntityLookup',
    version = 0.4,
    description = 'Package to match words and phrases to Entity labels',
    ext_modules = cythonize(ext)
)

注意包含 gomp(又名 libgomp.so.1)。这是定义 GOMP_parallel 的地方。

要编译... python setup.py build_ext

我总是在原地使用此代码(未安装在任何地方),为此您需要设置一个指向已编译 entity_lookup.so 的链接,该链接出现在脚本创建的“build”目录的深处。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-09
    • 2014-12-24
    • 2011-05-16
    • 2023-03-28
    • 1970-01-01
    相关资源
    最近更新 更多