Coût de maintenance de la base de code de programmation SIMD

14

Question:

Le consensus de l'industrie du logiciel est qu'un code propre et simple est fondamental pour la viabilité à long terme de la base de code et de l'organisation qui en est propriétaire. Ces propriétés permettent de réduire les coûts de maintenance et d'augmenter la probabilité de poursuite de la base de code.

Cependant, le code SIMD est différent du code d'application général, et j'aimerais savoir s'il existe un consensus similaire concernant un code propre et simple s'appliquant spécifiquement au code SIMD.


Contexte de ma question.

J'écris beaucoup de code SIMD (instruction unique, données multiples) pour diverses tâches de traitement et d'analyse d'images. Récemment, j'ai également dû porter un petit nombre de ces fonctions d'une architecture (SSE2) à une autre (ARM NEON).

Le code est écrit pour un logiciel sous emballage, il ne peut donc pas dépendre de langages propriétaires sans droits de redistribution illimités tels que MATLAB.

Un exemple de structure de code typique:

  • Utilisation du type de matrice d' OpenCV ( Mat) pour toute la gestion de la mémoire, du tampon et de la durée de vie.
  • Après avoir vérifié la taille (dimensions) des arguments d'entrée, les pointeurs vers l'adresse de début de chaque ligne de pixels sont pris.
  • Le nombre de pixels et les adresses de début de chaque ligne de pixels de chaque matrice d'entrée sont passés dans certaines fonctions C ++ de bas niveau.
  • Ces fonctions C ++ de bas niveau utilisent les intrinsèques SIMD (pour l' architecture Intel et ARM NEON ), en chargeant et en sauvegardant les adresses de pointeurs brutes.
  • Caractéristiques de ces fonctions C ++ de bas niveau:
    • Exclusivement unidimensionnel (consécutif en mémoire)
    • Ne traite pas des allocations de mémoire.
      (Chaque allocation, y compris les temporaires, est gérée par le code externe à l'aide des fonctionnalités OpenCV.)
    • La plage de longueurs de noms des symboles (intrinsèques, noms de variables, etc.) est d'environ 10 à 20 caractères, ce qui est assez excessif.
      (Se lit comme un babillage techno.)
    • La réutilisation des variables SIMD est déconseillée car les compilateurs sont assez bogués dans l'analyse correcte du code qui n'est pas écrit dans le style de codage "à affectation unique".
      (J'ai déposé plusieurs rapports de bogues du compilateur.)

Quels aspects de la programmation SIMD feraient en sorte que la discussion diffère du cas général? Ou, pourquoi SIMD est-il différent?

En termes de coût de développement initial

  • Il est bien connu que le coût de développement initial du code SIMD C ++ avec de bonnes performances est d'environ 10x - 100x (avec une large marge) par rapport au code C ++ écrit de manière non conventionnelle.
  • Comme indiqué dans les réponses à Choisir entre performance et code lisible / plus propre? , la plupart du code (y compris le code écrit par hasard et le code SIMD) n'est initialement ni propre ni rapide .
  • Les améliorations évolutives des performances du code (en code scalaire et SIMD) sont déconseillées (car elles sont considérées comme une sorte de retouche logicielle ), et le coût et les avantages ne sont pas suivis.

En termes de propension
(par exemple le principe de Pareto, alias la règle 80-20 )

  • Même si le traitement d'image ne comprend que 20% d'un système logiciel (en termes de taille de code et de fonctionnalité), le traitement d'image est relativement lent (lorsqu'il est considéré comme un pourcentage du temps CPU passé), prenant plus de 80% du temps.
    • Cela est dû à l'effet de la taille des données: une taille d'image typique est mesurée en mégaoctets, tandis que la taille typique des données non-image est mesurée en kilo-octets.
  • Dans le code de traitement d'image, un programmeur SIMD est formé pour reconnaître automatiquement le code à 20% comprenant les points chauds en identifiant la structure de boucle dans le code C ++. Ainsi, du point de vue d'un programmeur SIMD, 100% du "code qui compte" est un goulot d'étranglement des performances.
  • Souvent dans un système de traitement d'image, plusieurs points d'accès existent et occupent des proportions comparables de temps. Par exemple, il peut y avoir 5 points d'accès occupant chacun (20%, 18%, 16%, 14%, 12%) du temps total. Pour obtenir un gain de performances élevé, tous les hotspots doivent être réécrits dans SIMD.
    • Cela se résume comme la règle du saut de ballon: un ballon ne peut pas être sauté deux fois.
    • Supposons qu'il y ait des ballons, disons 5 d'entre eux. La seule façon de les décimer est de les faire éclater un par un.
    • Une fois le premier ballon sauté, les 4 ballons restants représentent désormais un pourcentage plus élevé du temps total d'exécution.
    • Pour faire de nouveaux gains, il faut alors faire éclater un autre ballon.
      (Cela va à l'encontre de la règle d'optimisation 80-20: un bon résultat économique peut être obtenu après la cueillette des 20% de fruits les plus bas.)

En termes de lisibilité et de maintenance

  • Le code SIMD est manifestement difficile à lire.

    • Cela est vrai même si l'on suit toutes les meilleures pratiques d'ingénierie logicielle, par exemple le nommage, l'encapsulation, la constance (et la mise en évidence des effets secondaires), la décomposition des fonctions, etc.
    • Cela est vrai même pour les programmeurs SIMD expérimentés.
  • Le code SIMD optimal est très tordu (voir remarque) par rapport à son code prototype C ++ équivalent.

    • Il existe de nombreuses façons de déformer le code SIMD, mais seulement 1 tentative sur 10 permettra d'obtenir des résultats rapidement acceptables.
    • (C'est-à-dire dans les airs de gains de performances 4x-10x afin de justifier un coût de développement élevé. Des gains encore plus élevés ont été observés dans la pratique.)

(Remarque)
Il s'agit de la thèse principale du projet MIT Halide - citant le titre de l'article textuellement:

"découplage des algorithmes des plannings pour une optimisation aisée des pipelines de traitement d'image"

En termes d'applicabilité directe

  • Le code SIMD est strictement lié à une architecture unique. Chaque nouvelle architecture (ou chaque élargissement des registres SIMD) nécessite une réécriture.
  • Contrairement à la majorité du développement logiciel, chaque morceau de code SIMD est généralement écrit dans un seul but qui ne change jamais.
    (À l'exception du portage vers d'autres architectures.)
  • Certaines architectures conservent une parfaite compatibilité descendante (Intel); certains échouent par une quantité insignifiante (ARM AArch64, remplaçant vtblpar vtblq) mais qui est suffisant pour empêcher la compilation de certains codes.

En termes de compétences et de formation

  • Les connaissances préalables requises pour former correctement un nouveau programmeur à l'écriture et à la maintenance du code SIMD ne sont pas claires.
  • Les diplômés du collégial qui ont appris les programmes SIMD à l'école semblent le mépriser et le rejeter comme une carrière peu pratique.
  • Le démontage-lecture et le profilage de performances de bas niveau sont cités comme deux compétences fondamentales pour écrire du code SIMD hautes performances. Cependant, on ne sait pas comment former systématiquement les programmeurs à ces deux compétences.
  • L'architecture CPU moderne (qui diffère considérablement de ce qui est enseigné dans les manuels) rend la formation encore plus difficile.

En termes d'exactitude et de coûts liés aux défauts

  • Une seule fonction de traitement SIMD est en fait suffisamment cohérente pour que l'on puisse établir l'exactitude en:
    • Appliquer des méthodes formelles (avec stylo et papier) , et
    • Vérification des plages d'entiers en sortie (avec le code prototype et effectuées en dehors de l'exécution) .
  • Le processus de vérification est cependant très coûteux (consacre 100% de temps à l'examen du code et 100% de temps à la vérification du modèle de prototype), ce qui triple le coût de développement déjà coûteux du code SIMD.
  • Si un bogue parvient d'une manière ou d'une autre à passer à travers ce processus de vérification, il est presque impossible de "réparer" (corriger) sauf pour remplacer (réécrire) la fonction défectueuse suspectée.
  • Le code SIMD souffre des défauts évidents du compilateur C ++ (optimisation du générateur de code).
    • Le code SIMD généré à l'aide de modèles d'expression C ++ souffre également grandement des défauts du compilateur.

En termes d'innovations disruptives

  • De nombreuses solutions ont été proposées par les universités, mais peu connaissent une utilisation commerciale généralisée.

    • MIT Halide
    • Stanford Darkroom
    • NT2 (Numerical Template Toolbox) et le Boost.SIMD associé
  • Les bibliothèques à usage commercial répandu ne semblent pas être fortement compatibles SIMD.

    • Les bibliothèques open source semblent tièdes pour SIMD.
      • Récemment, j'ai cette observation de première main après avoir profilé un grand nombre de fonctions API OpenCV, à partir de la version 2.4.9.
      • De nombreuses autres bibliothèques de traitement d'images que j'ai présentées ne font pas non plus un usage intensif de SIMD, ou manquent les véritables hotspots.
    • Les bibliothèques commerciales semblent éviter complètement SIMD.
      • Dans certains cas, j'ai même vu des bibliothèques de traitement d'image rétablir le code optimisé SIMD dans une version antérieure en code non SIMD dans une version ultérieure, entraînant de graves régressions de performances.
        (La réponse du vendeur est qu'il était nécessaire d'éviter les bogues du compilateur.)

La question de ce programmeur: le code à faible latence doit-il parfois être "moche"? est lié, et j'ai déjà écrit une réponse à cette question pour expliquer mes points de vue il y a quelques années.

Cependant, cette réponse est à peu près "l'apaisement" au point de vue de "l'optimisation prématurée", c'est-à-dire au point de vue que:

  • Toutes les optimisations sont prématurées par définition (ou, à court terme par nature ), et
  • La seule optimisation qui présente des avantages à long terme est la simplicité.

Mais de tels points de vue sont contestés dans cet article d'ACM .


Tout cela m'amène à me demander: le
code SIMD est différent du code d'application général, et je voudrais savoir s'il existe un consensus similaire dans l'industrie concernant la valeur d'un code propre et simple pour le code SIMD.

rwong
la source
2
Avez-vous des exigences de performance? Pouvez-vous répondre à vos exigences de performances sans utiliser SIMD? Sinon, la question est sans objet.
Charles E. Grant
4
C'est beaucoup trop long pour une question, probablement parce qu'une bonne partie de celui-ci est effectivement une tentative de répondre à la question, et même longue pour une réponse (en partie parce qu'elle touche à bien plus d'aspects que la plupart des réponses raisonnables).
3
J'aime avoir à la fois le code propre / simple / lent (pour la preuve de concept initiale et la documentation ultérieure) en plus des alternatives optimisées. Cela le rend facile à comprendre (car les gens peuvent simplement lire le code propre / simple / lent) et facile à vérifier (en comparant la version optimisée à la version propre / simple / lente manuellement et dans les tests unitaires)
Brendan
2
@Brendan J'ai été dans un projet similaire et j'ai utilisé une approche de test avec du code simple / lent. Bien qu'il s'agisse d'une option qui mérite d'être envisagée, elle présente également des limites. Tout d'abord, la différence de performances peut s'avérer prohibitive: les tests utilisant du code non optimisé peuvent s'exécuter pendant des heures ... jours. Deuxièmement, pour le traitement d'image, il peut s'avérer que la comparaison bit par bit ne fonctionne tout simplement pas, lorsque le code optimisé produit des résultats légèrement différents - de sorte qu'il faudrait utiliser une comparaison plus sophistiquée, comme la différence de carré moyen de la racine ef
gnat
2
Je vote pour fermer cette question comme hors sujet car ce n'est pas un problème de programmation conceptuelle comme décrit dans le centre d'aide .
durron597

Réponses:

6

Je n'ai pas écrit beaucoup de code SIMD pour moi, mais beaucoup de code assembleur il y a quelques décennies. AFAIK utilisant intrinsèques SIMD est essentiellement une programmation d'assembleur, et toute votre question pourrait être reformulée simplement en remplaçant "SIMD" par le mot "assemblage". Par exemple, les points que vous avez déjà mentionnés, comme

  • le code prend 10x à 100x pour se développer que le "code de haut niveau"

  • il est lié à une architecture spécifique

  • le code n'est jamais "propre" ni facile à refactoriser

  • vous avez besoin d'experts pour l'écrire et le maintenir

  • le débogage et la maintenance est difficile, évoluant vraiment dur

ne sont en aucun cas «spéciaux» pour SIMD - ces points sont valables pour tout type de langage d'assemblage, et ils sont tous «consensus de l'industrie». Et la conclusion dans l'industrie du logiciel est également à peu près la même que pour l'assembleur:

  • ne l'écrivez pas si vous n'y êtes pas obligé - utilisez un langage de haut niveau dans la mesure du possible et laissez les compilateurs faire le gros du travail

  • si les compilateurs ne sont pas suffisants, encapsulez au moins les parties "bas niveau" dans certaines bibliothèques, mais évitez de répandre le code partout dans votre programme

  • comme il est presque impossible d'écrire un assembleur "auto-documenté" ou du code SIMD, essayez d'équilibrer cela avec beaucoup de documentation.

Bien sûr, il y a une différence avec le code assembleur ou machine "classique": aujourd'hui, les compilateurs modernes produisent généralement du code machine de haute qualité à partir d'un langage de haut niveau, souvent mieux optimisé que le code assembleur écrit manuellement. Pour les architectures SIMD qui sont populaires aujourd'hui, la qualité des compilateurs disponibles est AFAIK bien en deçà - et peut-être qu'elle n'atteindra jamais cela, car la vectorisation automatique est toujours un sujet de recherche scientifique. Voir, par exemple, cet article qui décrit les différences d'opimisation entre un compilateur et un humain, donnant une idée qu'il pourrait être très difficile de créer de bons compilateurs SIMD.

Comme vous l'avez déjà décrit dans votre question, il existe également un problème de qualité avec les bibliothèques de pointe actuelles. Donc, à mon humble avis, nous pouvons espérer qu'au cours des prochaines années, la qualité des compilateurs et des bibliothèques augmentera, peut-être que le matériel SIMD devra changer pour devenir plus "convivial pour le compilateur", peut-être des langages de programmation spécialisés prenant en charge une vectorisation plus facile (comme Halide, qui vous l'avez mentionné deux fois) deviendra plus populaire (n'était-ce pas déjà une force de Fortran?). Selon Wikipedia , SIMD est devenu "un produit de masse" il y a environ 15 à 20 ans (et Halide a moins de 3 ans, lorsque j'interprète correctement les documents). Comparez cela aux compilateurs de temps pour le langage d'assemblage "classique" nécessaire pour devenir mature. Selon cet article Wikipediail a fallu près de 30 ans (de ~ 1970 à la fin des années 1990) pour que les compilateurs dépassent les performances des experts humains (en produisant du code machine non parallèle). Il nous faudra donc peut-être attendre encore 10 à 15 ans avant que la même chose se produise pour les compilateurs compatibles SIMD.

Doc Brown
la source
d'après ma lecture d'un article de Wikipedia , il semble y avoir un consensus général de l' industrie selon lequel le code optimisé à bas niveau est "considéré comme difficile à utiliser, en raison des nombreux détails techniques dont il faut se souvenir"
gnat
@gnat: oui, absolument, mais je pense que si j'ajoute ceci à ma réponse, je devrais une douzaine d'autres choses déjà mentionnées par le PO en d'autres termes dans sa trop longue question.
Doc Brown
d'accord, l'analyse dans votre réponse semble assez bonne telle quelle, ajoutant que la référence comporterait un risque de "surcharge"
gnat
4

Mon organisation a réglé ce problème précis. Nos produits se trouvent dans l'espace vidéo, mais une grande partie du code que nous écrivons est un traitement d'image qui fonctionnerait également pour les images fixes.

Nous avons "résolu" (ou peut-être "traité") le problème en écrivant notre propre compilateur. Ce n'est pas aussi fou que cela puisse paraître au premier abord. Il a un ensemble restreint d'entrées. Nous savons que tout le code fonctionne sur des images, principalement des images RGBA. Nous avons configuré certaines contraintes, comme le fait que les tampons d'entrée et de sortie ne peuvent jamais se chevaucher, il n'y a donc pas d'alias de pointeur. Des choses comme ça.

Nous écrivons ensuite notre code dans le langage d'ombrage OpenGL (glsl). Il est compilé en code scalaire, SSE, SSE2, SSE3, AVX, Neon et bien sûr glsl réel. Lorsque nous devons prendre en charge une nouvelle plate-forme, nous mettons à jour le compilateur pour générer du code pour cette plate-forme.

Nous réalisons également la mosaïque des images pour améliorer la cohérence du cache, et des trucs comme ça. Mais en gardant le traitement d'image sur un petit noyau et en utilisant glsl (qui ne supporte même pas les pointeurs), nous réduisons considérablement la complexité de la compilation du code.

Cette approche n'est pas pour tout le monde et elle a ses propres problèmes (vous devez vous assurer de l'exactitude du compilateur, par exemple). Mais cela a plutôt bien fonctionné pour nous.

user1118321
la source
Cela semble 🔥🔥! Ce produit est-il vendu ou mis à disposition de manière autonome? (Aussi, 'AVC' = AVX?)
Ahmed Fasih
Désolé, oui, je voulais dire AVX (je vais arranger ça.). Nous ne vendons pas actuellement le compilateur en tant que produit autonome, bien que cela puisse se produire à l'avenir.
user1118321
Pas de blague, ça sonne vraiment bien. La chose la plus proche que j'ai vue comme celle-ci est la façon dont le compilateur CUDA était capable de créer des programmes «série» qui s'exécutent sur le CPU pour le débogage - nous espérions que cela se généraliserait en un moyen d'écrire du code CPU multi-thread et SIMD, mais Hélas. La prochaine chose la plus proche à laquelle je peux penser est OpenCL - avez-vous évalué OpenCL et l'avez-vous trouvé inférieur à votre compilateur GLSL-to-all?
Ahmed Fasih
1
Eh bien, OpenCL n'existait pas lorsque nous avons commencé, je ne pense pas. (Ou si c'était le cas, c'était assez nouveau.) Donc, cela n'entre pas vraiment dans l'équation.
user1118321
0

Il ne semble pas ajouter trop de frais de maintenance si vous envisagez d'utiliser un langage de niveau supérieur:

Vector<float> values = GetValues();
Vector<float> increment = GetIncrement();

// Perform addition as a vector operation:
List<float> result = (values + increment).ToList();

contre

List<float> values = GetValues();
List<float> increment = GetIncrement();

// Perform addition as a monadic sequence operation:
List<float> result = values.Zip(increment, (v, i) => v + i).ToList();

Bien sûr, vous devrez faire face aux limites de la bibliothèque, mais vous ne la maintiendrez pas vous-même. Cela pourrait être un bon équilibre entre les coûts de maintenance et la performance.

http://blogs.msdn.com/b/dotnet/archive/2014/04/07/the-jit-finally-proposed-jit-and-simd-are-getting-married.aspx

http://blogs.msdn.com/b/dotnet/archive/2014/05/13/update-to-simd-support.aspx

Tanière
la source
d'après ma lecture, l'option d'utiliser des bibliothèques externes a déjà été étudiée et abordée par le demandeur: "Les bibliothèques à usage commercial répandu ne semblent pas être fortement compatibles SIMD ..."
gnat
@gnat J'ai effectivement lu tout ce paragraphe, pas seulement les puces de premier niveau, et l'affiche ne mentionne aucune bibliothèque SIMD à usage général, juste celles de vision par ordinateur et de traitement d'image. Sans oublier que l'analyse des applications de langues de niveau supérieur est complètement manquante, malgré l'absence de balise C ++ et aucune spécificité C ++ reflétée dans le titre de la question. Cela m'amène à croire que, bien que ma question ne soit pas considérée comme principale, elle est susceptible d'ajouter de la valeur et de sensibiliser les gens à d'autres options.
Den
1
À ma connaissance, le PO demande s'il existe des solutions à large diffusion commerciale. Bien que j'apprécie votre indice (je peux peut-être utiliser la bibliothèque pour un projet ici), d'après ce que je vois, RyuJIT est loin d'être une "norme industrielle largement acceptée".
Doc Brown
@DocBrown peut-être, mais sa véritable question est formulée pour être plus générique: "... consensus de l'industrie concernant la valeur d'un code propre et simple pour le code SIMD ...". Je doute qu'il y ait un consensus (officiel), mais je soutiens que les langages de niveau supérieur peuvent réduire la différence entre le code "habituel" et SIMD, tout comme le C ++, oublions l'assemblage, réduisant ainsi les coûts de maintenance.
Den
-1

J'ai fait de la programmation d'assemblage dans le passé, pas de la programmation SIMD récemment.

Avez-vous envisagé d'utiliser un compilateur compatible SIMD comme Intel? Est Guide de vectorisation avec Intel® C ++ Compilateurs intéressant?

Plusieurs de vos commentaires comme "ballon-popping" suggèrent d'utiliser un compilateur (pour obtenir des avantages tout au long si vous n'avez pas un seul point chaud).

ChrisW
la source
d'après ma lecture, cette approche a été tentée par le demandeur, voir les mentions de bogues / défauts du compilateur dans la question
moucher
L'OP n'a pas dit s'ils avaient essayé le compilateur Intel , qui est également le sujet de ce sujet Programmers.SE . La plupart des gens ne l'ont pas essayé. Ce n'est pas pour tout le monde; mais cela pourrait convenir à l'entreprise / à la question du PO (meilleures performances pour des coûts de codage / conception / maintenance réduits).
ChrisW
Eh bien, ce que j'ai lu dans la question suggère que le demandeur est au courant des compilateurs pour Intel et d'autres architectures: "Certaines architectures maintiennent une compatibilité descendante parfaite (Intel); certaines sont insuffisantes ..."
gnat
"Intel" dans cette phrase signifie le concepteur Intel-la-puce, pas Intel-le-compilateur-écrivain.
ChrisW