OpenACC против C++: FATAL ERROR: переменная частично присутствует на устройстве

Я пытаюсь перенести какое-то приложение C++ на GPU с помощью OpenACC. Как и следовало ожидать, в коде C++ много инкапсуляции и абстракции. Память выделяется в некотором векторном классе, затем этот класс повторно используется во многих других классах приложения. И у меня возникают проблемы с попыткой правильно вставить прагмы OpenACC в код. Вот упрощенный пример кода, над которым я работаю:

#define DATASIZE 16

class Data {
  float *arr;
public:
  Data() {arr = new float[DATASIZE];}
  ~Data() { delete [] arr; }
  float &get(int i) { return arr[i]; }
};

class DataKeeper {
  Data a, b, c;
public:
  void init() {
    for (int i = 0; i < DATASIZE; ++i)
      a.get(i) = 0.0;
  }
};

int main() {
  DataKeeper DK;
  DK.init();
}

Я вставляю некоторые прагмы OpenACC для отправки необходимых данных на устройство и получаю такой код:

#define DATASIZE 16

class Data {
  float *arr;

public:
  Data() {
    arr = new float[DATASIZE];
#pragma acc enter data copyin(this)
#pragma acc enter data create(arr[:DATASIZE])
  }

  ~Data() {
#pragma acc exit data delete(arr)
#pragma acc exit data delete(this)
    delete [] arr;
  }

  float &get(int i) { return arr[i]; }
};

class DataKeeper {
  Data a, b, c;

public:
  DataKeeper() {
#pragma acc enter data copyin(this)
  }

  ~DataKeeper() {
#pragma acc exit data delete(this)
  }

  void init() {
#pragma acc parallel loop
    for (int i = 0; i < DATASIZE; ++i) {
      a.get(i) = 0.0;
    }
  }
};

int main() {
  DataKeeper DK;
  DK.init();
}

Но после компиляции и запуска я получаю следующую ошибку:

$ pgc++ test.cc -acc -g

$ ./a.out 
_T24395416_101 lives at 0x7fff49e03070 size 24 partially present
Present table dump for device[1]: NVIDIA Tesla GPU 0, compute capability 3.5, threadid=1
host:0x1ae6eb0 device:0xc05ca0200 size:64 presentcount:0+1 line:11 name:(null)
host:0x1f33620 device:0xc05ca0600 size:64 presentcount:0+1 line:11 name:(null)
host:0x1f33d10 device:0xc05ca0a00 size:64 presentcount:0+1 line:11 name:(null)
host:0x7fff49e03070 device:0xc05ca0000 size:8 presentcount:0+1 line:11 name:_T24395600_98
host:0x7fff49e03078 device:0xc05ca0400 size:8 presentcount:0+1 line:11 name:_T24395600_98
host:0x7fff49e03080 device:0xc05ca0800 size:8 presentcount:0+1 line:11 name:_T24395600_98
allocated block device:0xc05ca0000 size:512 thread:1
allocated block device:0xc05ca0200 size:512 thread:1
allocated block device:0xc05ca0400 size:512 thread:1
allocated block device:0xc05ca0600 size:512 thread:1
allocated block device:0xc05ca0800 size:512 thread:1
allocated block device:0xc05ca0a00 size:512 thread:1

FATAL ERROR: variable in data clause is partially present on the device: name=_T24395416_101
 file:/home/bozhenovn/tst/test.cc _ZN10DataKeeperC1Ev line:27

Я понятия не имею, что не так с кодом. Я был бы признателен за любые идеи о том, как я могу исправить код или предложения, как я могу продолжить исследование проблемы. Благодарю вас!


person Nikolai    schedule 11.04.2017    source источник


Ответы (1)


Здесь происходит то, что адрес хоста «a» совпадает с начальным адресом «DK». Следовательно, когда компилятор ищет адрес хоста в текущей таблице, которую он использует для сопоставления адреса хоста переменной с адресом устройства, он видит, что размер другой. «a» — размер 8, а «DK» — размер 24.

Я покажу исправление ниже, но давайте вернемся назад и поймем, что здесь происходит. Когда «DK» создается на хосте, он сначала создает хранилище для каждого из своих членов данных, а затем вызывает конструктор класса каждого члена данных. Затем он выполняет свой собственный конструктор. Следовательно, для каждого члена данных ваш код создаст указатель класса this на устройстве, а затем выделит массив «arr» на устройстве. После этого на устройстве создается «DK» с пространством для каждого члена данных. Однако, поскольку копия устройства "DK" создается после элементов данных, компилятор не может автоматически связать их.

Ниже я разместил два возможных исправления.

Во-первых, вы можете позволить классу «Данные» управлять своими собственными данными, но вам потребуется динамически распределять элементы данных класса. Таким образом, конструктор Data появится после конструктора DataKeeper, чтобы компилятор мог связать данные устройства (также называемые «присоединить»).

Во-вторых, вы можете использовать класс DataKeeper для управления данными класса Data. Однако для этого потребуется, чтобы данные Data были общедоступными.

