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*变量不用释放,因为它们只是保存了对应智能指针的一个副本。

posted @ 2024-06-23 13:55  兜尼完  阅读(27)  评论(0编辑  收藏  举报