Está envolvendo um recurso de hardware usando polimorfismo indo longe demais?

5

Estou escrevendo um mecanismo de simulação que consiste em vários componentes, cada um deles operando em um conjunto fixo de buffers compartilhados.

Na prática, a simulação será executada inteiramente na GPU. No entanto, ao desenvolver um componente, é mais fácil copiar os buffers da GPU, executar o componente na CPU e gravar os buffers atualizados de volta, continuando na GPU. Quando o componente foi totalmente depurado, ele é portado para um kernel da GPU.

Eu quero limpar meu código e escrever uma interface para o 'sistema principal' (o bit que mantém os buffers) que os componentes usarão, e isso levanta a questão de como apresentar os buffers.

Eu poderia escrever algo como:

interface ISystem
{
    Array x;
    Array y;
    ComputeBuffer gpu_x;
    ComputeBuffer gpu_y;
    int numElements;
}

Mas isso não é muito legal.

Eu poderia fazer algo como:

interface ISystem
{
    IBuffer x;
    IBuffer y;
    int numElements;
}

Onde o IBuffer é uma interface adequada para uso por código que deseja acesso da CPU ao buffer, mas também o que liga o buffer a seus kernels de GPU.

A minha pergunta é, até onde devo empurrar essa abstração?

Eu poderia fazer um objeto verdadeiramente polimórfico como:

class BufferHelper<T>
{
    static implicit operator ComputeBuffer(BufferHelper helper);
    static implicit operator T[](BufferHelper helper);
}

A leitura de volta para a CPU tem implicações significativas no desempenho. No entanto, qualquer um que escrever componentes saberá isso, e será óbvio que isso ocorre e onde, do criador de perfil.

Do ponto de vista do desempenho, não há benefício em codificar a autodocumentação por meio de interfaces explícitas para os dois usos do buffer, mas ainda assim meus instintos dizem que algo não está correto com esse design.

No exemplo tradicional de polimorfismo você poderia, digamos, adicionar dois inteiros ou concatenar duas strings; operações que são análogas se não idênticas.

No caso acima, as operações (ligando um buffer vs. lendo de volta e modificando-o) são completamente diferentes .

Para colocar de outra forma, esse uso do polimorfismo vai tão longe a ponto de diminuir a legibilidade e a manutenção do código escondendo coisas que o desenvolvedor deveria ver?

    
por sebf 13.02.2018 / 12:51
fonte

2 respostas

4

A maneira como imagino seu sistema, com base na descrição fornecida, é como um conjunto de componentes de trabalho que lê valores de zero ou mais buffers de entrada e grava seus resultados em zero ou mais buffers de saída. Além desses componentes de trabalho, existe uma parte de gerenciamento / configuração que cria os componentes e buffers e os conecta da maneira correta.

Em tal sistema, os componentes do trabalhador não devem se importar de onde os valores em seus bufferes de entrada vêm, nem onde os valores no (s) buffer (s) de saída vão para. Em particular, os componentes do trabalhador não devem saber se o buffer mantém os valores na mesma GPU, copia-os para / da CPU ou até mesmo se os valores são transferidos entre as GPUs em máquinas diferentes.

Se você puder criar uma interface de buffer que possa ocultar esse tipo de informação dos componentes de trabalho usando-a sem incorrer em sobrecarga excessiva para o caso mais comum (provavelmente a comunicação entre componentes na mesma GPU), então você deve ir para isso.
Então, é responsabilidade do componente de configuração selecionar as implementações de buffer que melhor atendem aos requisitos de simulação rápida e facilitar a depuração do componente de trabalho X.

    
por 13.02.2018 / 14:38
fonte
1

Se seu objetivo é separar a chamada do aplicativo do hardware, você está OK. No entanto, se o seu objetivo é trabalhar com o P / Invoke, você está introduzindo problemas.

Este é um caso em que você pode envolver a representação interna com uma interface externa. Se você optar por fazer isso, você está fazendo Composição , que seria a maneira de apresentar uma interface fácil de usar para chamadas de hardware.

O problema com P / Invoke é que o struct que descreve a estrutura de dados que você envia para a API de baixo nível deve corresponder aos bytes esperados. Isso significa que você não pode redefinir suas interfaces de baixo nível à vontade. Você tem que optar por expô-los aos usuários ou envolvê-los com sua própria API.

    
por 13.02.2018 / 15:08
fonte