Пример закрепленной памяти JCuda
JCuda + GEForce Gt640 Вопрос:
Я пытаюсь уменьшить задержку, связанную с копированием памяти с устройства на хост после того, как результат вычислен графическим процессором. Делая простую программу Vector Add, я обнаружил, что большая часть задержки действительно копирует буфер результатов обратно на сторону хоста. Задержка передачи исходных буферов на стороне устройства незначительна ~.30 мс, а копирование результата обратно составляет порядка 20 мс.
Я провел исследование и обнаружил, что лучшей альтернативой копированию результатов является использование закрепленной памяти. Из того, что я узнал, эта память выделяется на стороне хоста, но ядро будет иметь прямой доступ к ней через pci-e, что, в свою очередь, даст более высокую скорость, чем копирование результата после массового вычисления. Я использую следующий пример, но результаты не дают того, что я ожидаю.
Ядро: {Простой пример, чтобы проиллюстрировать точку, запуск 1 блока 1 потока только}
extern "C"
__global__ void add(int* test)
{
test[0]=1; test[1]=2; test[2]=3; test[3]=4; test[4]=5;
}
Джава:
import java.io.*;
import jcuda.*;
import jcuda.runtime.*;
import jcuda.driver.*;
import static jcuda.runtime.cudaMemcpyKind.*;
import static jcuda.driver.JCudaDriver.*;
public class JCudaTest
{
public static void main(String args[])
{
// Initialize the driver and create a context for the first device.
cuInit(0);
CUdevice device = new CUdevice();
cuDeviceGet(device, 0);
CUcontext context = new CUcontext();
cuCtxCreate(context, 0, device);
// Load the ptx file.
CUmodule module = new CUmodule();
JCudaDriver.cuModuleLoad(module, "JCudaKernel.ptx");
// Obtain a function pointer to the kernel function.
CUfunction function = new CUfunction();
JCudaDriver.cuModuleGetFunction(function, module, "add");
Pointer P = new Pointer();
JCudaDriver.cuMemAllocHost(P, 5*Sizeof.INT);
Pointer kernelParameters = Pointer.to(P);
// Call the kernel function with 1 block, 1 thread:
JCudaDriver.cuLaunchKernel(function, 1, 1, 1, 1, 1, 1, 0, null, kernelParameters, null);
int [] T = new int[5];
JCuda.cudaMemcpy(Pointer.to(T), P, 5*Sizeof.INT, cudaMemcpyHostToHost);
// Print the results:
for(int i=0; i<5; i++)
System.out.println(T[i]);
}
}
1.) Постройте ядро: root@NVS295-CUDA:~/JCUDA/MySamples# nvcc -ptx JCudaKernel.cu root@NVS295-CUDA:~/JCUDA/MySamples# ls -lrt | grep ptx -rw-r- r-- 1 root root 3295 27 марта 17:46 JCudaKernel.ptx
2.) Сборка Java: root @ NVS295-CUDA: ~ / JCUDA / MySamples # javac -cp "../JCuda-All-0.5.0-bin-linux-x86/*:." JCudaTest.java
3.) Запустите код: root@NVS295-CUDA:~/JCUDA/MySamples# java -cp "../JCuda-All-0.5.0-bin-linux-x86/*:." JCudaTest 0 0 0 0 0
Ожидается: 1 2 3 4 5
Примечание: я использую JCuda0.5.0 для x86, если это имеет значение.
Пожалуйста, дайте мне знать, что я делаю не так, и спасибо заранее: Илир
1 ответ
Проблема здесь в том, что устройство может не получить доступ к памяти хоста напрямую.
По общему признанию, документация здесь вводит в заблуждение:
cuMemAllocHost
Выделяет байтовые байты памяти хоста, которая заблокирована на странице и доступна для устройства...
Это звучит как четкое утверждение. Однако "доступность" здесь не означает, что память может использоваться непосредственно в качестве параметра ядра во всех случаях. Это возможно только на устройствах, поддерживающих унифицированную адресацию. Для всех других устройств необходимо получить указатель устройства, который соответствует выделенному указателю хоста, с помощью cuMemHostGetDevicePointer.
Ключевым моментом памяти хоста с блокировкой страницы является то, что передача данных между хостом и устройством происходит быстрее. Пример использования этой памяти в JCuda можно увидеть в образце JCudaBandwidthTest (это для API времени выполнения, но для API драйвера это работает аналогично).
РЕДАКТИРОВАТЬ:
Обратите внимание, что новая функция Unified Memory в CUDA 6 фактически поддерживает то, что вы изначально намеревались сделать: cudaMallocManaged
Вы можете выделить память, которая напрямую доступна хосту и устройству (в том смысле, что она может, например, передаваться ядру, записываться устройством и впоследствии считываться хостом без дополнительных усилий). К сожалению, эта концепция не очень хорошо отображается на Java, потому что память по-прежнему управляется CUDA - и эта память не может заменить память, которая, например, используется виртуальной машиной Java для float[]
массив или так. Но, по крайней мере, должно быть возможно создать ByteBuffer
из памяти, которая была выделена с cudaMallocManaged
, так что вы можете получить доступ к этой памяти, например, как FloatBuffer
,