• [^] # Re: Pas spécialement stressant

    Posté par . En réponse au journal Psychologie, science et reproductibilité. Évalué à 10.

    En informatique, sauf cas exceptionnel, le système est déterministe

    Ça, c'est la théorie. En pratique, c'est juste « plus » déterministe que les sciences du vivant. Il y a plusieurs raisons:

    1. Un système informatique, même s'il est composé de pièces qui individuellement sont bien comprises, est composé de milliards de ces dernières (transistors, fils pour connecter les composants, etc.)
    2. Tout un tas de choses sont cachées aux chercheurs. Par exemple, dans le domaine de l'informatique haute-performance, aucun fondeur/constructeur de processeur ne donnera les specs exactes de ses puces, et ce, même si on est officiellement partenaire avec.
    3. Au niveau des OS, non seulement il faut composer avec la version du système utilisé, mais aussi, même avec un système donné, il y a des comportements qui changent. Par exemple, utiliser Ubuntu server ou RedHat/CentOS ou Scientific Linux, ça mène à de subtils changements dans le comportement du noyau, car des patchs différents sont appliqués.
    4. Un même OS sur des machines différentes aura souvent certains comportements différents.

    Quelques exemples :

    • Le système de caches des Core 2 était non-inclusif, c'est-à-dire que si une ligne de cache se trouvait en cache de niveau 2 (L2), on ne savait pas si elle se trouvait en niveau 1 (L1). Jusque là, tout va « bien ». Sauf qu'on ne savait pas non plus si elle se trouvait en L2 quand elle était présente dans (un des) L1.
    • Le système de caches des Nehalem (première génération des Core i7) était inclusif : si une ligne est présente dans un L1, alors elle l'est aussi au moins dans un L2, et dans le L3 (partagé par tous les cœurs).
    • Avec le Sandy Bridge (Core i7 de 2nde génération), on repasse en non-inclusif
    • L'implémentation du protocole de cohérence de cache dans les processeurs est opaque : si on n'a pas de contact officieux chez le constructeur, on ne sait pas forcément pourquoi la bande-passante théorique est parfois jusqu'à deux fois plus grande que la bande-passante observée.
    • Un cache peut-être configuré en "write-through" (c'est-à-dire : si j'écris en mémoire et que la ligne n'est pas présente en cache, je me contente d'envoyer le mot dans le tampon d'écriture), ou "write-back" (c'est-à-dire : si la ligne de cache qui contient l'adresse à écrire n'est pas présente, il faut d'abord la charger, puis écrire la ligne). Le cas du write-back est celui rencontré le plus souvent, car dans le cas moyen, c'est celui qui est le plus utile. Mais du coup, lorsqu'on mesure la bande-passante effective en lecture et en écriture, on se retrouve avec une asymétrie.
    • Le modèle mémoire (au-delà de la cohérence de cache) varie d'une version de processeur x86 à une autre, et entre les constructeurs. Au point qu'Intel ne fait que de vagues descriptions dans ses manuels. À comparer avec le manuel de SPARC v9, qui avait dans une annexe la formulation mathématique de son modèle de constance mémoire.
    • Les compilateurs optimisent bien mieux que les humains, mais ça a un coût : parfois, une fonction sera inlinée, et du coup, en passant par un logiciel de profilage, il semblera que certaines fonctions prennent beaucoup de temps et que la vraie fonction « intensive », étant inlinée, coûtera « 0 ».
    • Les compilateurs proposent plusieurs centaines d'options de compilation. Chercher à déterminer la meilleure combinaison d'options de compil pour un code donné revient à faire une recherche exhaustive, soit des millions de combinaisons possible (et une phase de compilation pour chaque). Du coup, il existe tout un pan de la recherche en compilation qui passe par des algos d'apprentissage (réseaux de neurones et algos génétiques). Et du coup, on « perd » une partie de l'information 1 .
    • Le compilateur est souvent au courant de certaines choses concernant le système. Par exemple, si je compile avec -fopenmp ou -lpthread, le compilateur va d'office supprimer certaines optimisations qu'il ne peut pas garantir correctes dans le cadre d'une exécution parallèle. Résultat : débugger un code purement séquentiel ou bien qui contient ne serait-ce qu'un thread qui appelle le même code va potentiellement avec un comportement différent niveau perfs. De plus, bon courage pour savoir quelles optimisations ont été désactivées, et lesquelles seront réactivées d'une version à l'autre d'un compilateur.
    • Les processeurs proposent des « grandes pages » depuis un bon moment (sur x86, une page mémoire normale vaut 4kio; une grande page vaut 2Mio ou 4Mio). Mais Linux utilisent un vilain hack pour faire fonctionner le bousin, ce qui fait qu'en pratique, on n'a pas forcément de garanties pour obtenir des grandes pages même quand on demande gentiment, surtout si on mélange allègrement les deux.

    Je ne parle même pas de ce qui se passe quand on commence à rajouter des niveaux d'indirection (utilisation de CLI ou JVM, utilisation d'hyperviseurs, etc.). Attention, l'informatique reste 1000 fois plus « déterministe » que les phénomènes naturels qu'on cherche à isoler et observer. Mais dire que l'informatique est en gros déterministe, c'est plus ou moins comme si on disait « on a observé et compris les règles de combinaison entre deux atomes, du coup comprendre comment des organismes vivant interagissent est déterministe. » : il y a trop de niveaux d'indirection pour pouvoir l'affirmer avec certitude.


    1. À noter que des anciens collègues avaient commencé « pour le fun » à jouer avec un jeu restreint de ces options de compilation (en sélectionnant celles qui auraient sans doute le plus d'impact sur un code de calcul intensif), et leur conclusion était que « -O3 est suffisant dans 90% des cas ».