example - Il miglior approccio per GPGPU / CUDA / OpenCL in Java?





(8)


So che è tardi ma dai un'occhiata a questo: https://github.com/pcpratts/rootbeer1

Non ho lavorato con esso, ma sembra molto più facile da usare rispetto ad altre soluzioni.

Dalla pagina del progetto:

Rootbeer è più avanzato di CUDA o OpenCL Java Language Bindings. Con i binding lo sviluppatore deve serializzare complessi grafici di oggetti in matrici di tipi primitivi. Con Rootbeer questo viene fatto automaticamente. Anche con i binding di linguaggio, lo sviluppatore deve scrivere il kernel GPU in CUDA o OpenCL. Con Rootbeer viene eseguita un'analisi statica del Bytecode Java (utilizzando Fuliggine) e il codice CUDA viene generato automaticamente.

Il calcolo generico su unità di elaborazione grafica ( GPGPU ) è un concetto molto interessante per sfruttare la potenza della GPU per qualsiasi tipo di calcolo.

Mi piacerebbe utilizzare GPGPU per l'elaborazione di immagini, particelle e operazioni geometriche veloci.

In questo momento, sembra che i due contendenti in questo spazio siano CUDA e OpenCL. Mi piacerebbe sapere:

  • OpenCL è ancora utilizzabile da Java su Windows / Mac?
  • Quali sono i modi in cui le librerie possono interfacciarsi con OpenCL / CUDA?
  • Sta usando JNA direttamente un'opzione?
  • Sto dimenticando qualcosa?

Ogni esperienza / esempi / storie di guerra del mondo reale sono apprezzate.







Posso anche raccomandare JOCL di jogamp.org , funziona su Linux, Mac e Windows. CONRAD , ad esempio, utilizza pesantemente OpenCL in combinazione con JOCL.




Seguendo gli ultimi risultati di Google, suppongo che tensorflow sia il miglior approccio al calcolo su GPU, non solo OpenCL. Tensorflow supporta i calcoli OpenCL e CUDA con la stessa API.




Bene CUDA è una modifica di C, per scrivere il kernel CUDA devi codificare in C, e poi compilare in forma eseguibile con il compilatore CUDA di nvidia. Il codice nativo prodotto potrebbe quindi essere collegato a Java utilizzando JNI. Quindi tecnicamente non puoi scrivere il codice del kernel da Java. C'è JCUDA http://www.jcuda.de/jcuda/JCuda.html , ti fornisce le apis di cuda per la memoria generale / gestione dei dispositivi e alcuni metodi Java implementati in CUDA e JNI wrapped (FFT, alcuni metodi di algebra lineare .. ecc. ecc.).

D'altra parte OpenCL è solo un'API. I kernel OpenCL sono semplici stringhe passate all'API, quindi con OpenCL da Java dovresti essere in grado di specificare i tuoi kernel. Il http://www.jocl.org/ OpenCL per java può essere trovato qui http://www.jocl.org/ .




AFAIK, JavaCL / OpenCL4Java è l'unico binding OpenCL disponibile su tutte le piattaforme in questo momento (tra cui MacOS X, FreeBSD, Linux, Windows, Solaris, tutte nelle varianti Intel 32, 64 bit e ppc, grazie al suo uso di JNA ).

Ha demo che in realtà funzionano bene da Java Web Start almeno su Mac e Windows (per evitare crash casuali su Linux, si prega di consultare questa pagina wiki , come ad esempio questa Demo di Particles .

Include anche alcune utilità (generazione di numeri casuali GPGPU, riduzione parallela di base, algebra lineare) e un DSL Scala .

Infine, sono i binding più vecchi disponibili (da giugno 2009) e ha una community di utenti attiva .

(Disclaimer: I'm JavaCL 's author :-))




Ho usato JOCL e ne sono molto felice.

Il principale svantaggio di OpenCL rispetto a CUDA (almeno per me) è la mancanza di librerie disponibili (Thrust, CUDPP, ecc.). Tuttavia CUDA può essere facilmente portato su OpenCL, e osservando come funzionano queste librerie (algoritmi, strategie, ecc.) È davvero molto bello, dato che si impara molto con esso.




Un riferimento è sempre un valore quando rappresentato, indipendentemente dalla lingua che usi.

Ottenendo una vista al di fuori della finestra, diamo un'occhiata all'assemblaggio o ad una gestione della memoria di basso livello. A livello della CPU, un riferimento a qualsiasi cosa diventa immediatamente un valore se viene scritto nella memoria o in uno dei registri della CPU. (Ecco perché il puntatore è una buona definizione: è un valore, che ha uno scopo allo stesso tempo).

I dati in memoria hanno una Location e in quella posizione c'è un valore (byte, word, qualunque). In Assembly abbiamo una comoda soluzione per assegnare un Nome a una determinata posizione (variabile aka), ma quando si compila il codice, l'assemblatore semplicemente sostituisce Nome con la posizione designata, proprio come il browser sostituisce i nomi di dominio con gli indirizzi IP.

