OpenCL中的SVM使用案例
SVM(共享虚拟内存)是为了解决向显卡传输数据中包含指针的问题。此时仅用cl::Buffer拷贝数据是不够的,因为数据中的指针会因为拷贝变成野指针。这就需要SVM的帮助,它可以保证数据中的指针到达GPU后仍然可以使用。这里给出一个计算单向链表中数字的和的例子。代码运行环境是VS2017,OpenCL3,显卡是Intel Core i5的核芯显卡。CPP文件如下:
string kernelStr = R"( struct Element { struct Element* next; float data; }; struct MLinkedList { struct Element* first; struct Element* last; }; global volatile atomic_float sum = ATOMIC_VAR_INIT(0); kernel void add(global const struct MLinkedList* input, global float* output) { struct Element* iter = input->first; while (iter) { atomic_fetch_add(&sum, iter->data); iter = iter->next; } *output = atomic_load(&sum); })"; class MLinkedList { private: struct Element; public: using Pointer = cl::pointer<MLinkedList::Element, cl::detail::Deleter< cl::SVMAllocator<MLinkedList::Element, cl::SVMTraitCoarse<>>>>; MLinkedList(); void append(float value, vector<Pointer>& box); private: Element* first; Element* last; }; struct MLinkedList::Element { Element* next; float data; }; MLinkedList::MLinkedList() { first = 0; last = 0; } void MLinkedList::append(float value, vector<Pointer>& box) { Pointer elem = cl::allocate_svm<MLinkedList::Element, cl::SVMTraitCoarse<>>(); elem->data = value; elem->next = 0; if (!last) { first = elem.get(); last = first; } else { last->next = elem.get(); last = last->next; } box.push_back(std::move(elem)); } int main() { cl::Program program(kernelStr); try { program.build("-cl-std=CL2.0"); } catch (...) { cl_int buildErr = CL_SUCCESS; auto buildInfo = program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(&buildErr); for (auto &pair : buildInfo) { std::cerr << pair.second << std::endl << std::endl; } return 1; } vector<MLinkedList::Pointer> svmBox; auto svmList = cl::allocate_svm<MLinkedList, cl::SVMTraitCoarse<>>(); svmList->append(100.5f, svmBox); svmList->append(200.5f, svmBox); svmList->append(300.5f, svmBox); svmList->append(400.5f, svmBox); vector<float> b(1, 0); cl::Buffer outputb(b.begin(), b.end(), false); auto kernel = cl::KernelFunctor<decltype(svmList)&, cl::Buffer&>(program, "add"); //std::for_each(svmBox.begin(), svmBox.end(), /* 注释1,× */ // [&kernel](MLinkedList::Pointer& p) { kernel.setSVMPointers(p); }); //vector<void*> svmps; /* 注释2,√ */ //std::for_each(svmBox.begin(), svmBox.end(), // [&svmps](MLinkedList::Pointer& p) { svmps.push_back(p.get()); }); //kernel.setSVMPointers(svmps); kernel.setSVMPointers(svmBox[0], svmBox[1], svmBox[2], svmBox[3]); int64 t1, t2; t1 = getTickCount(); kernel(cl::EnqueueArgs(cl::NDRange(1)), svmList, outputb); cl::copy(outputb, b.begin(), b.end()); cout << b[0] << endl; t2 = getTickCount(); cout << "CL1(ms):" << (t2 - t1) / getTickFrequency() * 1000 << endl; int c; cin >> c; return 0; }
本例仅用来展示SVM的用法,在运算效率上对CPU是没有任何优势的,因为此例GPU也是串行计算数字的和的。代码里MLinkedList是一个单向链表,只有十几行代码,对于有一定基础的人很容易看懂。需注意setSVMPointers(...)函数只能调用一次,需一次性将核函数引用的所有SVM指针设置完毕。所以上述代码中的注释1是错的,它分4次设置指针。注释2是对的,它是一次性设置完的,只不过用的是setSVMPointers(...)的另一个重载函数。下面是程序运行截图:
最后再插一嘴,上述通过cl::allocate_svm<...>分配的内存不需要手动释放,因为它返回的是智能指针而不是裸指针。所以链表中的Element*变量不用释放,因为它们只是保存了对应智能指针的一个副本。