Обратите внимание, что я написал главу 5 «Расширенное управление данными» книги «Параллельное программирование с OpenACC» и включил в нее раздел об управлении данными классов C++. Вы можете найти мой пример кода по адресу: https://github.com/rmfarber/ParallelProgrammingWithOpenACC/tree/master/Chapter05 В частности, посмотрите, как я создал общий класс-контейнер "accList".

Исправление №1:

#define DATASIZE 16
#include <iostream>
#ifdef _OPENACC
#include <openacc.h>
#endif

class Data {
  float *arr;

public:
  Data() {
    arr = new float[DATASIZE];
#pragma acc enter data copyin(this)
#pragma acc enter data create(arr[:DATASIZE])
  }

  ~Data() {
#pragma acc exit data delete(arr)
#pragma acc exit data delete(this)
    delete [] arr;
  }

  float &get(int i) { return arr[i]; }
  void updatehost() {
   #pragma acc update host(arr[0:DATASIZE])
  }

};

class DataKeeper {
  Data *a, *b, *c;

public:
  DataKeeper() {
#pragma acc enter data copyin(this)
  a = new Data;
  b = new Data;
  c = new Data;
  }

  ~DataKeeper() {
#pragma acc exit data delete(this)
  delete a;
  delete b;
  delete c;
  }

  void init() {
#pragma acc parallel loop present(a,b,c)
    for (int i = 0; i < DATASIZE; ++i) {
      a->get(i) = i;
    }
    a->updatehost();
    std::cout << "a.arr[0]=" << a->get(0) << std::endl;
    std::cout << "a.arr[end]=" << a->get(DATASIZE-1) << std::endl;
  }
};

int main() {
  DataKeeper DK;
  DK.init();
}

Исправление № 2

#define DATASIZE 16
#include <iostream>
#ifdef _OPENACC
#include <openacc.h>
#endif

class Data {
public:
  float *arr;

  Data() {
    arr = new float[DATASIZE];
  }

  ~Data() {
    delete [] arr;
  }

  float &get(int i) { return arr[i]; }
};

class DataKeeper {
  Data a, b, c;

public:
  DataKeeper() {
#pragma acc enter data copyin(this)
#pragma acc enter data create(a.arr[0:DATASIZE])
#pragma acc enter data create(b.arr[0:DATASIZE])
#pragma acc enter data create(c.arr[0:DATASIZE])
  }

  ~DataKeeper() {
#pragma acc exit data delete(this)
#pragma acc exit data delete(a.arr)
#pragma acc exit data delete(b.arr)
#pragma acc exit data delete(c.arr)
  }

  void init() {
#pragma acc parallel loop present(a,b,c)
    for (int i = 0; i < DATASIZE; ++i) {
      a.get(i) = i;
    }
#pragma acc update host(a.arr[0:DATASIZE])
    std::cout << "a.arr[0]=" << a.arr[0] << std::endl;
    std::cout << "a.arr[end]=" << a.arr[DATASIZE-1] << std::endl;
  }
};

int main() {
  DataKeeper DK;
  DK.init();
}
person Mat Colgrove    schedule 11.04.2017
comment
Большое спасибо! Я выбрал второй подход, и он работает. Кстати, не могли бы вы посоветовать что-нибудь почитать по отладке приложений OpenACC? Кажется, в книге нет отдельной главы по отладке, не так ли? - person Nikolai; 14.04.2017
comment
Я не знаю каких-либо статей конкретно по отладке OpenACC. Я, вероятно, должен написать один. Однако проблема в том, что я часто меняю технику, которую использую, в зависимости от приложения и характера ошибки, поэтому не подходит для хорошей статьи по общей отладке. - person Mat Colgrove; 14.04.2017
comment
В общем, я бы рекомендовал сначала отладить код без включенного OpenACC. У меня были пользователи, присылавшие мне код, говорящий, что OpenACC не работает, когда на самом деле код не работал в -O0 -g. Затем я бы перешел к использованию -ta=multicore, так как вам не нужно беспокоиться о перемещении данных и вы можете сосредоточиться на проблемах с распараллеливанием. Отладчики, такие как PGDBG, которые могут отлаживать многопоточные приложения, также будут работать. Наконец, переместите на GPU и обратите внимание на сообщения обратной связи компилятора (-Minfo=accel) - person Mat Colgrove; 14.04.2017
comment
Если вы получаете неправильные ответы, посмотрите на движение данных. Скорее всего вам не хватает синхронизации данных. Ошибки недопустимого адреса обычно возникают из-за доступа за границу или разыменования указателей узлов на устройстве. Также могут помочь такие утилиты, как cuda-memcheck и cuda-gdb. Кроме того, вы можете установить переменную среды PGI_ACC_DEBUG=1, которая будет выводить все вызовы во время выполнения. Особенно полезно, когда происходит сбой кода, и вы хотите быстро определить, в какой вычислительной области произошел сбой. - person Mat Colgrove; 14.04.2017