Rcpp 不编译带有 pari.h 标头的 cpp 源代码

Ser*_*kel 1 gcc r pari rcpp pari-gp

我编写了一个 cpp 源代码,其中包含 pari.h 头文件:

#include<string>
#include<vector>
#include<algorithm>
#include<cmath>
#include<stdlib.h>
#include<time.h>
#include<iterator> // for ostream_iterator
#include<strings.h>
#include<string.h>
#include<sstream>
#include <pari/pari.h> // for PARI/GP library
#include<Rcpp.h> // for sourceCpp to work, this line must be uncommented

// Enable C++11 via this plugin (Rcpp 0.10.3 or later)
// [[Rcpp::plugins(cpp11)]]

using namespace std;
using namespace Rcpp; // for sourceCpp to work, this line must be uncommented

// [[Rcpp::export]]
int main() {

    long maxp = 1000000; // max value
    pari_init(500000,2); // initiate pari
    size_t rsize = 500000; // set stack size variables
    size_t vsize = 100000000;

    void paristack_setsize(size_t rsize, size_t vsize); // declare stack function
    paristack_setsize(rsize, vsize); // set stack size
    gp_allocatemem(stoi(100000000)); // allocate memory
    GEN p1; // declare PARI variable

    p1 = cgetg(maxp, t_VEC); // make the PARI variable a vector
    long j; // declare the variable for the number to be checked. one above the vector iterator
    for (long i = 0; i <= maxp; ++i) { // iterate over PARI vector
        j = i + 1; // decrement index for number
        gel(p1, i) = sumdiv(stoi(j)); // calculate the sum of divisors and update the vector
    }

    vector<long> p2(maxp); // empty vector of native type
    GEN x; // declare a PARI variable to subset PARI vector
    for (long i = 0; i < maxp; i++) { // for2, across vector indices
        x = gel(p1, i); // subset one item of vector
        p2[i] = gtolong(x); // convert PARI to native long integer and update long vector item
    } // close for2

    for (long z = 0; z < maxp; z++) { // for3, to iterate for stdout
        cout << p2[z] << '\n'; // return the result. the vector items are printed separately
    } // close for3

} // close function
Run Code Online (Sandbox Code Playgroud)

(请注意,可能存在不必要的标头,我通常跨源复制所有标头,但这不是问题)。类似的没有 pari.h 头文件的源文件可以使用 Rcpp 很好地编译,并包含必要的部分(例如头文件、命名空间、导出行等)。

当 Rcpp 相关引用被注释时,源代码可以很好地编译,并且在使用以下标志直接使用 g++ 编译时可以正常工作:

g++ -lpari -fpermissive -Wall -Wextra -lm -fno-strict-aliasing -fomit-frame-pointer -o sumdivisors.o sumdivisors.cpp
Run Code Online (Sandbox Code Playgroud)

我还将这些标志导入到 R 中:

Sys.setenv("PKG_CXXFLAGS"="-lpari -fpermissive -Wall -Wextra -lm -fno-strict-aliasing -fomit-frame-pointer")
Run Code Online (Sandbox Code Playgroud)

我还在/usr/local/lib64/R/library/Rcpp/include/下的 pari 目录中创建了一个符号链接/usr/include

然而,sourceCpp 命令的输出如下:

> sourceCpp("sumdivisors.cpp")
In file included from /usr/local/lib64/R/library/Rcpp/include/Rcpp/r/headers.h:48:0,
                 from /usr/local/lib64/R/library/Rcpp/include/RcppCommon.h:29,
                 from /usr/local/lib64/R/library/Rcpp/include/Rcpp.h:27,
                 from sumdivisors.cpp:15:
/usr/local/lib64/R/library/Rcpp/include/Rcpp/platform/compiler.h:47:0: warning: "GCC_VERSION" redefined
     #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)                                                                                                      

In file included from /usr/local/lib64/R/library/Rcpp/include/pari/pari.h:16:0,
                 from sumdivisors.cpp:14:
/usr/local/lib64/R/library/Rcpp/include/pari/paricfg.h:19:0: note: this is the location of the previous definition
 #define GCC_VERSION "gcc version 6.2.1 20160830 (GCC)"                                                                                                                                       

Error in dyn.load("/tmp/Rtmpc9edZe/sourceCpp-x86_64-pc-linux-gnu-0.12.8/sourcecpp_188e46b44088/sourceCpp_2.so") :                                                                             
  unable to load shared object '/tmp/Rtmpc9edZe/sourceCpp-x86_64-pc-linux-gnu-0.12.8/sourcecpp_188e46b44088/sourceCpp_2.so':                                                                  
  /tmp/Rtmpc9edZe/sourceCpp-x86_64-pc-linux-gnu-0.12.8/sourcecpp_188e46b44088/sourceCpp_2.so: undefined symbol: pari_mainstack  
