Pourquoi les moteurs doivent-ils être optimisés pour les nouveaux processeurs de la même architecture?

39

Lorsqu'une nouvelle génération de processeurs est disponible, la plupart des sites Web signalent que les moteurs de jeu et les programmes doivent être optimisés pour le nouveau matériel. Je ne comprends pas trop pourquoi. Un processeur a généralement une architecture qui définit le type de jeu d’instructions qu’il utilise. Celui que nous utilisons tous aujourd'hui est amd_x86_64. Pourquoi un programme ou un compilateur doit-il être mis à jour si tous les processeurs utilisent cette même architecture? Certes, il existe dans le pipeline du nouveau processeur des fonctionnalités qui optimisent l'exécution du code machine, mais pourquoi faudrait-il modifier le code machine lui-même si l'architecture ne le faisait pas?

Salbeira
la source
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
Josh
14
"Besoin" est une mauvaise formulation, et plus de marketing que de vérité, un peu comme Windows doit prendre en charge une certaine nouvelle génération de CPU (ou ne le fait pas, comme dans le cas de Windows 7, qui fonctionnerait en principe parfaitement par exemple avec Ryzen, sauf pour utiliser 3 à 4% de puissance en plus que nécessaire). Ce réglage consiste uniquement à essayer d’extraire un peu plus de la CPU, en se rapprochant au maximum. De manière réaliste, vous pourrez peut-être gagner un total général de 1 à 2% dans les exemples non récupérés en raison d'une planification différente et de l'utilisation de deux nouvelles instructions.
Damon
2
Ce n'est pas parce que deux processeurs peuvent effectuer les mêmes opérations que les opérations ont la même performance sur les deux processeurs ...
Mehrdad
Voir une question connexe sur Stack Overflow: Comment fonctionne réellement mtune?
Marc.2377

Réponses:

54

Parce que différentes générations de la même architecture peuvent avoir différents jeux d'instructions .

Par exemple, les extensions Streaming SIMD constituent probablement le jeu d'instructions x86 le plus connu. Pourtant, et malgré l'existence d'une architecture x86, il existe des interfaces SSE, SSE2, SSE3 et SSE4.

Chacune de ces générations peut inclure de nouvelles instructions offrant des moyens plus rapides d'effectuer certaines opérations. Un exemple intéressant pour les jeux pourrait être les instructions du produit scalaire.

Ainsi, si un moteur de jeu est compilé pour une génération précédente d'une architecture, il ne supportera pas ces nouvelles instructions. De même, il peut être nécessaire d'optimiser le moteur pour les nouvelles instructions; SSE4 , par exemple, prend en charge les instructions de produit point qui fonctionnent sur des données de type tableau de structures. Une optimisation qui pourrait tirer parti de ces nouvelles instructions consisterait à changer la disposition de vos données en tableau de structures.

Maximus Minimus
la source
1
@Panzercrisis - merci pour la suggestion de modification. Pour être clair: la question initiale ne portait pas sur votre propre code, mais sur le code du moteur. "Optimiser votre propre code" n'est donc pas une bonne suggestion de modification. Cependant, cela a mis en évidence le fait que je devais préciser que lorsque j'ai dit «optimiser», je voulais dire «optimiser le code moteur», alors j'ai édité pour prendre cela en compte.
Maximus Minimus
37

La réponse de Maximus est correcte, je veux juste donner un autre morceau de l'histoire:

Le matériel lui-même change de la même manière que vous devez modifier la manière dont vous codez, quelles que soient les nouvelles instructions introduites.

  • L'augmentation ou la diminution des quantités de cache signifie que vous devez vous préoccuper moins de l'optimisation / de l'invalidation du cache. Plus de cache signifie que, avec de petites données, vous pouvez vous concentrer moins sur la contiguïté des données pour éviter les problèmes de performances. Moins de cache signifie que cela pourrait être un problème, et très peu de cache signifie que, avec de grandes structures de données, cela n'aura aucune importance.

  • De nouveaux niveaux de cache signifient que vous devez réfléchir davantage à la manière dont vous organisez des ensembles de données encore plus volumineux (L1, L2, L2, L3, L4).

  • Plus de cœurs signifie que vous devez réfléchir à la manière dont vous allez améliorer les applications multithreads et à la manière dont votre application évolue dans un environnement multi-processus.

  • Des horloges plus rapides signifient que vous devez penser davantage à la latence de la mémoire qu'à la vitesse de calcul du processeur, qui constitue un goulot d'étranglement pour votre système.

  • Le nombre de FPU sur un système peut ne plus correspondre au nombre d'ALU entières par cœur (AMD avait / avait une telle architecture).

  • Le nombre de cycles d'horloge nécessaires pour calculer une opération peut être diminué ou augmenté.

  • Le nombre de registres disponibles a changé.

