L'involucro di una risorsa hardware utilizza il polimorfismo andando troppo oltre?

5

Sto scrivendo un motore di simulazione costituito da un numero di componenti, ognuno dei quali opera su un set fisso di buffer condivisi.

In pratica, la simulazione funzionerà interamente sulla GPU. Tuttavia, quando si sviluppa un componente, è più semplice copiare i buffer dalla GPU, eseguire il componente sulla CPU e scrivere nuovamente i buffer aggiornati, continuando sulla GPU. Quando il componente è stato completamente debugato, viene portato su un kernel GPU.

Voglio pulire il mio codice e scrivere un'interfaccia al 'sistema principale' (il bit che gestisce i buffer) che i componenti useranno, e questo solleva la questione su come presentare i buffer.

Potrei scrivere qualcosa del tipo:

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

Ma non è molto pulito.

Potrei fare qualcosa di simile:

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

Dove IBuffer è un'interfaccia adatta per l'uso da parte del codice che vuole l'accesso della CPU al buffer, ma anche quello che lega il buffer ai suoi kernel GPU.

La mia domanda è: quanto lontano dovrei spingere questa astrazione?

Potrei creare un oggetto veramente polimorfico come:

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

La lettura della CPU ha implicazioni significative sulle prestazioni. Tuttavia, chiunque scriverà i componenti lo saprà, e sarà ovvio che ciò avvenga, e dove, dal profiler.

Da un punto di vista delle prestazioni, quindi, non c'è alcun vantaggio per il codice di auto-documentazione avendo interfacce esplicite per i due usi del buffer, ma il mio istinto dice che qualcosa non è giusto con questo design.

Nell'esempio tradizionale di polimorfismo, aggiungerei due interi o concatenare due stringhe; operazioni analoghe se non identiche.

Nel caso precedente, le operazioni (legare un buffer e leggerlo e modificarlo) sono completamente differenti .

Per dirla in un altro modo, questo uso del polimorfismo porta a ridurre la leggibilità e la manutenibilità del codice nascondendo cose che lo sviluppatore dovrebbe vedere?

    
posta sebf 13.02.2018 - 12:51
fonte

2 risposte

4

Il modo in cui immagino il tuo sistema, basato sulla descrizione fornita, è come un insieme di componenti worker che leggono valori da zero o più buffer di input e scrivono i loro risultati su zero o più buffer di output. Oltre a questi componenti di lavoro, c'è una parte di gestione / configurazione che crea i componenti e i buffer e li collega insieme nel modo corretto.

In un tale sistema, le componenti worker non dovrebbero preoccuparsi di dove provengono i valori nei loro buffer di input, né dove vanno a finire i valori nei buffer di output. In particolare, i componenti worker non dovrebbero sapere se il buffer mantiene i valori sulla stessa GPU, li copia nella / dalla CPU o anche se i valori vengono trasferiti tra GPU su macchine diverse.

Se puoi progettare un'interfaccia buffer in grado di nascondere quel tipo di informazioni dai componenti dell'operatore che la utilizzano senza incorrere in un sovraccarico eccessivo per il caso più comune (probabilmente comunicazione tra componenti sulla stessa GPU), allora dovresti farlo.
Quindi è responsabilità del componente di configurazione selezionare le implementazioni del buffer che soddisfano al meglio i requisiti per la simulazione rapida e la facilità del debug del componente X.

    
risposta data 13.02.2018 - 14:38
fonte
1

Se il tuo obiettivo è separare la chiamata dell'applicazione dall'hardware, sei OK. Tuttavia, se il tuo obiettivo è quello di lavorare con P / Invoke, allora stai introducendo dei problemi.

Questo è un caso in cui è possibile includere la rappresentazione interna con un'interfaccia esterna. Se scegli di farlo, esegui Composizione quale sarebbe il modo di presentare un'interfaccia facile da usare per le chiamate hardware.

Il problema con P / Invoke è che il tuo struct che descrive la struttura dati che invii all'API di basso livello deve corrispondere ai byte previsti. Ciò significa che non puoi ridefinire a piacere le tue interfacce di basso livello. Devi scegliere di esporli agli utenti o di includerli con la tua API.

    
risposta data 13.02.2018 - 15:08
fonte