Lors de l'analyse des performances d'un système de stockage distribué, de nombreux éléments doivent être pris en compte. Les caractéristiques de vos algorithmes (locaux et distribués), la topologie du réseau, les capacités de débit et de latence de votre support de stockage sous-jacent, etc. Mais il y a un détail qu'il est facile de négliger: que se passe-t-il lorsque votre programme doit demander de la mémoire au système d'exploitation?
Dans cet article, je présenterai quelques anecdotes peut-être amusantes sur les fautes de page et leur impact sur les performances.
Qu'est-ce qu'une faute de page?
Une erreur de page est une sorte d'exception dans les systèmes de mémoire virtuelle - elle se produit lorsqu'un processus tente d'accéder à une page de mémoire en y lisant ou en y écrivant, et cette page n'est pas mappée à un bloc physique de bits dans la RAM. (Un défaut se produit également si un processus accède à la mémoire, il n'est pas autorisé à le faire, mais ce n'est pas le genre qui nous intéresse ici.) Les défauts de page sont une partie parfaitement normale de la vie dans la plupart des systèmes d'exploitation - quand un processus mappe la mémoire dans son virtuel espace d'adressage, le système d'exploitation ne mappe souvent pas physiquement cette mémoire jusqu'à ce qu'elle soit utilisée. Cependant, les erreurs de page ne sont pas inoffensives pour les performances, car elles augmentent la latence de certains accès mémoire pendant que le système d'exploitation met à jour ses tables de pages.
Une régression des performances du noyau Linux
Il y a quelque temps, notre ligne hybride de la série C exécutait la version 4.8 du noyau Linux. Ce n'était pas idéal, car ce n'était pas une version du noyau LTS, et elle n'était plus prise en charge dans la pile d'activation LTS d'Ubuntu, ce qui signifiait que nous risquions de prendre du retard sur les dernières corrections de bogues et mises à jour de sécurité. À l'époque, nous avons décidé d'essayer le noyau 4.13 plus récent - et lorsque nous l'avons fait, les tests de performance ont montré que le débit avait chuté de 25%.
C'est une régression assez choquante, nous avons donc creusé. Comme la seule chose qui avait changé était le noyau, j'ai commencé par examiner les métriques du système à l'aide de l'utilitaire SAR. suite de surveillance des performances sysstat Linux.
Pour aider à affiner la recherche, une technique que j’aime utiliser consiste à effectuer une série de tests de performance des «bons» et des «mauvais» builds, à collecter plusieurs échantillons de chaque métrique enregistrable, puis à comparer les deux collections de métriques utilisant un test t indépendant 2-sample, en particulier Test t de Welch sur les variances inégales. C’est un test conçu pour évaluer l’hypothèse voulant que deux populations aient des moyennes égales et qui est facilement disponible. dans la bibliothèque scipy.
J'aborderai plus en détail cette technique dans un prochain article, mais l'essentiel est: ingérer les échantillons de SAR en deux. cadres de données, effectuez le test t, supprimez toutes les lignes avec une valeur p supérieure à un certain seuil (je choisis généralement 5%), et triez les autres par statistique t (ordre croissant si les entrées étaient (bonnes, mauvaises) - ceci met l'accent sur les résultats qui avaient une corrélation négative avec la performance, et ceux-ci sont souvent intéressants). Dans ce cas, le résultat ressemblait à ceci:
Le titre de cet article mentionne bien des défauts de page. Et ici, il semblait que le passage à 4.13 avait plus que doublé le taux de défauts de pages pour ce repère. Alors, j'ai ensuite exploré l'outil de performance pratique, avec lequel j'avais l'habitude d'enregistrer pendant trente secondes tous les événements de mmap, munmap et page page dans le noyau, au nom du démon du système de fichiers de Qumulo:
$ perf record \ -e "syscalls: sys_enter_m * map" \ -e "exceptions: page_fault_ *" \ -p `pidof qfsd` sleep 30
Les événements enregistrés contiennent de nombreuses informations utiles, notamment l'heure et l'adresse à laquelle chaque mappage ou défaillance est survenu, le type de défaillance, etc. J'ai utilisé le script perf et quelques travaux laids pour transformer les données au format CSV pour faciliter l'analyse.
$ perf script | grep page_fault_ \ | sed -e s /.* \ [\ (. * \) \] \ ([^:] \ + \). * adresse = 0x \ ([^] \ + \) f. * code_erreur = 0x \ ( . * \) / \ 1, \ 2, \ 3, \ 4 / '> faults.csv $ perf script | grep mmap \ | sed -e /.* \ [\ (. * \) \] \ ([^:] \ + \):. * addr: 0x \ ([^,] \ + \). * / \ 1, \ 2, \ 3 / '> mmaps.csv
(Je ne suis pas fier de ces expressions régulières, mais ils ont fait le travail!)
À partir des adresses, j'ai rapidement déterminé que la plupart des nouveaux défauts se produisaient dans la région de mémoire utilisée par qfsd pour le cache de données de bloc. Nous mappons la mémoire dans le cache en blocs de 2 Mio, mais l'utilisons dans des blocs de 4 Kio - ci-dessous est une visualisation des appels mmap et des défauts de page autour d'une de ces régions de 2 Mio lors de l'utilisation de Linux 4.13:
Notez le point orange solitaire en bas à gauche, suivi d'une longue et longue chaîne de défauts de page (points bleus) dans la région de 2 Mio au-dessus de cette adresse de base. (Notez également l'ordre dans lequel les pages ont tendance à être consultées - à l'envers dans l'espace d'adressage! Ce détail un peu surprenant devient important plus tard…)
J'ai exécuté les mêmes mesures sur notre version de noyau 4.8, et les résultats ne pourraient pas être plus différents. Ici, il n’y avait que des fautes d’une ou deux pages après chaque mmap!
À l'heure actuelle, ceux d'entre vous qui sont beaucoup plus familiarisés avec la configuration de la mémoire virtuelle Linux que moi ont probablement crié la réponse dans votre esprit, ou peut-être à voix haute. Mais moi, je devais partir et apprendre à la dure. J'ai au moins eu le sentiment de soupçonner que la réponse pourrait être trouvée dans la configuration de construction du noyau, alors j'ai attrapé le kconfigs pour les deux noyaux et j'ai commencé à couper en deux. (Nous utilisons le noyau tel que distribué par Ubuntu, nous prenons donc pour la plupart les configurations par défaut. Très pratique, mais pas génial quand quelque chose devient plus lent et que vous ne savez pas pourquoi!)
Après quelques itérations de bissection, la liste des différences de configuration restantes était suffisamment petite pour que je la lise attentivement, et un paramètre m’a sauté aux yeux. C'était le diff:
- TRANSPARENT_HUGEPAGE_ALWAYS = y
+ TRANSPARENT_HUGEPAGE_MADVISE = y
Les pages gigantesques transparentes sont une caractéristique du système de mémoire Linux qui, lorsqu'il est activé pour une région de mémoire, amène le noyau à utiliser de «grandes» pages 2 MiB pour sauvegarder des mappages de mémoire au lieu du KiB 4 habituel. Ce changement signifie que THP, activé par défaut, est devenu désactivé par défaut. Il a été configuré pour les configurations par défaut des distributions Xenial et Artful d’Ubuntu. Voir ce numéro de Launchpad pour plus d'informations sur les raisons pour lesquelles le changement a été effectué.
Par coïncidence, nous masquons 2 MiB à la fois dans notre cache de données, ce qui explique pourquoi, auparavant, nous ne prenions généralement qu'une faute de page pour chaque bloc. Mystère résolu! Nos modèles d'accès bénéficient réellement des tables de pages plus petites et du nombre de défauts permis par THP; nous l'avons donc simplement réactivé pour notre processus, et la plupart des régressions de performances ont disparu.
Défauts de page II: Boogaloo électrique
Avance rapide jusqu'à la fin de 2018. Mon équipe travaillait sur l’optimisation des performances de très lourdes charges de travail de lecture aléatoire (vous avez peut-être vu les récents articles de blog de deux de mes collègues, Matt McMullan et Graham Ellis, à peu près le même projet). Nous avions fait beaucoup de progrès en améliorant notre système de mise en cache de verrous distribués, en ajustant le planificateur de tâches, en rendant la prélecture plus intelligente et en réduisant les conflits de verrouillages rotatifs à divers endroits.
Cependant, nous avons remarqué qu'il y avait des sources étranges de variabilité dans notre benchmark - certaines courses étaient juste un peu plus rapides que d'autres, sans raison immédiatement évidente. Un examen des données SAR a montré que les défauts de page étaient à nouveau fortement corrélés à la bimodalité. Nous avons trouvé certaines choses qui pourraient faciliter l'accès à la mémoire ici, notamment en s'assurant qu'une plus grande partie du cache de bloc a été initialement mappée - mais cet article de blog devient déjà un peu long, alors je vais peut-être entrer plus en détail sur ceux à une date ultérieure!
Mais rappelez-vous ce tracé plus tôt, montrant le modèle d'accès en arrière? Nous nous en sommes souvenus aussi et avons creusé un peu plus profondément. Cela s'est avéré être simplement un effet secondaire de la façon dont notre cache distribue les emplacements, nous avons donc essayé de l'inverser, de sorte que les tâches demandant des emplacements de cache auraient tendance à les obtenir dans l'ordre croissant des adresses. Un peu à notre grande surprise, cette performance légèrement améliorée! (Nous ne savons toujours pas pourquoi - notre intuition est que Linux est capable de pré-cartographier ou de faire une autre optimisation en présence d'un accès séquentiel.)
Ce graphique montre les améliorations relatives que nous avons apportées au repère de lecture aléatoire au cours des dix semaines environ:
Ce dernier problème visait à éviter les erreurs de page! Il est souvent important de savoir ce que votre système d'exploitation fait sous le capot.