Tous ces éléments ont un impact très réel sur les performances des programmes, qui ont émis des hypothèses sur l’architecture sous-jacente d’un matériel antérieur avec le même ISA, qu’elles soient positives ou négatives.

quand
la source
"L'augmentation ou la diminution des niveaux de cache signifie que vous devez vous préoccuper moins de la cohérence du cache." Voulez-vous dire faux partage? Même que pratiquement toutes les lignes $ CPU ont presque toujours 64 B ...
Maciej Piechotka
1
Maciej prenait juste votre déclaration sur la cohérence du cache :) Vous vouliez probablement dire "optimisation du cache" ou quelque chose du genre. La cohérence de la mémoire cache est la capacité d'un système à conserver au logiciel une vue cohérente de la mémoire, même si N est en présence de caches indépendants . Ceci est complètement orthogonal à la taille. TBH, la déclaration n’est pas vraiment pertinente, mais votre réponse (en particulier les points 5 et 6) répond mieux à la question que celle acceptée par l’OMI :) Peut-être que souligner la différence entre architecture et architecture en u le fera ressortir davantage.
Margaret Bloom
4
"comme si la multiplication prenait plus de temps que l'addition, où, comme aujourd'hui avec les systèmes de renseignements modernes et les CPUS, il prend le même temps" Ce n'est pas tout à fait vrai. Dans les architectures en pipeline, vous devez faire la différence entre la latence (lorsque le résultat est prêt) et le débit (combien vous pouvez en faire par cycle). En outre, les processeurs Intel modernes offrent un débit de 4 et une latence de 1. Multiplier les débits de sortie 1 et 3 (ou 4). Ce sont les choses qui changent avec chaque architecture et qui nécessitent une optimisation. Par exemple, pdepprend 1 cycle sur Intel mais 6 sur Ryzen, donc ne voulez pas l'utiliser sur Ryzen.
Christoph
2
@Clearer Je sais que nous parlons ici de processeurs, mais vous n'avez jamais programmé pour les GPU, n'est-ce pas? Le même code produit des performances tellement différentes que vous êtes souvent obligé de prendre en compte les capacités matérielles de CUDA. C'est de là que je viens avec cela, la taille du cache (mémoire partagée, cache géré N1) doit en réalité être prise en compte dans la manière de coder quelque chose dans CUDA.
Quand
2
@Christoph est correct. Le repère que vous liez concerne une boucle sur un tableau c[i] = a[i] OP b[i](c.-à-d. 2 charges et 1 magasin par opération), de sorte que les temps sont dominés par la bande passante mémoire en raison de la très faible intensité de calcul. La taille du tableau n'est pas affichée, donc IDK si elle convient à L1D. ( gcc4.9 -Ofasttrès probablement, ces boucles sont vectorisées automatiquement, de sorte que vous ne mesurez même pas le coût des opérations scalaires normales dans le cadre d’un code entier complexe). La première ligne de cette page est IMPORTANT: des commentaires utiles ont révélé que certaines de ces mesures sont sérieusement défectueuses. Une mise à jour majeure est sur le chemin .
Peter Cordes
2

Même au-delà de changements brusques tels que la prise en charge de nouvelles instructions, les fabricants de microprocesseurs modifient constamment leurs conceptions pour améliorer les performances. Chaque nouvelle conception peut avoir des performances relatives différentes pour chaque instruction ou technique. Vous avez peut-être écrit du code sans embranchement soigneusement optimisé pour le modèle X, mais le modèle Y dispose d'un prédicteur de branche amélioré qui réduit la pénalité de prédiction erronée pour la version sans code du code (ce qui libère également un registre pouvant être utilisé ailleurs) . Peut-être que le modèle Y prend en charge un plus grand parallélisme d'une certaine instruction à latence élevée, de sorte qu'une boucle déroulée de cette instruction permet d'obtenir un meilleur débit, alors que sur le modèle X, une séquence plus courte était préférable.

Tout problème peut être résolu de nombreuses manières et chaque programme est une collection imbriquée de compromis et d’allocation de ressources, du point de vue de l’optimisation. Même de petits changements dans la disponibilité de ces ressources ou dans le coût d'un code donné par rapport à ces ressources peuvent avoir un effet en cascade qui confère un avantage substantiel en performances à un code ou à un autre. Même si une puce mise à niveau a « plus de tout », combien beaucoup plus de chaque chose peut faire pencher la balance.

Hobbs
la source