带有 OpenMP 关键指令的 Rcpp 明显比编译的 C++ 代码慢
Rcpp with OpenMP critical directive significatnly slower than compiled C++ code
正如标题所说,与 R 包中使用的已编译 & 运行 C++ 代码相比,使用 Rcpp 中的 #pragma omp critical
指令会显着降低执行速度,因为没有使用所有 CPU 力量.
考虑一个简单的 C++ 程序(使用 cmake):
test.h 为:
#ifndef RCPP_TEST_TEST_H
#define RCPP_TEST_TEST_H
#include <limits>
#include <cstdio>
#include <chrono>
#include <iostream>
#include <omp.h>
namespace rcpptest {
class Test {
public:
static unsigned int test();
};
}
#endif //RCPP_TEST_TEST_H
test.h 在 test.cpp 中的实施:
#include "test.h"
namespace rcpptest {
unsigned int Test::test() {
omp_set_num_threads(8);
unsigned int x = 0;
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
#pragma omp parallel for
for (unsigned int i = 0; i < 100000000; ++i) {
#pragma omp critical
++x;
}
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
std::cout << "finished (ms): " << std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() <<std::endl;
return x;
}
}
主要为:
#include "src/test.h"
int main() {
unsigned int x = rcpptest::Test::test();
return 0;
}
如果我在 IDE (CLion) 中构建并 运行 这个程序,一切正常。
然后我使用 Rcpp 创建了一个 R 包:
library(Rcpp)
Rcpp.package.skeleton('rcppTestLib')
并为包 + "Rcpp" 文件使用相同的 C++ 源代码来导出我的测试函数以便从 R (rcppTestLib.cpp):
#include <Rcpp.h>
#include "test.h"
// [[Rcpp::export]]
void rcppTest() {
rcpptest::Test::test();
}
如果我然后 运行 使用包
从 R 进行测试
library(rcppTestLib)
rcppTest()
执行速度慢得多。
我运行 很少测试同时使用编译的 c++ 和 Rcpp 包,结果是:
program | execution time
-----------------------------
compiled c++ | ~7 200ms
Rcpp package | ~551 000 ms
不同之处在于,使用 Rcpp 包时,会生成 8 个线程,但每个线程仅使用约 1% 的 CPU,而使用编译的 C++ 时,8 个线程加在一起使用了所有 CPU 力量.
我尝试将 #pragma omp critical
切换为 #pragma omp atomic
结果:
program | execution time
-----------------------------
compiled c++ | ~2 900ms
Rcpp package | ~3 300 ms
使用 #pragma omp atomic
Rcpp 包产生 8 个线程并使用所有 CPU 的能力。然而,执行时间仍然存在差异,但并不那么显着。
所以我的问题是:为什么使用 #pragma omp critical
R / Rcpp 包不使用所有 CPU 功能,而使用 #pragma omp atomic
它甚至可以构建相同的代码 [= CLion 中的 69=] 在两种情况下都使用所有 CPU 电源?
我在这里错过了什么?
这里有两个可能的选项:
- 在包形式中,
OpenMP
标志选项尚未在 src/Makevars
(unix) 或 src/Makevars.win
(windows) 中设置
- 缺少
num_threads(x)
,因为 critical
推出
对于一个,放在 src/Makevars
或 src/Makevars.win
文件中:
PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) $(SHLIB_OPENMP_CFLAGS)
PKG_CFLAGS = $(SHLIB_OPENMP_CFLAGS)
PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS)
详情见:https://cran.r-project.org/doc/manuals/r-release/R-exts.html#OpenMP-support
关于遗漏 num_threads(x)
...我已经能够稍微加快问题...
变化中:
#pragma omp parallel for
到
#pragma omp parallel for num_threads(4)
产量:
之前
finished (ms): 30822
[1] 1e+08
对比
之后
finished (ms): 17979
[1] 1e+08
或大约 1.7 的加速。我的想法是 cmake
正在设置全局线程选项。
omp_set_num_threads(x)
或
set OMP_NUM_THREADS=x
https://gcc.gnu.org/onlinedocs/libgomp/omp_005fset_005fnum_005fthreads.html
@coatless 再次完全正确。我们创建的 default src/Makevars*
没有 OpenMP。你在当前足够的编译器上看到这个:
ccache g++ -I/usr/share/R/include -DNDEBUG -I"/usr/local/lib/R/site-library/Rcpp/include" -fpic -g -O3 -Wall -pipe -march=native -c test.cpp -o test.o
test.cpp:10:0: warning: ignoring #pragma omp parallel [-Wunknown-pragmas]
#pragma omp parallel for
test.cpp:13:0: warning: ignoring #pragma omp critical [-Wunknown-pragmas]
#pragma omp critical
根据需要添加 src/Makevars
后,一切都很好。 htop
显示我选择挂钩的 CPU 数量。
但是你的例子仍然很糟糕,因为循环做得太少了。开销成为主导。我这里有多个内核,但没有理由 运行 更快 OMP_NUM_THREADS=2
应该 运行 比 OMP_NUM_THREADS=3
或 OMP_NUM_THREADS=4
更快——除了事实
我们这里似乎只有开销。
正如标题所说,与 R 包中使用的已编译 & 运行 C++ 代码相比,使用 Rcpp 中的 #pragma omp critical
指令会显着降低执行速度,因为没有使用所有 CPU 力量.
考虑一个简单的 C++ 程序(使用 cmake):
test.h 为:
#ifndef RCPP_TEST_TEST_H
#define RCPP_TEST_TEST_H
#include <limits>
#include <cstdio>
#include <chrono>
#include <iostream>
#include <omp.h>
namespace rcpptest {
class Test {
public:
static unsigned int test();
};
}
#endif //RCPP_TEST_TEST_H
test.h 在 test.cpp 中的实施:
#include "test.h"
namespace rcpptest {
unsigned int Test::test() {
omp_set_num_threads(8);
unsigned int x = 0;
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
#pragma omp parallel for
for (unsigned int i = 0; i < 100000000; ++i) {
#pragma omp critical
++x;
}
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
std::cout << "finished (ms): " << std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count() <<std::endl;
return x;
}
}
主要为:
#include "src/test.h"
int main() {
unsigned int x = rcpptest::Test::test();
return 0;
}
如果我在 IDE (CLion) 中构建并 运行 这个程序,一切正常。
然后我使用 Rcpp 创建了一个 R 包:
library(Rcpp)
Rcpp.package.skeleton('rcppTestLib')
并为包 + "Rcpp" 文件使用相同的 C++ 源代码来导出我的测试函数以便从 R (rcppTestLib.cpp):
#include <Rcpp.h>
#include "test.h"
// [[Rcpp::export]]
void rcppTest() {
rcpptest::Test::test();
}
如果我然后 运行 使用包
从 R 进行测试library(rcppTestLib)
rcppTest()
执行速度慢得多。
我运行 很少测试同时使用编译的 c++ 和 Rcpp 包,结果是:
program | execution time
-----------------------------
compiled c++ | ~7 200ms
Rcpp package | ~551 000 ms
不同之处在于,使用 Rcpp 包时,会生成 8 个线程,但每个线程仅使用约 1% 的 CPU,而使用编译的 C++ 时,8 个线程加在一起使用了所有 CPU 力量.
我尝试将 #pragma omp critical
切换为 #pragma omp atomic
结果:
program | execution time
-----------------------------
compiled c++ | ~2 900ms
Rcpp package | ~3 300 ms
使用 #pragma omp atomic
Rcpp 包产生 8 个线程并使用所有 CPU 的能力。然而,执行时间仍然存在差异,但并不那么显着。
所以我的问题是:为什么使用 #pragma omp critical
R / Rcpp 包不使用所有 CPU 功能,而使用 #pragma omp atomic
它甚至可以构建相同的代码 [= CLion 中的 69=] 在两种情况下都使用所有 CPU 电源?
我在这里错过了什么?
这里有两个可能的选项:
- 在包形式中,
OpenMP
标志选项尚未在src/Makevars
(unix) 或src/Makevars.win
(windows) 中设置
- 缺少
num_threads(x)
,因为critical
推出
对于一个,放在 src/Makevars
或 src/Makevars.win
文件中:
PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) $(SHLIB_OPENMP_CFLAGS)
PKG_CFLAGS = $(SHLIB_OPENMP_CFLAGS)
PKG_CXXFLAGS = $(SHLIB_OPENMP_CXXFLAGS)
详情见:https://cran.r-project.org/doc/manuals/r-release/R-exts.html#OpenMP-support
关于遗漏 num_threads(x)
...我已经能够稍微加快问题...
变化中:
#pragma omp parallel for
到
#pragma omp parallel for num_threads(4)
产量:
之前
finished (ms): 30822
[1] 1e+08
对比
之后
finished (ms): 17979
[1] 1e+08
或大约 1.7 的加速。我的想法是 cmake
正在设置全局线程选项。
omp_set_num_threads(x)
或
set OMP_NUM_THREADS=x
https://gcc.gnu.org/onlinedocs/libgomp/omp_005fset_005fnum_005fthreads.html
@coatless 再次完全正确。我们创建的 default src/Makevars*
没有 OpenMP。你在当前足够的编译器上看到这个:
ccache g++ -I/usr/share/R/include -DNDEBUG -I"/usr/local/lib/R/site-library/Rcpp/include" -fpic -g -O3 -Wall -pipe -march=native -c test.cpp -o test.o
test.cpp:10:0: warning: ignoring #pragma omp parallel [-Wunknown-pragmas]
#pragma omp parallel for
test.cpp:13:0: warning: ignoring #pragma omp critical [-Wunknown-pragmas]
#pragma omp critical
根据需要添加 src/Makevars
后,一切都很好。 htop
显示我选择挂钩的 CPU 数量。
但是你的例子仍然很糟糕,因为循环做得太少了。开销成为主导。我这里有多个内核,但没有理由 运行 更快 OMP_NUM_THREADS=2
应该 运行 比 OMP_NUM_THREADS=3
或 OMP_NUM_THREADS=4
更快——除了事实
我们这里似乎只有开销。