Giù fino al midollo è tecnicamente impossibile passare un riferimento a qualcosa in qualsiasi lingua senza rappresentarlo (quando diventa immediatamente un valore).

Diciamo che abbiamo una variabile Foo, la sua posizione è al 47 ° byte in memoria e il suo valore è 5. Abbiamo un'altra variabile Ref2Foo che è a 223rd byte in memoria, e il suo valore sarà 47. Questo Ref2Foo potrebbe essere una variabile tecnica , non creato esplicitamente dal programma. Se guardi solo 5 e 47 senza altre informazioni, vedrai solo due valori . Se li usi come riferimenti, allora per raggiungere 5dobbiamo viaggiare:

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

Ecco come funzionano i jump-tables.

Se vogliamo chiamare un metodo / funzione / procedura con il valore di Foo, ci sono alcuni modi possibili per passare la variabile al metodo, a seconda della lingua e delle sue diverse modalità di chiamata del metodo :

  1. 5 viene copiata in uno dei registri della CPU (ad esempio EAX).
  2. 5 ottiene PUSHd in pila.
  3. 47 viene copiata in uno dei registri della CPU
  4. 47 PUSH in pila.
  5. 223 viene copiata in uno dei registri della CPU.
  6. 223 ottiene PUSHd in pila.

In tutti i casi sopra i quali è stato creato un valore, una copia di un valore esistente, è ora disponibile il metodo di ricezione per gestirlo. Quando scrivi "Foo" all'interno del metodo, viene letto da EAX, o automaticamente dereferenziato , o dereferenziato, il processo dipende da come funziona la lingua e / o da quale tipo di Foo detta. Questo è nascosto allo sviluppatore fino a che non aggira il processo di dereferenziazione. Quindi un riferimento è un valore quando rappresentato, perché un riferimento è un valore che deve essere elaborato (a livello di lingua).

Ora abbiamo passato Foo al metodo:

  • nel caso 1. e 2. se si modifica Foo ( Foo = 9), questo ha effetto solo sull'ambito locale in quanto si ha una copia del Valore. Dall'interno del metodo non siamo nemmeno in grado di determinare in quale posizione si trovasse la Foo originale.
  • nel caso 3. e 4. se si usano costrutti di linguaggio predefiniti e si cambia Foo ( Foo = 11), potrebbe cambiare Foo globalmente (dipende dal linguaggio, ad esempio Java o come Pascal's procedure findMin(x, y, z: integer; var m : integer); ). Tuttavia, se il linguaggio consente di aggirare il processo di dereferenziamento, puoi cambiare 47, dillo a 49. A quel punto Foo sembra essere stato cambiato se lo hai letto, perché hai cambiato il puntatore locale ad esso. E se dovessi modificare questo Foo all'interno del metodo ( Foo = 12) probabilmente FUBAR eseguirai il programma (noto come segfault) perché scriverai su una memoria diversa da quella prevista, puoi anche modificare un'area che è destinata a contenere un eseguibile programma e la scrittura in esso modificherà il codice in esecuzione (Foo non è ora a 47). Ma il valore di Foo di47non è cambiato a livello globale, solo quello all'interno del metodo, perché 47era anche una copia del metodo.
  • nel caso 5. e 6. se si modifica 223all'interno del metodo si crea lo stesso caos come in 3. o 4. (un puntatore, che punta a un valore ora cattivo, che viene nuovamente utilizzato come puntatore) ma questo è ancora un locale problema, come 223 è stato copiato . Tuttavia se sei in grado di dereferenziare Ref2Foo(cioè 223), raggiungere e modificare il valore puntato 47, per esempio, 49influirà su Foo a livello globale , perché in questo caso i metodi hanno una copia di 223ma il riferimento 47esiste solo una volta e cambiando quello al 49porterà ogni Ref2Foodoppio dereferenziazione ad un valore errato.

Facendo clic su dettagli insignificanti, anche i linguaggi che passano per riferimento passano i valori alle funzioni, ma tali funzioni sanno che devono usarli per scopi di dereferenziazione. Questo pass-the-reference-as-value è appena nascosto dal programmatore perché è praticamente inutile e la terminologia è solo pass-by-reference .

Anche il pass-by-value rigoroso è inutile, significherebbe che un array da 100 Mbyte dovrebbe essere copiato ogni volta che chiamiamo un metodo con l'array come argomento, quindi Java non può essere strettamente pass-by-value. Ogni linguaggio passerebbe un riferimento a questo enorme array (come valore) e utilizza il meccanismo copy-on-write se tale array può essere modificato localmente all'interno del metodo o consente al metodo (come Java) di modificare l'array globalmente (da vista del chiamante) e alcune lingue consente di modificare il valore del riferimento stesso.

Quindi, in breve, e nella terminologia propria di Java, Java è un valore pass-by dove il valore può essere: un valore reale o un valore che è una rappresentazione di un riferimento .





java cuda gpgpu opencl