Run Code Online (Sandbox Code Playgroud)

我复制了包含或不包含 C++11 启用行的步骤,没有任何变化。我还更改了 gcc 标志,但没有结果。看来gcc版本定义和pari_mainstack的定义有问题。

我相信问题不在于源代码是如何编写的。下面两个例子,其中,上面的cpp代码被转换为返回向量的代码,并且函数不是main。还提供了一个类似且简单的代码,可以使用 Rcpp 很好地编译:

#include<stdio.h>
#include<numeric> // for "accumulate"
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<cmath>
#include<stdlib.h>
#include<time.h>
#include<iterator> // for ostream_iterator
#include<strings.h>
#include<string.h>
#include<sstream>
#include <pari/pari.h> // for PARI/GP library
#include<Rcpp.h> // for sourceCpp to work, this line must be uncommented

// Enable C++11 via this plugin (Rcpp 0.10.3 or later)
// [[Rcpp::plugins(cpp11)]]

using namespace std;
using namespace Rcpp; // for sourceCpp to work, this line must be uncommented

// [[Rcpp::export]]
vector<long> sumdivisors() {

    long maxp = 1000000; // max value
    pari_init(500000,2); // initiate pari
    size_t rsize = 500000; // set stack size variables
    size_t vsize = 100000000;

    void paristack_setsize(size_t rsize, size_t vsize); // declare stack function
    paristack_setsize(rsize, vsize); // set stack size
    gp_allocatemem(stoi(100000000)); // allocate memory
    GEN p1; // declare PARI variable

    p1 = cgetg(maxp, t_VEC); // make the PARI variable a vector
    long j; // declare the variable for the number to be checked. one above the vector iterator
    for (long i = 0; i <= maxp; ++i) { // iterate over PARI vector
        j = i + 1; // decrement index for number
        gel(p1, i) = sumdiv(stoi(j)); // calculate the sum of divisors and update the vector
    }

    vector<long> p2(maxp); // empty vector of native type
    GEN x; // declare a PARI variable to subset PARI vector
    for (long i = 0; i < maxp; i++) { // for2, across vector indices
        x = gel(p1, i); // subset one item of vector
        p2[i] = gtolong(x); // convert PARI to native long integer and update long vector item
    } // close for2

    return(p2);
    /*
    for (long z = 0; z < maxp; z++) { // for3, to iterate for stdout
        cout << p2[z] << '\n'; // return the result. the vector items are printed separately
    } // close for3
    */

} // close function
Run Code Online (Sandbox Code Playgroud)

#include<stdio.h>
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<cmath>
#include<math.h>
#include<time.h>
#include<Rcpp.h>

using namespace std;
using namespace Rcpp;

//#include "std_lib_facilities.h"

// [[Rcpp::export]]
int pe001Cpp(int x) { // define a function pe001 with one in$teger input
    int sum35 = 0; // define a scalar for the sum. start value is 0
    for (int i=1; i<x; ++i) { // for 1 loop for counting up to x
        if (i % 3 == 0 || i % 5 == 0) { // if 1, divisible by 3 or 5
            sum35 += i; // update sum
        } // close if 1
    } // close for 1
    return sum35; // return the final value
} // close function

// [[Rcpp::export]]
int pe001Cppb(int x) { // efficient method
    int sumdivisible(int x, int y); // declare the below function in this scope
    return sumdivisible(x, 3) + sumdivisible(x, 5) - sumdivisible(x, 15); // return the total sum
} // close function pe001Cppb

int sumdivisible(int x, int y) { // sum of terms divisibile by y
    int ny = floor ((x-1) / y); // number of terms less than x and divisible by y
    return ny * (ny + 1) / 2 * y; // return the sum
} // close function sumdivisible
Run Code Online (Sandbox Code Playgroud)

执行直接编译的二进制文件后过滤后的 strace 输出如下:

