ОШИБКА: неявный захват 'this' не разрешен для функций ядра, SYCL, DPCPP

Я пытаюсь написать своего рода класс карты, который обертывает вызовы OneAPI, скрывая проблемы с аппаратным таргетингом через некоторый параметр, определяющий тип цели (ЦП или ГП/ускоритель). Карта направляет код в ядро ​​SYCL или в TBB для реализации операции карты через параллельный for. Он принимает в качестве параметров тип устройства, ЦП или ГП, а также функцию и применяется ко всем элементам в коллекции. Но в функции ядра у меня есть ошибка, из-за которой неявный захват не разрешен. Я не могу понять, в чем моя ошибка. это мой код:

    #include <CL/sycl.hpp>
    #include <iostream>
    #include <tbb/tbb.h>
    #include <tbb/parallel_for.h>
    #include <vector>
    #include <string>
    #include <queue>
    #include<tbb/blocked_range.h>
    #include <tbb/global_control.h>
    
    using namespace std;
    using namespace cl::sycl;
    using namespace tbb;
    
    template<typename Tin, typename Tout>
    class Map {
    private:
        function<Tout(Tin)> fun;
        string device_type;
    public:
        Map() {}
        Map(function<Tout(Tin)> f):fun(f) {}
        void f(function<Tout(Tin)> ff) {
            fun = ff;
           }
        void set_device(string dev) {
                device_type = dev;
            }
    
    
        vector<Tout> operator()(vector<Tin>& v) {
            device *my_dev = new device();
            if(device_type == "cpu"){
                if(my_dev->is_cpu()) {
                    vector<Tout> r(0);
                    tbb::parallel_for(tbb::blocked_range<Tin>(0, v.size()),
                            [&](tbb::blocked_range<Tin> t) {
                        for (int index = t.begin(); index < t.end(); ++index){
                            r[index] = fun(v[index]);
                        }
                });
               return r;
             }
            }else if(device_type == "gpu"){
                if(my_dev->is_gpu()) {
                    vector<Tout> r(v.size());
                    sycl::queue gpuQueue{gpu_selector()};
                    sycl::range<1> n_item{v.size()};
                    sycl::buffer<Tin, 1> in_buffer(&v[0], n_item);
                    sycl::buffer<Tout, 1> out_buffer(&r[0], n_item);
                    gpuQueue.submit([&](sycl::handler& h){
                         //local copy of fun                        
                        //auto f = fun;
                        sycl::accessor in_accessor(in_buffer, h, sycl::read_only);
                        sycl::accessor out_accessor(out_buffer, h, sycl::write_only);
                        h.parallel_for(n_item, [=](sycl::id<1> index) {
                            out_accessor[index] = fun(in_accessor[index]);
                        });
                    }).wait();
                    return r;
                }
    
            }
        }
    
    };
    
    int main(int argc, char *argv[]) {
    
    
        vector<int> v = {1,2,3,4,5,6,7,8};
    
        auto f = [](int x){return (++x);};
    
        sycl::device dev = sycl::cpu_selector().select_device();
        string dev_type = argv[1];
        Map <int,int> m(f);
        m.set_device(dev_type);
        auto r = m(v);
        for(auto &e:r) {
            cout << e << "\n";
        }
    
      return 0;
    }

Когда я проверяю Проблемы в консоли Eclipse, мне показывает эту ошибку:

1- неявный захват this не разрешен для функций ядра


person Sahar Sa    schedule 01.05.2021    source источник


Ответы (1)


Вы пытаетесь получить доступ к fun в вашем ядре, переменной-члену Map. Доступ к переменным-членам в C++ осуществляется с помощью указателя this. Лямбда-выражения не захватывают указатель this по умолчанию в C++, поэтому появляется сообщение об ошибке.

Однако, даже если вы захватите this в своем ядре, это не сработает, потому что this будет указывать на память хоста, которая обычно недоступна на устройстве.

Одно очень простое решение для этого обычно состоит в том, чтобы просто использовать локальные копии в вашем ядре:

class X {
  void run(sycl::queue& q){
    q.submit([&](sycl::handler& cgh){
      int local_var = var; // Note: This can also be expressed using the lambda capture list
      cgh.parallel_for(..., [=](...){ /* use local_var here*/});
    });
  }

  int var;
};

Начиная с C++17, вы также можете просто захватить класс путем копирования: [*this](...){...}.

Более фундаментальная проблема с вашим кодом заключается в том, что спецификация SYCL не позволяет использовать std::function внутри кода устройства. В некоторых случаях и для некоторых реализаций SYCL это может работать (например, для серверных частей хоста), но это расширение. Проблема в том, что реализация std::function обычно использует механизмы, которые не поддерживаются устройством для стирания типов, такие как динамический полиморфизм.

Одним из решений может быть включение типа функции в аргументы шаблона класса вместо использования std::function.

person illuhad    schedule 02.05.2021
comment
Благодарю за ваш ответ. проблема в том, что я пытался использовать локальную копию моей функции, например: auto f = fun; но все равно выдает ошибку: параметр ядра нетривиально копирует конструируемый тип класса/структуры 'std::function‹int (int)›' - person Sahar Sa; 03.05.2021
comment
Кроме того, я обновил свой код - person Sahar Sa; 03.05.2021
comment
Как я уже объяснял, невозможно использовать std::function в коде устройства, потому что он использует функции, которые могут не поддерживаться на всех устройствах, на которые нацелен SYCL. - person illuhad; 03.05.2021
comment
не могли бы вы подсказать мне, что мне делать, пожалуйста? - person Sahar Sa; 03.05.2021
comment
в шаблоне класса у меня есть вывод и тип ввода моей функции. как вы сказали, я должен добавить еще и тип функции в шаблон?! - person Sahar Sa; 03.05.2021
comment
Вот несколько идей, как это можно реализовать: godbolt.org/z/Yac8xPod1 Примечание. Пожалуйста, не не помещайте using namespace std; using namespace cl::sycl; в свой код. Это верный путь к катастрофе, так как могут возникнуть конфликты имен, например. sycl::queue и std::queue. Также обратите внимание, что архитектура создания новой очереди, буферов для каждого вызова operator() будет неэффективной, если вы вызываете ее несколько раз. - person illuhad; 03.05.2021
comment
Большое спасибо, это было действительно полезно для меня. - person Sahar Sa; 04.05.2021