Sur mon ancienne machine à laver, la carte électronique a lâché 6 mois après la fin de la garantie. Bilan, 300€ pour la remplacer, soit presque le prix de la machine… Après démontage, il s'est avéré que la carte était commune à pas mal de modèles de différentes marques et j'ai trouvé un tuto indiquant comment remplacer les 3 composants grillés. Au passage il y avait une explication sur l'origine de la panne : un composant trop justement dimensionné. Le coût des 3 composants était d'environ 1€, mais ils se commandent par 10, et en soudant ça avec mon gros fer à souder, la machine a encore tourné pendant 10 ans.
J'ai aussi récupéré un écran Dell 30 pouces comme ça : tuto sur le net, même problème de dimensionnement d'un composant, composant de rechange pas cher, j'y connais rien en électronique et j'ai pas de supers outils, et ça fonctionne encore des années !
Évidemment, les modèles plus récents avec USB et écran me laissent un peu plus perplexes question longévité/fiabilité que le TM31 beaucoup plus simple.
J'ai le FPP220 depuis environ 10 ans, les plastiques du bol et du blender se sont fissurés progressivement, aujourd'hui le blender est HS, le bol ne va pas tarder à rendre l'âme. Même constat sur un petit mixer de la même marque. Un petit axe en métal, pour la rotation des pièces, est scellé dans le plastique au fond des bols et ça se fissure petit à petit autour. En regardant les pièces de rechange sur le net, le prix de l'ensemble bol + blender + couvercle est largement supérieur à celui d'un nouveau modèle… Prochain achat un truc en métal ? En tout cas le Thermomix c'est plus cher mais ça tient le coup et on trouve les pièces de rechange moins cher (joints, etc, sauf le bol). Vive le métal !
Ça dépend ce que l'on veut mesurer : si le jeu de données ne tient pas dans le cache on va de toute façon se heurter au coût de l'éviction du cache avec un "mov" standard, qui sera plus faible si on fait un "movntdq" et on peut mesurer la bande passante mémoire max, sinon si le jeu de données tient dans le cache et qu'on fait plusieurs itérations dessus on va mesurer le débit des caches et là il vaut mieux utiliser un "mov" pour que les données soient laissées en cache, sinon avec "movntdq" certaines pages auront tendance à être évincées et le débit va baisser.
Au passage j'ai une petite remarque sur la bande passante du processeur M1 : en lecture je mesure environ 60Go/s (c'est énorme !) et en écriture "seulement" 30Go/s, donc c'est pas symétrique contrairement aux systèmes habituels. Je n'ai pas trouvé de détails sur le pourquoi du comment (et Apple n'est pas très bavard sur son processeur) donc si quelqu'un a une info là dessus je prends !
En fait c'est pas 1,6GHz mais 1,6G transactions de 64 bits donc 8 octets (la fréquence réelle est 800MHz mais on peut faire 2 transactions par cycle), ce qui donne le débit de 12,8Go/s, plus de détails ici : https://en.wikipedia.org/wiki/DDR3_SDRAM.
Comme la mémoire de 8Go est soudée je pense que ce n'est qu'une barrette, on a un seul canal, donc on arrive bien à la bande passante maximale avec le code optimisé. Pas la peine d'essayer de trafiquer encore le code pour gagner des perfs ! Bon après en passant en AVX on peut faire moins d'instructions et donc sans doute pouvoir réduire la fréquence et baisser la conso (j'ai fait des expériences sur Arm et le passage d'un code scalaire à NEON permet de réduire pas mal la conso en baissant la fréquence tout en gardant les performances) ou avoir plus de ressources disponibles pour d'autres processus.
Je n'y crois pas trop moi-même ! Il vaut mieux passer par l'unité SIMD dispo qui est plus large et possède les bonnes instructions.
En plus on peut aussi optimiser les loads : comme on ne fait que lire et qu'on ne réutilise pas les données on va vite remplir le cache pour des tableaux assez grands et donc il faut trouver des lignes dispos pour mettre les nouvelles données ce qui peut prendre un peu de temps. On peut dans ce cas utiliser les loads non temporels via les intrinsics streams (movntdq), c'est à dire qu'on indique au cache qu'il peut évincer sans pitié la ligne de cache que l'on vient juste d'utiliser et ça va un peu plus vite pour trouver de la place dans le cache pour charger de nouvelles données. On retrouve ces instructions notamment dans le code de memcpy.
Effectivement, le plus pratique est de faire un XOR avec deux comparaisons. Si on veut quand même supprimer une comparaison il faut ajouter pas mal d'opérations logiques et des masques donc ce n'est plus rentable. Une idée : "replier" les sous parties 32-bit sur elles-mêmes avec des OR et des masques de manières à tout accumuler dans le bit 0 et le bit 32, puis décaler le bit 32 vers la position 1 et on obtient soit 0 si pas trouvé, soit 1 si trouvé en position basse, soit 2 si trouvé en position haute, il ne reste qu'à comparer avec 0 et retourner i+lavaleurcalculée-1. Bon là on n'est plus dans l'optimisation du tout !
Dans le cas du find, on peut raisonnablement se dire que la performance est limitée par la bande passante mémoire, on est dans un scénario memory-bound, car le processeur doit charger beaucoup de données et ne fait que peu de calcul dessus, en tout cas pas suffisamment pour recouvrir le temps d'attente de la donnée. Du coup est-ce qu'on pourrait avoir des infos sur la mémoire utilisée sur la machine pour déterminer une bande passante max ?
D'après les données, le tableau fait 16M de int donc 64Mo, l'élément à trouver est en dernière position donc on traverse tout, et le temps pour la version optimisée SSE est d'environ 5ms, ce qui donne environ un débit de 12,8Go/s. Le i5-3210M ça doit être du Sandy Bridge avec de la DDR3 12800, donc on attendrait la perf max ! mais c'est en simple canal et il faudrait vérifier la config mémoire pour voir si c'est pas du double canal.
Il est possible de faire de la vecto sans unité vectorielle ! En manipulant des types de taille inférieure à 64 bits, par exemple des int, on peut "caster" le tableau en (int64_t*) donc charger deux int à la fois et faire une seule instruction de comparaison avec un registre contenant deux fois l'int recherché. Cela permet de diviser par deux le nombre d'instructions de comparaison et de chargement, même si on charge finalement la même quantité de données. Pour des opérations simples on doit pouvoir gagner, par contre j'avais testé il y a longtemps pour des instructions arithmétiques pour du traitement d'images, donc en manipulant 8 octets dans un registre 64-bit, mais il faut gérer les risques de dépassement en évitant la propagation des retenues entre les octets et c'était finalement pas efficace. En plus le code était illisible, mais ça peut être bien pour de l'obfuscation !
Bonjour,
Merci pour cette petite expérience. Je travaille pas mal sur l'optimisation de codes/parallélisation et je me méfie toujours de ce que fait le compilateur suite à de nombreuses déconvenues. Mes conclusions (en partie suite aux résultats dans ce papier https://hal.archives-ouvertes.fr/hal-01915529, je recommande le tableau page 8!) :
le compilateur n'apporte aucune garantie sur les optimisations : parfois avec -O2 c'est plus rapide qu'avec -O3, parfois c'est avec -Ofast, parfois avec un -march qui ne correspond pas à l'archi de la machine sur laquelle tourne le code, …
le compilateur casse parfois les optimisations qu'on essaie de mettre en place -> assembleur…
l'autovectorisation ne vectorise pas des cas triviaux mais vectorise des cas complexes sans doute par identification de motifs et remplacement par du code optimisé (un peu comme la boucle for pour copier qui est remplacée par un memcpy), mais dès qu'on modifie un peu le motif, plus personne…
l'autovectorisation quand elle produit du code vectorisé ne garantit pas que le code vectorisé soit plus efficace, par exemple sur un traitement d'image, le code "vectorisé" va passer son temps à faire des instructions shuffle/unpack/permute pour réorganiser les données avant de faire quelques calculs puis refaire tout le chemin inverse et parfois c'est plus lent que du code scalaire… mais si on le fait correctement à la main avec des intrinsics on va 3x plus vite ! C'est ce qui a motivé la désactivation de la vecto pour AVX-512, le gain de perf dans de nombreux cas n'était pas suffisant pour contrecarrer la baisse de fréquence.
l'autovectorisation n'est pas portable entre architectures (je sais que ce ne sont pas les mêmes instructions mais le processus devrait être un peu indépendant de l'archi), le même compilo va arriver à vectoriser sur x86 en SSE, mais pas en NEON sur Arm, sans utiliser des instructions spécifiques.
sur Arm 32-bit j'ai eu une surprise avec une ancienne version de GCC : mes intrinsics étaient convertis en code scalaire mais ça allait 20-30% plus vite que le code scalaire parce que du coup la boucle était déroulée de 4 !
parfois l'ajout d'une directive #pragma omp simd bloque l'autovectorisation qui est pourtant faite avec juste -O3.
il y a des optimisations qui cassent si on ajoute une petite couche de templates, même juste en utilisant std::vector à la place d'un tableau à la C.
j'ai vu des personnes passer énormément de temps à modifier leur code et à espérer que le compilo daigne enfin vectoriser tout seul. Le code ne ressemble plus à rien, on peut espérer gagner 20%, alors qu'en passant par des intrinsics et avec la surcharge des opérateurs on peut vectoriser sans modifier la structure du code et on a un x2 à x5 suivant les archis.
[^] # Re: Comme ça me rappel des galère
Posté par sjub . En réponse au journal J'essaie de commander des pièces détachées pour du petit électroménager. Évalué à 10. Dernière modification le 22 novembre 2021 à 12:46.
Sur mon ancienne machine à laver, la carte électronique a lâché 6 mois après la fin de la garantie. Bilan, 300€ pour la remplacer, soit presque le prix de la machine… Après démontage, il s'est avéré que la carte était commune à pas mal de modèles de différentes marques et j'ai trouvé un tuto indiquant comment remplacer les 3 composants grillés. Au passage il y avait une explication sur l'origine de la panne : un composant trop justement dimensionné. Le coût des 3 composants était d'environ 1€, mais ils se commandent par 10, et en soudant ça avec mon gros fer à souder, la machine a encore tourné pendant 10 ans.
J'ai aussi récupéré un écran Dell 30 pouces comme ça : tuto sur le net, même problème de dimensionnement d'un composant, composant de rechange pas cher, j'y connais rien en électronique et j'ai pas de supers outils, et ça fonctionne encore des années !
[^] # Re: Le plastique c'est pas fantastique
Posté par sjub . En réponse au journal J'essaie de commander des pièces détachées pour du petit électroménager. Évalué à 4.
Évidemment, les modèles plus récents avec USB et écran me laissent un peu plus perplexes question longévité/fiabilité que le TM31 beaucoup plus simple.
# Le plastique c'est pas fantastique
Posté par sjub . En réponse au journal J'essaie de commander des pièces détachées pour du petit électroménager. Évalué à 1.
J'ai le FPP220 depuis environ 10 ans, les plastiques du bol et du blender se sont fissurés progressivement, aujourd'hui le blender est HS, le bol ne va pas tarder à rendre l'âme. Même constat sur un petit mixer de la même marque. Un petit axe en métal, pour la rotation des pièces, est scellé dans le plastique au fond des bols et ça se fissure petit à petit autour. En regardant les pièces de rechange sur le net, le prix de l'ensemble bol + blender + couvercle est largement supérieur à celui d'un nouveau modèle… Prochain achat un truc en métal ? En tout cas le Thermomix c'est plus cher mais ça tient le coup et on trouve les pièces de rechange moins cher (joints, etc, sauf le bol). Vive le métal !
[^] # Re: Efficacité - Borne sup
Posté par sjub . En réponse au journal Recherche de valeur dans un tableau et l'écosystème des compilateurs C++. Évalué à 4.
Ça dépend ce que l'on veut mesurer : si le jeu de données ne tient pas dans le cache on va de toute façon se heurter au coût de l'éviction du cache avec un "mov" standard, qui sera plus faible si on fait un "movntdq" et on peut mesurer la bande passante mémoire max, sinon si le jeu de données tient dans le cache et qu'on fait plusieurs itérations dessus on va mesurer le débit des caches et là il vaut mieux utiliser un "mov" pour que les données soient laissées en cache, sinon avec "movntdq" certaines pages auront tendance à être évincées et le débit va baisser.
Au passage j'ai une petite remarque sur la bande passante du processeur M1 : en lecture je mesure environ 60Go/s (c'est énorme !) et en écriture "seulement" 30Go/s, donc c'est pas symétrique contrairement aux systèmes habituels. Je n'ai pas trouvé de détails sur le pourquoi du comment (et Apple n'est pas très bavard sur son processeur) donc si quelqu'un a une info là dessus je prends !
[^] # Re: Efficacité - Borne sup
Posté par sjub . En réponse au journal Recherche de valeur dans un tableau et l'écosystème des compilateurs C++. Évalué à 3.
En fait c'est pas 1,6GHz mais 1,6G transactions de 64 bits donc 8 octets (la fréquence réelle est 800MHz mais on peut faire 2 transactions par cycle), ce qui donne le débit de 12,8Go/s, plus de détails ici : https://en.wikipedia.org/wiki/DDR3_SDRAM.
Comme la mémoire de 8Go est soudée je pense que ce n'est qu'une barrette, on a un seul canal, donc on arrive bien à la bande passante maximale avec le code optimisé. Pas la peine d'essayer de trafiquer encore le code pour gagner des perfs ! Bon après en passant en AVX on peut faire moins d'instructions et donc sans doute pouvoir réduire la fréquence et baisser la conso (j'ai fait des expériences sur Arm et le passage d'un code scalaire à NEON permet de réduire pas mal la conso en baissant la fréquence tout en gardant les performances) ou avoir plus de ressources disponibles pour d'autres processus.
[^] # Re: le plus rapide en code simple
Posté par sjub . En réponse au journal Recherche de valeur dans un tableau et l'écosystème des compilateurs C++. Évalué à 3.
Je n'y crois pas trop moi-même ! Il vaut mieux passer par l'unité SIMD dispo qui est plus large et possède les bonnes instructions.
En plus on peut aussi optimiser les loads : comme on ne fait que lire et qu'on ne réutilise pas les données on va vite remplir le cache pour des tableaux assez grands et donc il faut trouver des lignes dispos pour mettre les nouvelles données ce qui peut prendre un peu de temps. On peut dans ce cas utiliser les loads non temporels via les intrinsics streams (movntdq), c'est à dire qu'on indique au cache qu'il peut évincer sans pitié la ligne de cache que l'on vient juste d'utiliser et ça va un peu plus vite pour trouver de la place dans le cache pour charger de nouvelles données. On retrouve ces instructions notamment dans le code de memcpy.
[^] # Re: le plus rapide en code simple
Posté par sjub . En réponse au journal Recherche de valeur dans un tableau et l'écosystème des compilateurs C++. Évalué à 1.
Effectivement, le plus pratique est de faire un XOR avec deux comparaisons. Si on veut quand même supprimer une comparaison il faut ajouter pas mal d'opérations logiques et des masques donc ce n'est plus rentable. Une idée : "replier" les sous parties 32-bit sur elles-mêmes avec des OR et des masques de manières à tout accumuler dans le bit 0 et le bit 32, puis décaler le bit 32 vers la position 1 et on obtient soit 0 si pas trouvé, soit 1 si trouvé en position basse, soit 2 si trouvé en position haute, il ne reste qu'à comparer avec 0 et retourner i+lavaleurcalculée-1. Bon là on n'est plus dans l'optimisation du tout !
# Efficacité - Borne sup
Posté par sjub . En réponse au journal Recherche de valeur dans un tableau et l'écosystème des compilateurs C++. Évalué à 2.
Dans le cas du find, on peut raisonnablement se dire que la performance est limitée par la bande passante mémoire, on est dans un scénario memory-bound, car le processeur doit charger beaucoup de données et ne fait que peu de calcul dessus, en tout cas pas suffisamment pour recouvrir le temps d'attente de la donnée. Du coup est-ce qu'on pourrait avoir des infos sur la mémoire utilisée sur la machine pour déterminer une bande passante max ?
D'après les données, le tableau fait 16M de int donc 64Mo, l'élément à trouver est en dernière position donc on traverse tout, et le temps pour la version optimisée SSE est d'environ 5ms, ce qui donne environ un débit de 12,8Go/s. Le i5-3210M ça doit être du Sandy Bridge avec de la DDR3 12800, donc on attendrait la perf max ! mais c'est en simple canal et il faudrait vérifier la config mémoire pour voir si c'est pas du double canal.
[^] # Re: le plus rapide en code simple
Posté par sjub . En réponse au journal Recherche de valeur dans un tableau et l'écosystème des compilateurs C++. Évalué à 2.
Il est possible de faire de la vecto sans unité vectorielle ! En manipulant des types de taille inférieure à 64 bits, par exemple des int, on peut "caster" le tableau en (int64_t*) donc charger deux int à la fois et faire une seule instruction de comparaison avec un registre contenant deux fois l'int recherché. Cela permet de diviser par deux le nombre d'instructions de comparaison et de chargement, même si on charge finalement la même quantité de données. Pour des opérations simples on doit pouvoir gagner, par contre j'avais testé il y a longtemps pour des instructions arithmétiques pour du traitement d'images, donc en manipulant 8 octets dans un registre 64-bit, mais il faut gérer les risques de dépassement en évitant la propagation des retenues entre les octets et c'était finalement pas efficace. En plus le code était illisible, mais ça peut être bien pour de l'obfuscation !
# Les compilateurs et l'optimisation
Posté par sjub . En réponse au journal Recherche de valeur dans un tableau et l'écosystème des compilateurs C++. Évalué à 10.
Bonjour,
Merci pour cette petite expérience. Je travaille pas mal sur l'optimisation de codes/parallélisation et je me méfie toujours de ce que fait le compilateur suite à de nombreuses déconvenues. Mes conclusions (en partie suite aux résultats dans ce papier https://hal.archives-ouvertes.fr/hal-01915529, je recommande le tableau page 8!) :
Vivement que Facebook vienne révolutionner tout ça… ou pas ! https://ai.facebook.com/blog/compilergym-making-compiler-optimizations-accessible-to-all/