MTLBuffer копирует данные в определенный буфер на хосте прямо с устройства

Описание проблемы

Моя программа использует MTLBuffer для выделения некоторой памяти на графическом процессоре и выполнения с ней вычислений. Затем мне нужно скопировать результат в определенное место на хосте. Все решения, которые я нашел в Интернете, включают в себя сначала синхронизацию буфера, а затем его копирование в нужное мне место. Есть ли способ скопировать данные из MTLBuffer напрямую в буфер хоста?


Образец кода

Текущая реализация:

void ComputeOnGPU(void* hostBuff, size_t buffSize)
{
    id<MTLBuffer> gpuBuff = [device newBufferWithLength: buffSize
                                    options: MTLResourceStorageModeManaged];
                                    
    //
    // Do stuff with the GPU buffer
    //

    id<MTLCommandQueue> commandQueue = [device newCommandQueue];
    id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
    id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
    [blitEncoder synchronizeResource: gpuBuff];
    [blitEncoder endEncoding];
    [commandBuffer commit];
    [commandBuffer waitUntilCompleted];

    std::memcpy(hostBuff, [gpuBuff contents], buffSize);

    [gpuBuff setPurgeableState: MTLPurgeableStateEmpty];
    [gpuBuff release];
}

Что я ищу:

void ComputeOnGPU(void* hostBuff, size_t buffSize)
{
    id<MTLBuffer> gpuBuff = [device newBufferWithLength: buffSize
                                    options: MTLResourceStorageModeManaged];
                                    
    //
    // Do stuff with the GPU buffer
    //

    id<MTLCommandQueue> commandQueue = [device newCommandQueue];
    id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
    id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];

    // Is there something like that?
    [blitEncoder copyMemoryFromBuffer: gpuBuff
                 toHost: hostBuff];

    [blitEncoder endEncoding];
    [commandBuffer commit];
    [commandBuffer waitUntilCompleted];

    [gpuBuff setPurgeableState: MTLPurgeableStateEmpty];
    [gpuBuff release];
}

Дополнительная информация

  1. Размер переданного ЦП буфера больше 4 КБ.
  2. hostBuff — это общий буфер. Он приходит извне, поэтому я не могу быть уверен, что он был выделен mmap().

Нужно ли вам использовать опцию режима хранения MTLResourceStorageModeManaged для gpuBuff?

Hamid Yusifli 16.03.2022 20:22

@HamidYusifli, в моей текущей реализации - да. Если мне не нужен общий указатель на хосте для копирования данных из буфера, лучше использовать MTLResourceStorageModePrivate.

Mykhailo Mushynskyi 16.03.2022 22:38
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
2
79
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Отвечая на ваш прямой вопрос: невозможно скопировать MTLBuffer в указатель хоста. Я думаю, что причина сводится к тому, как виртуальная память отображается для процессора и графического процессора. И даже если бы он был, он был бы идентичен тому, как работают управляемые буферы, потому что команда, которую вы кодируете в кодировщике команд, выполняется на временной шкале графического процессора, что означает, что даже после вызова этого командного метода вы не увидите результат в указатель хоста, если вы не завершите кодирование в кодировщике команд, а затем commit буфере команд, а затем дождитесь его завершения. Однако существует способ копирования между двумя MTLBuffer: -[MTLBlitCommandEncoder copyFromBuffer:sourceOffset:toBuffer:destinationOffset:size:].

Но если вы используете управляемые буферы, есть другой способ сделать это. Вы можете просто вызвать -[MTLBlitCommandEncoder synchronizeResource:], чтобы сделать изменения, сделанные на временной шкале графического процессора, видимыми, и прочитать их содержимое через -[MTLBuffer contents].

Кроме того, если вы всегда считываете его обратно на ЦП, а объем данных относительно невелик, вы также можете использовать режим общего хранилища.

Есть две статьи, в которых более подробно рассматривается выбор режима хранения: Выбор режима хранения ресурсов в iOS и tvOS и Выбор режима хранения ресурсов в macOS .

Спасибо за ответ. Размер буфера в большинстве случаев составляет около 80 МБ, поэтому я не должен использовать здесь общий режим. Я не вижу проблем в ожидании завершения выполнения команды. Проблема, которую я пытаюсь решить, состоит в том, чтобы избежать избыточных операций копирования. В настоящее время я копирую из VRAM в RAM (синхронизирую) и копирую из одного места в RAM в другое (memcpy). Я не очень хочу memcpy здесь, так как по логике должна быть возможность скопировать прямо в нужное мне место. AFAIK, это возможно с CUDA.

Mykhailo Mushynskyi 16.03.2022 22:53

Ну, на самом деле я думаю, что вы можете использовать -[MTLDevice newBufferWithBytesNoCopy:length:options:deallocator:], чтобы «поделиться» частью виртуальной памяти, не копируя ее, но при этом использовать ее как «буфер хоста».

JustSomeGuy 16.03.2022 23:37

Будьте осторожны, читайте документацию, вы не можете поделиться какой-либо частью памяти, она должна быть выровнена по границе страницы и выделена чем-то вроде vm_allocate

JustSomeGuy 16.03.2022 23:37

Боюсь, я не могу использовать newBufferWithBytesNoCopy здесь, потому что я продолжаю использовать буфер хоста после освобождения MTLBuffer.

Mykhailo Mushynskyi 16.03.2022 23:55

Но подождите... deallocator вызывается для какого буфера: RAM, VRAM или обоих? Они не упоминают об этом в документации: developer.apple.com/documentation/metal/mtldevice/…

Mykhailo Mushynskyi 16.03.2022 23:56

Можно использовать версию без deallocator или просто ничего не делать в блоке. Deallocator вызывается для указателя памяти, который вы передали конструктору, поэтому память хоста, когда MTLBuffer выходит за пределы области видимости. Драйвер должен очистить VRAM за вас.

JustSomeGuy 17.03.2022 00:07

Другие вопросы по теме