Ouep, enculage de mouches, toussatoussa.
J'avais commencé à me lancer dans des explications compliquées avec plein de code assembleur, mais je me suis embrouillé tout seul. La vectorisation statique de code séquentiel, de toute façon ça marche pas, la preuve j'y arrive pas.
Donc je vais plutôt prendre un exemple que je connais mieux. Suppose que tu dois compiler les codes pseudo-OpenCL suivants pour Xeon Phi. (Si tu n'aimes pas OpenCL, tu peux réécrire ça en CUDA, en GLSL ou en ISPC).
Tu as droit à tout le jeu d'instructions de Knights Corner, en particulier les instructions SIMD prédiquées par des registres de masque et les comparaisons SIMD qui écrivent dans lesdits registres.
Indice : ils sont classés par ordre de difficulté.Code:device bool saxpy_var(float * x, float a, float const * y, int n) { size_t tid = get_local_id(0); size_t tcnt = get_local_size(0); bool error = false; for(int i = tid; i < n && !error; i += tcnt) { x[i] = a * x[i] + y[i]; if(isnan(x[i]) { error = true; } } return error; } device bool saxpy_ret(float * x, float a, float const * y, int n) { size_t tid = get_local_id(0); size_t tcnt = get_local_size(0); for(int i = tid; i < n; i += tcnt) { x[i] = a * x[i] + y[i]; if(isnan(x[i]) { return true; } } return false; } global main_kernel_ret(float * x, float a, float const * y, int n) { if(saxpy_ret(x, a, y, n)) printf("Große Malheur!"); } device void saxpy_except(float * x, float a, float const * y, int n) { size_t tid = get_local_id(0); size_t tcnt = get_local_size(0); for(int i = tid; i < n; i += tcnt) { x[i] = a * x[i] + y[i]; if(isnan(x[i]) { throw 42; } } } global main_kernel_except(float * x, float a, float const * y, int n) { try { saxpy_except(x, a, y, n)) } catch(...) { printf("Große Malheur!"); } }
Dans mon projet de DF-like, j'ai commencé par faire des calculs de matrice pas optimisés du tout, avec deux boucles for et tout le bordel.
Puis j'ai remplacé tout ça par de l'assembleur inline écrit à la main et qui utilise le SIMD.
Je ne me souviens plus exactement du résultat, mais ça allait quelque chose comme dix fois plus vite.
Donc enculage de mouche... ou pas.
Rust fanboy
Je suis censé faire quelque chose avec ça ?
Non parce que je ne saurais pas vraiment quoi dire à ce sujet. Simplement que pour moi je dirais que ce n'est pas la manière dont l'erreur est retournée qui ferait merdoyer la vectorisation mais le check "isnan(x[i])". Parce que tu checkes les valeurs une par une, et que ça doit pas bien se vectoriser sur 4 floats d'un coup.
Dans les trois cas, la découverte d'une isnan entraîne aussi l'arrêt de l'itération. Hors tu n'as aucun moyen de savoir sur quelle valeur tu étais censé t'arrêter avant d'avoir fait la multiplication, ça peut très bien être au milieu d'un pack de floats que tu viens juste de multiplier d'un coup. Et je suppose aussi que t'es censé retourner un résultat à peu près exact, c'est à dire que si tu trouves un isnan dans le premier résultat d'un groupe de 4 floats vectorisés, t'es pas censé avoir multiplié les trois autres... parce que t'es allé trop vite, ni le reste des valeurs si tu t'amuses à le faire dans le désordre.
Qu'est ce que le compilateur est censé faire du coup ? Vectoriser sur le plus grand nombre d'éléments possibles, puis vérifier chaque résultat et si tu t'aperçois que t'en a trop fait, recommencer le calcul sur juste ce qu'il fallait et retourner le résultat ? Dans ce cas, en quoi est-ce différent quelque soit la manière dont l'erreur est retournée ?
---------- Post added at 22h06 ---------- Previous post was at 21h58 ----------
Le topic des bébés c'est là.
"Dieu est mort" · "Si le téléchargement c’est du vol, Linux c’est de la prostitution."
Pourquoi pas ? Tu checkes toutes les valeurs en même temps, tu récupères un vecteur de booléens, que tu peux utiliser comme masque pour prédiquer l'exécution des instructions suivantes. Tu peux aussi tester si tous les bits du masque sont à 0 ou tous à 1.
C'est exactement pour ça que j'ai pris un exemple en OpenCL. Sinon c'est vite prise de tête. Ici, tu t'en fous de l'ordre, tu vectorises entre des threads différents, et l'ordre d'exécution entre les threads n'est pas spécifié.Dans les trois cas, la découverte d'une isnan entraîne aussi l'arrêt de l'itération. Hors tu n'as aucun moyen de savoir sur quelle valeur tu étais censé t'arrêter avant d'avoir fait la multiplication, ça peut très bien être au milieu d'un pack de floats que tu viens juste de multiplier d'un coup. Et je suppose aussi que t'es censé retourner un résultat à peu près exact, c'est à dire que si tu trouves un isnan dans le premier résultat d'un groupe de 4 floats vectorisés, t'es pas censé avoir multiplié les trois autres... parce que t'es allé trop vite, ni le reste des valeurs si tu t'amuses à le faire dans le désordre.
Non, pas besoin de trucs compliqués comme retourner en arrière. C'est juste un exercice pour la gestion de flot de contrôle en SIMD.Qu'est ce que le compilateur est censé faire du coup ? Vectoriser sur le plus grand nombre d'éléments possibles, puis vérifier chaque résultat et si tu t'aperçois que t'en a trop fait, recommencer le calcul sur juste ce qu'il fallait et retourner le résultat ? Dans ce cas, en quoi est-ce différent quelque soit la manière dont l'erreur est retournée ?
Pour le saxpy sans le check, ça donne par exemple :
Toutes les variables sont des vecteurs. k est un vecteur de booléens, ce qui revient à un int16. Les masques de prédication sont entre parenthèses.Code:vmov I, Tid ; I[j] = Tid[j] forall j mov k, 0xffff ; k[j] = 1 forall j beginfor: (k) vcmp.lt k, I, n ; k[j] = (I[j] < n) when k[j] jmp.none k, endfor ; if(k[j]=0 forall j)... (k) vgather Xt, x[I] ; xt[j] = x[I[j]] when k[j] (k) vgather Yt, y[I] (k) vfma Xt, Xt, A, Yt (k) vscatter x[i], Xt (k) vadd I, I, Tnum jmp beginfor endfor: ret
Le code n'est pas optimisé du tout. Le compilo est un peu con, il n'a pas vu qu'on accédait à des éléments adjacents donc il émet des gather/scatter là où des load/store de vecteurs suffiraient.
Après ça j'ai décroché.Pour le saxpy sans le check, ça donne par exemple :
Sinon, ton histoire de vecteur de booléen, je ne vois pas bien comment, c'est le résultat des multiplications que tu vérifies, donc tu vas te taper toutes les opérations et vérifier ensuite.
Et je persiste sur l'histoire du check. Si ta condition te sert à arrêter l'itération, comme c'est le cas ici, tu n'as pas le droit de multiplier plus de valeurs que ce qui est écrit, sinon c'est que la logique compilée n'est pas la même que la logique du programme.
Si tu t'en branles, c'est que ton check n'est pas important, et autant ne pas le faire, ou le faire après (ou avant, comme je le faisais dans des boucles supplémentaires).
Sinon, pour rigoler un coup, j'ai fait un truc totalement horrible... Tellement que je le met en spoiler tiens.
Faut pas faire ça chez vous les enfants, mais c'est assez mortel et tu ne pourras pas venir me dire que mon exception viens perturber la vectorisation de la boucle :
Testé et certifié avec gcc -O3 -fnon-call-exceptionsCode:Spoiler Alert!
Edit: En fait c'est -fnon-call-exceptions qu'il lui faut pour être content avec ce code (plus besoin de wrapper ou de désactiver les inline).
Dernière modification par rOut ; 23/11/2012 à 00h40.
"Dieu est mort" · "Si le téléchargement c’est du vol, Linux c’est de la prostitution."
On ne vectorise pas la boucle for. On forme des vecteurs entre différents work-items OpenCL (j'ai qualifié de global la fonction principale, je voulais dire kernel, c'est l'effet CUDA). Comme le ferait un GPU, ou une vraie archi SIMD en forme d'armoire avec des leds qui clignotent (Connection Machine, Maspar, etc.). On peut vectoriser de cette façon même s'il n'y a pas de boucle du tout. Tu peux prendre un shader comme exemple.
Ici, seuls les work-items qui ont rencontré une erreur doivent s'arrêter tout de suite, les autres doivent continuer jusqu'à la fin comme si de rien n'était.
C'est même pas vraiment horrible. Tu peux même faire plus simple :Sinon, pour rigoler un coup, j'ai fait un truc totalement horrible... Tellement que je le met en spoiler tiens.
Faut pas faire ça chez vous les enfants, mais c'est assez mortel et tu ne pourras pas venir me dire que mon exception viens perturber la vectorisation de la boucle :
Mais ça ne tournera pas sous Xeon Phi. Il ne gère pas les exceptions FP.Code:#include <cstdio> #include <cmath> #include <cfloat> #include <iostream> #include <exception> #include <stdexcept> #include <signal.h> #include <fenv.h> void sigfpe_handler(int sig) { throw std::runtime_error("hop hop hop on s'arrete la monsieur."); } void saxpy_c(float* __restrict__ x, float a, float* __restrict__ y, int n) { for (int i = 0; i != n; i++) { x[i] = a * x[i] + y[i]; } } int main(int argc, char const* argv[]) { signal(SIGFPE, &sigfpe_handler); feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW); float x[] = { FLT_MAX* FLT_MAX }; float y[] = { 0.0 }; float a = 0; float n = sizeof(x) / sizeof(float); try { saxpy_c(x, a, y, n); } catch (std::exception const& e) { std::cout << "haha, dans ton cul le signal: " << e.what() << std::endl; } return static_cast< int >(x[0]); }
Et si tu compiles en x87, il faut rajouter une instruction nop flottante à la fin, parce que les exceptions sont envoyées lors de l'exécution de l'instruction flottante suivante (une histoire de câblage de copro externe dans les années 80).
Tu peux passer par un système de "clef" également :
Viande = 1
Feu = 2
Viande + Feu = 3
Viande = 1
Feu = 2
Viande cuite = 3
Barre de fer = 4
Charbon = 5
Epée = 9
Donc :
Viande cuite + viande cuite + viande cuite = Epée
Viande cuite + viande = Barre de fer
Viande x5 = charbon
Rust fanboy
Non ça ne l'est pas vraiment en effet : cf section 18:10 du draft ou bien la doc de Sun Studio C++
ou cette note pour HP aC++.
Faut utiliser longjmp, C rules
Quel mauvais esprit, on peut employer des puissances de 2 pour les éléments de base et les combiner avec des OR, comme dans un bitfield (tant qu'on n'a pas besoin d'utiliser plusieurs instances du même objet).
Comme ça on pourra avoir :
Viande + Feu = Viande cuite
Barre de fer + Charbon + Feu = Epée
Epée + Viande - (Barre de fer + Charbon) = Viande cuite
Après ça limite à 64 le nombre de composants de base. :cherche-un-reproche:
Plus sérieusement ça peut pose d'autres problèmes. Par exemple :
Viande = 1
Pomme de terre = 2
Feu = 4
Viande cuite = 5
Pomme de terre cuite = 6
Menu = 7
Le truc sympa c'est que tu peux faire directement :
Viande + Pomme de terre + Feu = Menu
Par contre, ça ça devrait pas être autorisé :
Viande cuite + Pomme de terre = Menu (puisque la pomme de terre n'est pas cuite)
Ou au contraire s'il veut interdire le "Viande + Pomme de terre + Feu", c'est à dire obliger le joueur à cuire séparément, ben il peut pas.
En fait ta méthode c'est une méthode "développeur de jeu des années 80-90", où tu utilisais des systèmes ingénieux et super rapides, mais qui t'obligeaient à adapter tout ton game design à ces limitations.
Rust fanboy
Il y a des moyens sympathiques d'avoir des bitfields sans gaspiller de mémoire et avec des noms sympas ? Parce que je suppose que les enums sont limités à 64 bits. Donc à 64 objet élémentaires.
---------- Post added at 15h58 ---------- Previous post was at 15h57 ----------
Ou alors tu associes à chaque composant de base un nombre premier, et ensuite il suffit de faire la décomposition en nombres premiers pour avoir la composition du craft !
"Dieu est mort" · "Si le téléchargement c’est du vol, Linux c’est de la prostitution."
En C++ t'as le controversé std::vector<bool> qui est un bitfield de taille "illimitée" (c'est à dire tout ce que peut contenir la mémoire quoi).
Il est controversé parce que c'est une spécialisation de std::vector. Beaucoup de gens pensent qu'ils auraient dû laisser std::vector<bool> comme un std::vector normal, et nommer le bitfield autrement.
Par contre la classe en elle même n'a aucun problème, tu peux parfaitement l'utiliser.
Par exemple tu créé un enum tout simple de tes objets (avec des valeurs 0, 1, 2, 3, etc.) puis tu écris "bitfield[ITEMS_MEAT] = 0;" et ça mettra le bit correspondant à 0, sans toucher aux autres.
Rust fanboy
Sinon il y a un truc intéressant, mais plus compliqué.
http://www.wutka.com/dawg.html
Mais ça permet de trouver facilement toutes les recettes possibles qui contiennent les ingrédients proposé par le joueur.
Anything that can go wrong will go wrong.
Rust fanboy
En fait ton article parle de "DAWG", d'où mon image du mème "yo dawg" :j'explique-mes-blagues:
Rust fanboy
Encore un problème avec un exo du cours (on ne se moque pas ). Un convertisseur de chiffre romain :
Je bloque sur la ligne : " for k in range(len(R) - 1) : ", ne comprenant pas pourquoi il est nécessaire de retirer un puis de le rajouter plus loin dans le code. Le prof explique ça comme ça :Code:def RomDec(R) : conv = {'C': 100, 'D': 500, 'I': 1, 'M': 1000, 'L': 50, 'V': 5, 'X': 10} R = [conv[x] for x in R.upper()] N = 0 for k in range(len(R) - 1) : if R[k] < R[k + 1] : N -= R[k] else : N += R[k] N += R[-1] return N
Ce qui ne m'aide pas des masses, et python me retourne un list index out of range si je teste sans enlever 1 à len(R).L'expression du prédicat Ri < Ri+1 empêche de coder ce traitement avec un simple for x in R, car alors il ne
serait pas possible d'accéder au x suivant...
En fait, le code dans la boucle traite les éléments 2 par 2, en comparant chaque élément (à l'indice k) à celui qui le suit (k + 1), pour établir si sa valeur doit être ajoutée ou retranchée du total, ce qui est le principe de composition des nombres romains. Si tu ne fais pas s'arrêter la boucle à l'avant-dernier élément, l'indice k + 1 dépassera la limite du tableau quand la boucle arrivera sur le dernier élément.
Le N += R[-1] permet d'ajouter le dernier élément, celui qui n'est justement pas traité par la boucle (et permet aussi de gérer les nombres romains composés d'une seule lettre par la même occasion, pusqu'ils ne passent pas par la boucle).
A chaque itération, tu veux regarder si l'élément courant est plus grand ou plus petit que celui qui suit, pour savoir si tu dois l'ajouter ou le retirer de la somme totale.
Par exemple: CIX, tu dois ajouter C (C > I), puis retirer I (I < X), puis ajouter X.
Ou encore: CXI, tu dois ajouter C (C > I), puis ajouter X (X > I), puis ajouter I.
Tu dois donc avoir un autre élément après l'élément courant pour pouvoir faire la comparaison. Ce qui n'est pas le cas quand tu as atteint de le dernier élément. Il faut donc le traiter à part, à la fin et en l'ajoutant systématiquement à la somme.
Dans le cas d'un nombre à un seul chiffre, il n'y a pas de comparaison à faire, la somme est égale au chiffre.
Une manière de faire en trichant un peu serait d'ajouter un 0 à la fin de la liste, de manière à aider l'algorithme.
Et plus simplement en une ligne :
Code:RomDec = lambda R: sum([ n > m and n or -n for c in [{'C': 100, 'D': 500, 'I': 1, 'M': 1000, 'L': 50, 'V': 5, 'X': 10, '0': 0}] for n,m in [ (c[r], c[s]) for r,s in zip(list(R), list(R)[1:] + ['0']) ] ])
"Dieu est mort" · "Si le téléchargement c’est du vol, Linux c’est de la prostitution."