Je travaille sur un projet d'entreprise réalisé en Java et qui nécessite une énorme puissance de calcul pour calculer les marchés commerciaux. Mathématiques simples mais avec une énorme quantité de données.
Nous avons commandé quelques cuda gpu pour l'essayer avec et puisque Java n'est pas pris en charge par cuda, je me demande par où commencer. Devrais-je construire une interface JNI? Devrais-je utiliser JCUDA ou existe-t-il d'autres moyens?
Je n'ai pas d'expérience dans ce domaine et j'aimerais que quelqu'un me dirige vers quelque chose afin que je puisse commencer à faire de la recherche et à apprendre.
Tout d'abord, vous devez être conscient du fait que CUDA n'effectuera pas automatiquement les calculs plus rapidement. D'une part, parce que la programmation GPU est un art et qu'il peut être très, très difficile de la maîtriser . D'autre part, les GPU ne conviennent que pour certains types de calculs.
Cela peut paraître déroutant, car vous pouvez en principe calculer n'importe quoi sur le GPU. Le point essentiel est bien entendu de savoir si vous allez réaliser une bonne accélération ou non. La classification la plus importante ici est de savoir si un problème est tâche parallèle ou données parallèle . La première concerne grossièrement les problèmes où plusieurs threads travaillent sur leurs propres tâches, plus ou moins indépendamment. La seconde concerne les problèmes où de nombreux threads font tous la même chose - mais sur des parties différentes des données.
Ce dernier problème est le type de problème pour lequel les GPU sont bons: ils ont beaucoup de cœurs , et tous les cœurs font la même chose, mais fonctionnent sur différentes parties des données d’entrée.
Vous avez mentionné que vous aviez "des calculs simples mais avec une énorme quantité de données". Bien que cela puisse sembler être un problème parfaitement parallèle aux données et donc convenir à un GPU, il convient également de prendre en compte un autre aspect: les GPU sont ridiculement rapides en termes de puissance de calcul théorique (FLOPS, Opérations en virgule flottante par seconde). Mais ils sont souvent limités par la bande passante de la mémoire.
Cela conduit à une autre classification des problèmes. Notamment si les problèmes sont liés à la mémoire ou calculés liés .
Le premier concerne les problèmes pour lesquels le nombre d'instructions effectuées pour chaque élément de données est faible. Par exemple, considérons une addition de vecteur parallèle: vous devrez lire deux éléments de données, puis effectuer une seule addition, puis écrire la somme dans le vecteur de résultat. Cela ne se produira pas plus rapidement sur le GPU, car l’ajout unique ne compense pas les efforts de lecture/écriture de la mémoire.
Le deuxième terme, "Compute Bound", fait référence à des problèmes où le nombre d'instructions est élevé par rapport au nombre de lectures/écritures en mémoire. Par exemple, considérons une multiplication de matrice: le nombre d'instructions sera O (n ^ 3) lorsque n est la taille de la matrice. Dans ce cas, on peut s’attendre à ce que le processeur graphique surpasse un processeur avec une certaine taille de matrice. Un autre exemple pourrait être lorsque de nombreux calculs trigonométriques complexes (sinus/cosinus, etc.) sont effectués sur "quelques" éléments de données.
En règle générale: vous pouvez supposer que la lecture/écriture d'un élément de données de la mémoire GPU "principale" a une latence d'environ 500 instructions ....
Par conséquent, un autre point clé pour les performances des GPU est localité de données : Si vous devez lire ou écrire des données (et dans la plupart des cas, vous devrez ;-)), alors vous devez vous assurer que les données sont conservées aussi près que possible des cœurs du processeur graphique. Les GPU ont ainsi certaines zones de mémoire (appelées "mémoire locale" ou "mémoire partagée") qui n’ont généralement que quelques Ko de taille, mais sont particulièrement efficaces pour les données sur le point d’être impliquées dans un calcul.
Donc, soulignons encore ceci: la programmation GPU est un art, qui n’est lié qu’à distance à la programmation parallèle sur le CPU. Des éléments tels que Threads in Java, avec toute l’infrastructure de concurrence telle que ThreadPoolExecutors
, ForkJoinPools
etc., peuvent donner l’impression que vous devez diviser votre travail d’une manière ou d’une autre et le répartir entre plusieurs processeurs. Sur le processeur graphique, vous rencontrerez peut-être des difficultés à un niveau beaucoup plus bas: occupation, pression des registres, pression de la mémoire partagée, coalescence de la mémoire, pour n'en citer que quelques-uns.
Toutefois, lorsque vous devez résoudre un problème lié au calcul parallèle aux données, le GPU est la solution.
Une remarque générale: vous avez spécifiquement demandé CUDA. Mais je vous recommande fortement de jeter également un coup d’œil à OpenCL. Il a plusieurs avantages. Tout d’abord, il s’agit d’un standard de l’industrie ouvert et indépendant du vendeur. Il existe des implémentations d’OpenCL par AMD, Apple, Intel et NVIDIA. En outre, OpenCL prend en charge un support beaucoup plus large dans le monde Java. Le seul cas où je préférerais utiliser CUDA est lorsque vous souhaitez utiliser les bibliothèques d'exécution CUDA, telles que CUFFT pour FFT ou CUBLAS pour BLAS (opérations Matrix/Vector). Bien qu'il existe des approches pour fournir des bibliothèques similaires pour OpenCL, elles ne peuvent pas être directement utilisées depuis le côté Java, à moins que vous ne créiez vos propres liaisons JNI pour ces bibliothèques.
Vous pourriez également trouver intéressant d'entendre qu'en octobre 2012, le groupe OpenJDK HotSpot a lancé le projet "Sumatra": http://openjdk.Java.net/projects/sumatra/ . L'objectif de ce projet est de fournir un support GPU directement dans la machine virtuelle, avec le support du JIT. L'état actuel et les premiers résultats sont visibles dans leur liste de diffusion à l'adresse suivante: http://mail.openjdk.Java.net/mailman/listinfo/sumatra-dev
Cependant, il y a quelque temps, j'ai collecté certaines ressources liées à "Java sur le GPU" en général. Je vais les résumer à nouveau ici, sans ordre particulier.
( Disclaimer : je suis l'auteur de http://jcuda.org/ et http://jocl.org/ )
https://github.com/aparapi/aparapi : une bibliothèque à code source ouvert créée et maintenue activement par AMD. Dans une classe spéciale "Kernel", il est possible de remplacer une méthode spécifique qui doit être exécutée en parallèle. Le code d'octet de cette méthode est chargé au moment de l'exécution à l'aide d'un propre lecteur de bytecode. Le code est traduit en code OpenCL, qui est ensuite compilé à l'aide du compilateur OpenCL. Le résultat peut ensuite être exécuté sur le périphérique OpenCL, qui peut être un GPU ou une CPU. Si la compilation dans OpenCL n'est pas possible (ou si aucune OpenCL n'est disponible), le code sera toujours exécuté en parallèle, à l'aide d'un pool de threads.
https://github.com/pcpratts/rootbeer1 : Une bibliothèque à code source ouvert permettant de convertir des parties de Java en programmes CUDA. Il offre des interfaces dédiées pouvant être implémentées pour indiquer qu’une certaine classe doit être exécutée sur le GPU. Contrairement à Aparapi, il tente de sérialiser automatiquement les données "pertinentes" (c'est-à-dire la partie pertinente complète du graphe d'objets!) Dans une représentation adaptée au GPU.
https://code.google.com/archive/p/Java-gpu/ : une bibliothèque permettant de traduire annotated Java code (avec certaines limitations) en code CUDA, qui est puis compilé dans une bibliothèque qui exécute le code sur le GPU. La bibliothèque a été développée dans le cadre d'une thèse de doctorat, qui contient des informations de base sur le processus de traduction.
https://github.com/ochafik/ScalaCL : Scala liaisons pour OpenCL. Permet à special Scala des collections d'être traitées en parallèle avec OpenCL. Les fonctions appelées sur les éléments des collections peuvent être habituelles Scala, fonctions (avec certaines limitations), qui sont ensuite traduites en noyaux OpenCL.
http://www.ateji.com/px/index.html : Une extension de langage pour Java qui autorise les constructions parallèles (par exemple, les boucles parallèles pour le style OpenMP) qui sont alors exécuté sur le GPU avec OpenCL. Malheureusement, ce projet très prometteur n'est plus maintenu.
http://www.habanero.rice.edu/Publications.html (JCUDA): une bibliothèque capable de traduire special Java Code (appelé code JCUDA) en Java et CUDA. -C du code, qui peut ensuite être compilé et exécuté sur le GPU. Cependant, la bibliothèque ne semble pas être accessible au public.
https://www2.informatik.uni-erlangen.de/EN/research/JavaOpenMP/index.html : Java extension de langage pour les constructions OpenMP, avec un backend CUDA
https://github.com/ochafik/JavaCL : Java liaisons pour OpenCL: une bibliothèque OpenCL orientée objet, basée sur des liaisons de bas niveau générées automatiquement.
http://jogamp.org/jocl/www/ : Java liaisons pour OpenCL: une bibliothèque OpenCL orientée objet, basée sur des liaisons de bas niveau générées automatiquement.
http://www.lwjgl.org/ : Java liaisons pour OpenCL: liaisons de bas niveau générées automatiquement et classes de confort orientées objet.
http://jocl.org/ : Java liaisons pour OpenCL: liaisons de bas niveau correspondant à un mappage 1: 1 de l'API OpenCL d'origine
http://jcuda.org/ : Java liaisons pour CUDA: liaisons de bas niveau correspondant à un mappage 1: 1 de l'API CUDA d'origine
http://sourceforge.net/projects/jopencl/ : Java liaisons pour OpenCL. Semble ne plus être maintenu depuis 2010
http://www.hoopoe-cloud.com/ : Java liaisons pour CUDA. Semble ne plus être maintenu
Je commencerais par utiliser l'un des projets disponibles pour Java et CUDA: http://www.jcuda.org/
D'après les recherches que j'ai effectuées, si vous ciblez les GPU Nvidia et avez décidé d'utiliser Cuda plutôt que openCL, j'ai trouvé trois façons d'utiliser l'API Cuda en Java.
Toutes ces réponses sont essentiellement des moyens d'utiliser le code c/c ++ en Java. Vous devriez vous demander pourquoi vous devez utiliser Java et si vous ne pouvez pas le faire dans c/c ++ à la place.
Si vous aimez Java et savez comment l'utiliser, vous ne voulez pas travailler avec toute la gestion des pointeurs et ce qui ne vient pas avec c/c ++, alors JCuda est probablement la solution. D'autre part, la bibliothèque Cuda Thrust et d'autres bibliothèques similaires peuvent être utilisées pour effectuer une grande partie de la gestion des pointeurs dans c/c ++ et vous devriez peut-être examiner cela.
Si vous aimez c/c ++ et que vous n’êtes pas préoccupé par la gestion des pointeurs mais que d’autres contraintes vous obligent à utiliser Java, alors JNI pourrait être la meilleure approche. Cependant, si vos méthodes JNI vont juste être des wrappers pour les commandes du noyau, vous pouvez aussi bien utiliser JCuda.
Il existe quelques alternatives à JCuda telles que Cuda4J et Root Beer, mais celles-ci ne semblent pas être maintenues. Alors qu'au moment de la rédaction de ce document, JCuda soutient Cuda 10.1. qui est le plus récent Cuda sdk.
En outre, quelques Java bibliothèques utilisant cuda, telles que deeplearning4j et Hadoop, peuvent être en mesure de faire ce que vous recherchez sans vous obliger à écrire directement le code du noyau. Cependant, je ne les ai pas trop examinées.