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
vtbl
parvtblq
) 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.)
- 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.
- Les bibliothèques open source semblent tièdes pour SIMD.
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.
Réponses:
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.
la source
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.
la source
Il ne semble pas ajouter trop de frais de maintenance si vous envisagez d'utiliser un langage de niveau supérieur:
contre
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
la source
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).
la source