C++ 为什么即使在使用-cudart static编译时,库用户仍然需要链接到cuda运行时

C++ 为什么即使在使用-cudart static编译时,库用户仍然需要链接到cuda运行时,c++,cuda,static-linking,dynamic-linking,C++,Cuda,Static Linking,Dynamic Linking,我有一些简单的cuda代码,我正在使用nvcc编译到一个静态库中,还有一些用户代码,我正在使用g++编译并链接到以前编译的静态库中。尝试链接时,即使我在nvcccompile命令行中使用-cudart static选项,也会因cudamaloc之类的内容而收到链接器错误 这是我的密码: //kern.hpp #include <cstddef> class Kern { private: float* d_data; size_t size;

我有一些简单的cuda代码,我正在使用
nvcc
编译到一个静态库中,还有一些用户代码,我正在使用
g++
编译并链接到以前编译的静态库中。尝试链接时,即使我在
nvcc
compile命令行中使用
-cudart static
选项,也会因
cudamaloc
之类的内容而收到链接器错误

这是我的密码:

//kern.hpp
#include <cstddef>

class Kern
{
    private:
        float* d_data;
        size_t size;

    public:
        Kern(size_t s);
        ~Kern();
        void set_data(float *d); 
};
然后用
g++
进行main操作,如下所示:

nvcc -I ./ -lib --compiler-options '-fPIC' -o libkern.a kern.cu -cudart static
g++ -o main main.cpp -I ./ -L. -L/opt/cuda/lib64 -lkern
$ nvcc  -Xcompiler '-fPIC' -I.  -c kern.cu
$ ld -o kern.ro -r kern.o -L/usr/local/cuda/lib64 -lcudart_static -lculibos
$ ar rs libkern.a kern.ro
ar: creating libkern.a
$ g++ -o main main.cpp  -I ./ -L.  -lkern -lpthread -lrt -ldl
$ cuda-memcheck ./main
========= CUDA-MEMCHECK
starting
Starting kernel with grid size 256 and block size 1
done
========= ERROR SUMMARY: 0 errors
$
这会产生错误:

/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `Kern::Kern(unsigned long)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x4d): undefined reference to `cudaMalloc'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `Kern::~Kern()':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x6b): undefined reference to `cudaFree'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `Kern::set_data(float*)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x152): undefined reference to `__cudaPushCallConfiguration'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x175): undefined reference to `cudaGetLastError'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x1a1): undefined reference to `cudaGetErrorString'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x1c6): undefined reference to `cudaDeviceSynchronize'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x1ee): undefined reference to `cudaMemcpy'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x1f3): undefined reference to `cudaDeviceSynchronize'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `__cudaUnregisterBinaryUtil()':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x24e): undefined reference to `__cudaUnregisterFatBinary'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `__nv_init_managed_rt_with_module(void**)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x269): undefined reference to `__cudaInitModule'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `__device_stub__Z4kernPfm(float*, unsigned long)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x305): undefined reference to `__cudaPopCallConfiguration'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `__nv_cudaEntityRegisterCallback(void**)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x430): undefined reference to `__cudaRegisterFunction'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `__sti____cudaRegisterAll()':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x44b): undefined reference to `__cudaRegisterFatBinary'
/usr/bin/ld: tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x47c): undefined reference to `__cudaRegisterFatBinaryEnd'
/usr/bin/ld: ./libkern.a(tmpxft_00001d30_00000000-8_kern.o): in function `cudaError cudaLaunchKernel<char>(char const*, dim3, dim3, void**, unsigned long, CUstream_st*)':
tmpxft_00001d30_00000000-5_kern.cudafe1.cpp:(.text+0x4d9): undefined reference to `cudaLaunchKernel'
collect2: error: ld returned 1 exit status
一切正常。 我的问题是,既然我在
nvcc
编译行中有一个
-cudart static
,那么
libkern.a
是否应该已经解析了cuda运行时的符号?为什么在
g++
行中仍然需要
-lcudart

另外,如果我将
libkern.a
更改为共享对象,则在
g++
行中不链接到cuda运行时也会起作用。即以下工作:

nvcc -I ./ -shared --compiler-options '-fPIC' -o libkern.so kern.cu -cudart static
g++ -o main main.cpp -I ./ -L. -L/opt/cuda/lib64 -lkern
为什么静态库版本失败,而共享对象版本可以工作

请注意,在
nvcc
行中将
-cudart static
替换为
-lcudart\u static
后,我尝试了上述场景,并且在进行替换后,行为没有任何变化。这是可以预期的,因为这两个选项基本上做了相同的事情,对吗

我在linux上

nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2019 NVIDIA Corporation
Built on Wed_Oct_23_19:24:38_PDT_2019
Cuda compilation tools, release 10.2, V10.2.89
非常感谢您提供的任何帮助和/或澄清。

如果您学习,很明显,
-lib
选项会创建一个静态库(并指定不链接),而
-shared
选项会创建一个共享库并指定链接。例如,摘录:

4.2.2.1--链接(-link) 指定默认行为:编译并链接所有输入文件

4.2.2.2--库(-lib) 如有必要,将所有输入文件编译为对象文件,并将结果添加到指定的库输出文件中

4.2.3.11--共享(-shared) 在链接过程中生成共享库。 使用选项——当需要其他链接器选项以实现更多控制时,使用链接器选项

我相信这或多或少与典型的gcc/g++用法一致。如果您在“g++创建静态库”上进行谷歌搜索,您将得到任意数量的数据,表明您基本上应该这样做:

g++ -c my_source_file.cpp ...
ar ...
换句话说,指定了源到对象的编译,但没有指定链接。举一个例子,
cudamaloc
是CUDA运行库的一部分,与它的连接将在链接阶段完成

nvcc
是一种相当复杂的隐蔽动物,但我们应该记住,对于某些功能,它主要使用已安装的主机工具链。这包括主机代码的编译,还包括最终链接阶段

再加上这一点,我相信你想在这里做的是“部分”链接或增量链接。在最终链接阶段之前执行一些最终链接阶段

GNU链接器(同样,默认情况下,
nvcc
将在linux上使用什么),因此,如果我们不考虑编译可重定位设备代码的问题,应该可以按如下方式执行您想要的操作:

nvcc -I ./ -lib --compiler-options '-fPIC' -o libkern.a kern.cu -cudart static
g++ -o main main.cpp -I ./ -L. -L/opt/cuda/lib64 -lkern
$ nvcc  -Xcompiler '-fPIC' -I.  -c kern.cu
$ ld -o kern.ro -r kern.o -L/usr/local/cuda/lib64 -lcudart_static -lculibos
$ ar rs libkern.a kern.ro
ar: creating libkern.a
$ g++ -o main main.cpp  -I ./ -L.  -lkern -lpthread -lrt -ldl
$ cuda-memcheck ./main
========= CUDA-MEMCHECK
starting
Starting kernel with grid size 256 and block size 1
done
========= ERROR SUMMARY: 0 errors
$
注:

  • -lpthread-lrt-ldl
    是cudart/culibos的标准库依赖项,因此需要在最终链接阶段提供这些依赖项,但它们不依赖于任何CUDA工具包项。如果您希望从增量链接对象中删除这些依赖项,我将其视为一个单独的问题,与CUDA无关

  • 对于这个简单的案例,归档步骤(创建库)不是必不可少的。我们可以直接将增量链接(
    -r
    )对象
    kern.ro
    传递到最终的编译/链接步骤

  • 请注意,您的CUDA安装显然位于不同的位置,因此可能需要更改上面的一些库路径(
    -L

  • 如果您进行研究,很明显,
    -lib
    选项创建了一个静态库(并指定没有链接),而
    -shared
    选项创建了一个共享库,并指定了链接。例如,摘录:

    4.2.2.1--链接(-link) 指定默认行为:编译并链接所有输入文件

    4.2.2.2--库(-lib) 如有必要,将所有输入文件编译为对象文件,并将结果添加到指定的库输出文件中

    4.2.3.11--共享(-shared) 在链接过程中生成共享库。 使用选项——当需要其他链接器选项以实现更多控制时,使用链接器选项

    我相信这或多或少与典型的gcc/g++用法一致。如果您在“g++创建静态库”上进行谷歌搜索,您将得到任意数量的数据,表明您基本上应该这样做:

    g++ -c my_source_file.cpp ...
    ar ...
    
    换句话说,指定了源到对象的编译,但没有指定链接。举一个例子,
    cudamaloc
    是CUDA运行库的一部分,与它的连接将在链接阶段完成

    nvcc
    是一种相当复杂的隐蔽动物,但我们应该记住,对于某些功能,它主要使用已安装的主机工具链。这包括主机代码的编译,还包括最终链接阶段

    再加上这一点,我相信你想在这里做的是“部分”链接或增量链接。在最终链接阶段之前执行一些最终链接阶段

    GNU链接器(同样,默认情况下,
    nvcc
    将在linux上使用什么),因此,如果我们不考虑编译可重定位设备代码的问题,应该可以按如下方式执行您想要的操作:

    nvcc -I ./ -lib --compiler-options '-fPIC' -o libkern.a kern.cu -cudart static
    
    g++ -o main main.cpp -I ./ -L. -L/opt/cuda/lib64 -lkern
    
    $ nvcc  -Xcompiler '-fPIC' -I.  -c kern.cu
    $ ld -o kern.ro -r kern.o -L/usr/local/cuda/lib64 -lcudart_static -lculibos
    $ ar rs libkern.a kern.ro
    ar: creating libkern.a
    $ g++ -o main main.cpp  -I ./ -L.  -lkern -lpthread -lrt -ldl
    $ cuda-memcheck ./main
    ========= CUDA-MEMCHECK
    starting
    Starting kernel with grid size 256 and block size 1
    done
    ========= ERROR SUMMARY: 0 errors
    $
    
    注:

  • -lpthread-lrt-ldl
    a