open("/usr/lib/libpari-gmp-tls.so.5", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libgmp.so.10", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libnss_compat.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libnsl.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libnss_nis.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3
Run Code Online (Sandbox Code Playgroud)

正如我们从这里看到的https://github.com/rstats-db/RPostgres/issues/80,问题可能是链接库版本错误,可以通过符号链接解决。所以我必须知道 Rcpp 尝试链接哪些库文件。

更新:

Scanelf 输出显示有问题的符号位于 /usr/lib/libpari-gmp-tls.so.2.9.1 中。

[s@SS ~]$ scanelf -l -s pari_mainstack | grep pari_mainstack
ET_DYN pari_mainstack /usr/lib/libpari-gmp-tls.so.2.9.1 
Run Code Online (Sandbox Code Playgroud)

g++ 编译文件的 strace 输出显示可执行文件链接到 /usr/lib/libpari-gmp-tls.so.5,它本身是 2.9.1 版本的符号链接:

[s@SS library]$ strace ./sumdivisors3.o |& grep so | grep -v "No such file"
execve("./sumdivisors3.o", ["./sumdivisors3.o"], [/* 79 vars */]) = 0
open("/usr/lib/libpari-gmp-tls.so.5", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libstdc++.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libm.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libgcc_s.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libgmp.so.10", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
open("/usr/lib/libnss_compat.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libnsl.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libnss_nis.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3
Run Code Online (Sandbox Code Playgroud)

由sourceCpp命令创建的sourceCpp_4.so文件的ldd输出如下:

[s@SS library]$ ldd /tmp/Rtmpau9YqY/sourceCpp-x86_64-pc-linux-gnu-0.12.8/sourcecpp_3a105ad2bdba/sourceCpp_4.so
        linux-vdso.so.1 (0x00007ffc28f9d000)
        libR.so => not found
        libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f5077111000)
        libm.so.6 => /usr/lib/libm.so.6 (0x00007f5076e0d000)
        libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007f5076bf6000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007f5076858000)
        /usr/lib64/ld-linux-x86-64.so.2 (0x0000564489276000)
Run Code Online (Sandbox Code Playgroud)

我使用 ldd 跟踪了所有这些文件,并且没有指向 /usr/lib/libpari-gmp-tls.so.2.9.1 或 /usr/lib/libpari-gmp-tls.so.5 库的链接。所以问题是,为什么 sourceCpp 不能链接到那些包含必要标头的文件(而 g++ 可以)?

更新:

sourceCpp 的详细输出显示以下命令:

g++  -I/usr/local/lib64/R/include -DNDEBUG  -I/usr/local/include  -I"/usr/local/lib64/R/library/Rcpp/include" -I"/home/s/codes/cpp/projecteuler/library"  -lpari -fpic  -g -O2 -c sumdivisors2.cpp -o sumdivisors2.o
g++ -shared -L/usr/local/lib64/R/lib -L/usr/local/lib64 -o sourceCpp_5.so sumdivisors2.o -L/usr/local/lib64/R/lib -lR
Run Code Online (Sandbox Code Playgroud)

我用(事实上 -lpari 就足够了:

Sys.setenv("PKG_CXXFLAGS"="-lpari")
Run Code Online (Sandbox Code Playgroud)

根据 gp2c 输出,-lpari 标志也应该包含在链接阶段,但这里链接命令没有它。这可能是问题的根源吗?或者在此之前,为什么sourceCpp_5.so文件没有链接到必要的pari库?

以及结局:

链接的依赖库也应该通过以下方式显式声明:

Sys.setenv("PKG_LIBS"="-lm -lpari -lc")

库标志由 gp2c 输出给出。顺便说一下,为了解决 gcc 版本问题,我没有创建到原始 pari 头目录的符号链接,而是在 R 库路径内创建了一个副本并注释掉了该行:

//#define GCC_VERSION "gcc 版本 6.2.1 20160830 (GCC)"

现在编译成功了,R可以在R中享受PARI/GP的数论计算速度,感谢Rcpp!

coa*_*ess 5

没有 pari.h 标头的类似源文件可以使用 Rcpp 很好地编译,并包含必要的部分(例如标头、命名空间、导出行等)

使用时sourceCpp(),一般用例不涉及使用系统安装的库,例如pari 使用先前已建立适当链接标志的 R 包。因此,单独的包将处理pariR 会话的表面处理,并随后为 Rcpp 插件管理器注册一个插件,以便// [[Rcpp::depends(pkgname)]]可以将其包含在头文件之后以设置适当的链接语句。

话虽如此,使用 Rcpp 的第一步是建立pari一个RcppPari包,最好以RcppGSL.

为此,您可能需要:

  • configure.ac:验证用户系统上是否存在该库以及系统配置是否合适(例如编译器是否有效等)
  • src/Makevars.in:设置适当的预处理器标志来处理包中嵌入的代码应如何编译链接。(请参阅下面的链接。)
  • r/inline.R:建立设置脚手架所需的插件以使用sourceCpp()and cppFunction()(上面讨论为“理想”解决方案)。

我使用 ldd 跟踪了所有这些文件,并且没有指向 /usr/lib/libpari-gmp-tls.so.2.9.1 或 /usr/lib/libpari-gmp-tls.so.5 库的链接。所以问题是,为什么 sourceCpp 不能链接到那些包含必要标头的文件(而 g++ 可以)?

正如您所发现的,在链接阶段没有正确调用in-lm -lpari -lc使用。相反,需要在选项中设置该标志。@jtilly 在评论中强调了这一点。PKG_CXXFLAGSPKG_LIB

在开始编写包之前,您可能需要了解 R 中与Makevars文件相关的预处理器标志背后的不同含义: