我正在尝试将CUDA程序分成两个独立的.cu文件,以便更接近用C ++编写真正的应用程序。我有一个简单的小程序:
在主机和设备上分配内存 将主机阵列初始化为一系列数字。 将主机阵列复制到设备阵列 使用设备内核查找数组中所有元素的平方 将设备阵列复制回主机阵列 打印结果
如果我把它全部放在一个.cu文件中并运行它,这很有用。当我将它分成两个单独的文件时,我开始得到链接错误。像我最近的所有问题一样,我知道这很小,但它是什么?
KernelSupport.cu
#ifndef _KERNEL_SUPPORT_
#define _KERNEL_SUPPORT_
#include <iostream>
#include <MyKernel.cu>
int main( int argc, char** argv)
{
int* hostArray;
int* deviceArray;
const int arrayLength = 16;
const unsigned int memSize = sizeof(int) * arrayLength;
hostArray = (int*)malloc(memSize);
cudaMalloc((void**) &deviceArray, memSize);
std::cout << "Before device\n";
for(int i=0;i<arrayLength;i++)
{
hostArray[i] = i+1;
std::cout << hostArray[i] << "\n";
}
std::cout << "\n";
cudaMemcpy(deviceArray, hostArray, memSize, cudaMemcpyHostToDevice);
TestDevice <<< 4, 4 >>> (deviceArray);
cudaMemcpy(hostArray, deviceArray, memSize, cudaMemcpyDeviceToHost);
std::cout << "After device\n";
for(int i=0;i<arrayLength;i++)
{
std::cout << hostArray[i] << "\n";
}
cudaFree(deviceArray);
free(hostArray);
std::cout << "Done\n";
}
#endif
MyKernel.cu
#ifndef _MY_KERNEL_
#define _MY_KERNEL_
__global__ void TestDevice(int *deviceArray)
{
int idx = blockIdx.x*blockDim.x + threadIdx.x;
deviceArray[idx] = deviceArray[idx]*deviceArray[idx];
}
#endif
构建日志:
1>------ Build started: Project: CUDASandbox, Configuration: Debug x64 ------
1>Compiling with CUDA Build Rule...
1>"C:\CUDA\bin64\nvcc.exe" -arch sm_10 -ccbin "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\bin" -Xcompiler "/EHsc /W3 /nologo /O2 /Zi /MT " -maxrregcount=32 --compile -o "x64\Debug\KernelSupport.cu.obj" "d:\Stuff\Programming\Visual Studio 2008\Projects\CUDASandbox\CUDASandbox\KernelSupport.cu"
1>KernelSupport.cu
1>tmpxft_000016f4_00000000-3_KernelSupport.cudafe1.gpu
1>tmpxft_000016f4_00000000-8_KernelSupport.cudafe2.gpu
1>tmpxft_000016f4_00000000-3_KernelSupport.cudafe1.cpp
1>tmpxft_000016f4_00000000-12_KernelSupport.ii
1>Linking...
1>KernelSupport.cu.obj : error LNK2005: __device_stub__Z10TestDevicePi already defined in MyKernel.cu.obj
1>KernelSupport.cu.obj : error LNK2005: "void __cdecl TestDevice__entry(int *)" (?TestDevice__entry@@YAXPEAH@Z) already defined in MyKernel.cu.obj
1>D:\Stuff\Programming\Visual Studio 2008\Projects\CUDASandbox\x64\Debug\CUDASandbox.exe : fatal error LNK1169: one or more multiply defined symbols found
1>Build log was saved at "file://d:\Stuff\Programming\Visual Studio 2008\Projects\CUDASandbox\CUDASandbox\x64\Debug\BuildLog.htm"
1>CUDASandbox - 3 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
我在Windows 7 64位上运行Visual Studio 2008。
编辑:
我想我需要详细说明这一点。我在这里寻找的最终结果是有一个普通的C ++应用程序,其中包含Main.cpp和int main()
事件,并从那里开始运行。在我的.cpp代码的某些点上,我希望能够引用CUDA位。所以我的想法(如果有更标准的约定,请纠正我)是我将CUDA内核代码放入他们的.cu文件中,然后有一个支持.cu文件,它将负责与设备通话并调用内核函数,什么不是。
答案 0 :(得分:12)
您在mykernel.cu
中包含kernelsupport.cu
,当您尝试链接编译器时会看到mykernel.cu两次。您必须创建一个定义TestDevice的标头,并将其包括在内。
评论:
这样的事情应该有效
// MyKernel.h
#ifndef mykernel_h
#define mykernel_h
__global__ void TestDevice(int* devicearray);
#endif
然后将包含文件更改为
//KernelSupport.cu
#ifndef _KERNEL_SUPPORT_
#define _KERNEL_SUPPORT_
#include <iostream>
#include <MyKernel.h>
// ...
重新编辑
只要你在c ++代码中使用的标题没有任何cuda特定的东西(__kernel__
,__global__
等),你应该很好地链接c ++和cuda代码。
答案 1 :(得分:3)
如果你看一下CUDA SDK代码示例,他们有extern C定义了从.cu文件编译的引用函数。这样,.cu文件由nvcc编译,只链接到主程序,而.cpp文件正常编译。
例如,在marchingCubes_kernel.cu中有函数体:
extern "C" void
launch_classifyVoxel( dim3 grid, dim3 threads, uint* voxelVerts, uint *voxelOccupied, uchar *volume,
uint3 gridSize, uint3 gridSizeShift, uint3 gridSizeMask, uint numVoxels,
float3 voxelSize, float isoValue)
{
// calculate number of vertices need per voxel
classifyVoxel<<<grid, threads>>>(voxelVerts, voxelOccupied, volume,
gridSize, gridSizeShift, gridSizeMask,
numVoxels, voxelSize, isoValue);
cutilCheckMsg("classifyVoxel failed");
}
在marchingCubes.cpp(main()所在的位置)只有一个定义:
extern "C" void
launch_classifyVoxel( dim3 grid, dim3 threads, uint* voxelVerts, uint *voxelOccupied, uchar *volume,
uint3 gridSize, uint3 gridSizeShift, uint3 gridSizeMask, uint numVoxels,
float3 voxelSize, float isoValue);
您也可以将它们放在.h文件中。
答案 2 :(得分:3)
实现分离实际上非常简单,请查看this answer了解如何进行设置。然后,您只需将主机代码放在.cpp文件中,将设备代码放在.cu文件中,构建规则就会告诉Visual Studio如何将它们链接到最终的可执行文件中。
代码中的直接问题是您定义__global__ TestDevice
函数两次,一次是#include
MyKernel.cu,一次是独立编译MyKernel.cu。
您需要将一个包装器放入.cu文件中 - 当您从主函数调用TestDevice<<<>>>
时,但当您将其移动到.cpp文件时,它将使用cl.exe编译,它不理解<<<>>>
语法。因此,您只需在.cpp文件中调用TestDeviceWrapper(griddim, blockdim, params)
并在.cu文件中提供此功能。
如果你想要一个例子,SDK中的SobolQRNG示例实现了很好的分离,尽管它仍然使用cutil,我总是建议避免使用cutil。
答案 3 :(得分:-3)
简单的解决方案是关闭MyKernel.cu文件的构建。
属性 - &gt;一般 - &gt;从构建中排除
更好的解决方案是将内核拆分为cu和cuh文件,并将其包括在内,例如:
//kernel.cu
#include "kernel.cuh"
#include <cuda_runtime.h>
__global__ void increment_by_one_kernel(int* vals) {
vals[threadIdx.x] += 1;
}
void increment_by_one(int* a) {
int* a_d;
cudaMalloc(&a_d, 1);
cudaMemcpy(a_d, a, 1, cudaMemcpyHostToDevice);
increment_by_one_kernel<<<1, 1>>>(a_d);
cudaMemcpy(a, a_d, 1, cudaMemcpyDeviceToHost);
cudaFree(a_d);
}
//kernel.cuh
#pragma once
void increment_by_one(int* a);
//main.cpp
#include "kernel.cuh"
int main() {
int a[] = {1};
increment_by_one(a);
return 0;
}