Nota che in realtà ce ne sono due tipi. Avete CL_MEM_READ_ONLY
, CL_MEM_WRITE_ONLY
e CL_MEM_READ_WRITE
quando assegnate i vostri buffer ma poi avete anche __read_only
, __write_only
e __read_write
per decorare i vostri puntatori nel codice del kernel con.
Questi potrebbero essere utilizzati sia per l'ottimizzazione che per il controllo degli errori. Diamo prima un'occhiata alle prestazioni. Se viene rilevato un buffer di sola scrittura, le scritture non devono essere memorizzate nella cache (come nella cache di scrittura), risparmiando più cache per le letture. Questo dipende molto dall'hardware della GPU e almeno l'hardware NVIDIA ha le istruzioni necessarie per implementarlo (i modificatori .cs
e .lu
). È possibile fare riferimento al loro PTX ISA. Non ho visto alcuna prova del compilatore che opera questa ottimizzazione, ad esempio:
__kernel void Memset4(__global __write_only unsigned int *p_dest,
const unsigned int n_dword_num)
{
unsigned int i = get_global_id(0);
if(i < n_dword_num)
p_dest[i] = 0; // this
}
viene compilato come:
st.global.u32 [%r10], %r11; // no cache operation specified
questo ha un senso come CUDA non ha equivalenti per quelle qualificazioni in modo che il compilatore molto probabilmente ignora silenziosamente quelli. Ma non fa male metterli lì, potremmo diventare più fortunati in futuro. In CUDA, alcune di queste funzionalità sono esposte utilizzando la funzione __ldg
e utilizzando i flag del compilatore per attivare/disattivare la memorizzazione nella cache dei trasferimenti globali di memoria in L1 (-Xptxas -dlcm=cg
). È inoltre possibile utilizzare sempre asm
se si scopre che il bypass della cache produce un vantaggio importante.
Per quanto riguarda il controllo degli errori, la scrittura su un buffer di sola lettura viene prontamente evitata utilizzando lo specificatore const
nella dichiarazione del kernel. La disabilitazione della lettura da un buffer di sola scrittura non è possibile nella "C" pura.
Un altro possibile ottimizzazione si verifica quando si mappano i buffer nella memoria host. Quando si esegue il mapping di un buffer CL_MEM_READ_ONLY
, la regione mappata potrebbe non essere inizializzata poiché l'host scriverà solo in quella memoria, in modo che il dispositivo possa leggerlo solo. Allo stesso modo, quando si annulla la mappatura di un buffer CL_MEM_WRITE_ONLY
, il driver non deve copiare il contenuto (potenzialmente modificato dall'host) dalla memoria dell'host alla memoria del dispositivo. Non ho misurato questo.
Come nota a margine, ho provato ad utilizzare:
inline unsigned int n_StreamingLoad(__global __read_only const unsigned int *p_src)
{
#ifdef NVIDIA
unsigned int n_result;
asm("ld.global.cs.u32 %r0, [%r1];" : "=r" (n_result) : "r" (p_src));
return n_result;
#else // NVIDIA
return *p_src; // generic
#endif // NVIDIA
}
inline void StreamingWrite(__global __write_only unsigned int *p_dest, const unsigned int n_value)
{
#ifdef NVIDIA
asm("st.global.cs.u32 [%r0], %r1;" : : "r" (p_dest), "r" (n_value) : "memory");
#else // NVIDIA
*p_dest = n_value; // generic
#endif // NVIDIA
}
che vi dà circa 15 extra GB/sec anche su un semplice kernel memcpy con sm_35
dispositivi (testato su GTX 780 e K40). Non ho visto un'accelerazione notevole su sm_30
(non sono sicuro che sia stato pensato anche per essere supportato lì - anche se le istruzioni non sono state rimosse da ptx). Nota che devi definire tu stesso lo NVIDIA
(o vedere Detect OpenCL device vendor in kernel code).