Aller au contenu
English
Virtualisation medium

Pourquoi votre VM KVM est lente : CPU, mémoire et disque

45 min de lecture

Logo KVM

Une VM lente l'est rarement par manque de ressources. Elle l'est parce qu'un réglage par défaut la prive d'une capacité du matériel : un modèle de CPU qui masque la moitié des jeux d'instructions, un bus disque ancien, un mode de cache qui force chaque écriture à traverser le disque. Ce guide montre où regarder, dans quel ordre, et ce que chaque réglage coûte réellement.

  • Choisir entre host-model et host-passthrough, et ce que chacun interdit
  • Lire la topologie que vous exposez à l'invité, sockets, cœurs et threads
  • Savoir quand NUMA compte, et quand s'en occuper est une perte de temps
  • Comparer les modes de cache disque, mesures à l'appui
  • Mesurer avant de régler, plutôt que d'appliquer une recette

C'est le réglage qui a le plus d'effet, et le plus mal compris. libvirt propose trois façons de décrire le processeur exposé à l'invité :

Fenêtre de terminal
virsh domcapabilities | grep -oE "<mode name='[a-z-]+' supported='[a-z]+'"
ModeCe que voit l'invitéMigration
customUn modèle nommé, figé, choisi par vousVers tout hôte qui sait l'émuler
host-modelLe modèle connu le plus proche de l'hôte, plus des featuresVers un hôte au moins équivalent
host-passthroughLe processeur de l'hôte, tel quelVers un hôte identique seulement

host-model ne copie pas votre processeur : il part du modèle QEMU le plus proche et lui ajoute les fonctions manquantes. L'écart est parfois spectaculaire. Sur la machine qui a servi à écrire ce guide, un Intel i7-12650H de 12e génération, libvirt déduit :

<model fallback='forbid'>Skylake-Client-IBRS</model>

Un modèle Skylake, soit plusieurs générations en arrière, complété par 117 features déclarées require. Le résultat est fonctionnellement proche du processeur réel, mais la liste est explicite, et c'est précisément ce qui rend la migration possible : l'hôte de destination peut vérifier qu'il sait tout fournir.

host-passthrough, lui, expose les 142 flags du processeur hôte sans traduction. L'invité voit un vrai Alder Lake, avec avx_vnni, sha_ni, vaes ou movdir64b, que le modèle Skylake ne déclare pas. Pour une charge qui exploite ces instructions, chiffrement ou calcul vectoriel, l'écart est réel.

<cpu mode='host-passthrough' check='none' migratable='on'/>

En virt-install, l'option correspondante est --cpu host-passthrough. Et il faut savoir que c'est déjà le défaut, ce qui surprend la plupart des gens. Vérifiable sans rien créer, --print-xml se contentant d'afficher la définition :

Fenêtre de terminal
virt-install --name lecture --memory 512 --vcpus 1 --disk none \
--import --osinfo ubuntu24.04 --print-xml | grep '<cpu'
<cpu mode="host-passthrough"/>

Le résultat est identique avec --osinfo generic, ubuntu24.04 ou rhel9.4 : le mode ne dépend pas du système invité. Toute machine créée par virt-install sans --cpu explicite est donc en host-passthrough, et ne pourra pas migrer vers un hôte au processeur différent. Personne ne l'a décidé, et c'est le genre de contrainte qu'on découvre le jour où l'on veut déplacer la machine.

Si votre parc est hétérogène, ou simplement si vous n'êtes pas certain que ces machines resteront là où elles sont, posez --cpu host-model à la création. Pour l'existant, la vérification tient en une commande :

Fenêtre de terminal
virsh dumpxml ma-vm | grep -A2 '<cpu'

Attribuer quatre vCPU ne dit pas comment l'invité les voit. Sans topologie déclarée, il perçoit souvent quatre sockets d'un cœur chacun, ce qui est une machine qui n'existe pas. Certains logiciels licenciés au socket en tirent des conclusions coûteuses, et certains ordonnanceurs y perdent leurs repères.

<cpu mode='host-model'>
<topology sockets='1' cores='4' threads='1'/>
</cpu>

Le produit sockets × cores × threads doit valoir exactement le nombre de vCPU déclarés, sinon le domaine refuse de démarrer. Déclarer threads='2' n'a de sens que si vous exposez réellement des paires de threads du même cœur physique.

NUMA décrit des machines où la mémoire est plus lente selon le processeur qui y accède. Sur un serveur bi-socket, y placer une VM à cheval coûte cher. Sur un poste de travail, la question ne se pose pas.

Fenêtre de terminal
virsh capabilities | grep -A4 '<topology>'

L'hôte de ce guide rend un seul nœud, cell id='0', avec ses 16 CPU et toute sa mémoire. Dans cette configuration, le pinning NUMA n'apporte rien : il n'y a pas de mémoire lointaine à éviter. Vérifiez donc votre topologie avant d'appliquer une recette trouvée pour un bi-socket, sous peine de complexifier une configuration sans rien gagner.

La mémoire : les grandes pages travaillent peut-être déjà

Section intitulée « La mémoire : les grandes pages travaillent peut-être déjà »

Les hugepages réduisent le coût de traduction d'adresses en couvrant 2 Mio par entrée au lieu de 4 Kio. Deux mécanismes coexistent, et on croit souvent n'avoir ni l'un ni l'autre :

Fenêtre de terminal
grep -E 'HugePages_Total|Hugepagesize|AnonHugePages' /proc/meminfo
cat /sys/kernel/mm/transparent_hugepage/enabled

Sur cette machine, HugePages_Total vaut 0 : aucune page statique n'est réservée. Pourtant AnonHugePages annonce 1,8 Gio déjà servis en grandes pages, parce que le THP est en mode madvise et que QEMU les demande. Une partie du gain est donc déjà acquise, sans configuration.

Réserver des hugepages statiques garde son intérêt pour une VM dont la latence doit être régulière : la mémoire est prise au démarrage de l'hôte et ne peut plus être fragmentée. Le coût est qu'elle devient indisponible pour tout le reste, réservée qu'elle soit utilisée ou non.

C'est le poste où un mauvais réglage se voit le plus, et où deux choix indépendants se cumulent.

BusQuand
virtio (virtio-blk)Le défaut raisonnable : chemin court, peu de couches
virtio-scsiDès qu'il faut beaucoup de disques, le discard fin ou des commandes SCSI
sata, ideUniquement pour un invité sans pilote virtio, Windows sans driver

Un disque en sata sur une VM Linux moderne est le cas classique de machine lente sans raison : l'invité a les pilotes virtio, personne ne les lui a donnés.

Le mode de cache décide qui garantit qu'une écriture a atteint le disque. Mesuré sur cet hôte avec qemu-img bench, 12 000 opérations de 64 Kio, cache de page vidé entre chaque passe :

ModeLectureÉcriture
none0,017 s0,236 s
writeback0,119 s0,299 s
directsync0,113 s1,710 s

L'écart qui compte est celui de la dernière ligne : directsync écrit sept fois plus lentement que none. Il force chaque écriture jusqu'au support, sans aucun cache intermédiaire, ce qui est le comportement le plus sûr et le plus coûteux qui soit.

Ces trois modes ne sont pas les seuls. libvirt en expose six, et la documentation de Red Hat les définit par ce qu'ils font du cache de page de l'hôte et du cache d'écriture du disque :

ModeCe qu'il faitPour qui
nonePas de cache côté hôte, mais l'écriture peut rester dans le cache du disqueInvités à forte activité disque, et compatible migration
writethroughCache côté hôte, mais écriture poussée jusqu'au supportPeu d'invités, faible activité, ou support sans cache d'écriture
writebackCache côté hôte, sans synchronisation immédiateLe plus rapide, au prix du risque en cas de coupure
directsyncComme writethrough, mais contourne le cache de pageQuand la durabilité prime sur tout le reste
unsafeTout est mis en cache et les demandes de synchronisation de l'invité sont ignoréesInstallations jetables, jamais des données qui comptent
defaultLaisse la plateforme déciderCe que vous avez si vous n'écrivez rien

La ligne qui décide souvent est la première, et pas pour sa vitesse : cache='none' est le seul mode que Red Hat présente comme compatible avec la migration. Un choix de performance devient ici un choix d'architecture.

<driver name='qemu' type='qcow2' cache='none' io='native' discard='unmap'/>

cache='none' évite en outre de garder deux fois les mêmes données en mémoire, côté hôte et côté invité. discard='unmap' fait remonter les suppressions de fichiers jusqu'à l'image, qui cesse ainsi de grossir indéfiniment.

Le mode d'entrées-sorties, à ne pas confondre avec le cache

Section intitulée « Le mode d'entrées-sorties, à ne pas confondre avec le cache »

L'attribut io est un réglage distinct, qui décide comment QEMU soumet les opérations, pas ce qu'il en met en cache :

ioMécanisme
nativeAIO du noyau avec entrées-sorties directes : le défaut de Red Hat Virtualization
threadsDes threads en espace utilisateur, défaut historique de RHEL
io_uringL'interface récente du noyau, plus efficace mais dont la maturité dépend des versions en présence

Les deux attributs ne se choisissent pas séparément, et ce n'est pas une affaire de bonnes pratiques : QEMU refuse la combinaison incohérente. Un io='native' posé sur un mode de cache qui passe par le cache de page fait échouer le démarrage, avec un message qui ne laisse aucun doute :

aio=native was specified, but it requires cache.direct=on, which was not specified.

cache.direct=on correspond, côté libvirt, aux modes none et directsync. Retenez donc le couple : io='native' impose un cache qui contourne la page cache. Les modes threads et io_uring, eux, s'accommodent de n'importe quel réglage de cache.

IOThreads : sortir les entrées-sorties du thread principal

Section intitulée « IOThreads : sortir les entrées-sorties du thread principal »

Par défaut, les opérations disque passent par la boucle d'événements principale de QEMU, partagée avec le reste de l'émulation. Les IOThreads sont, selon la documentation libvirt, des « threads de boucle d'événements dédiés » qui traitent les requêtes en bloc à part, « afin d'améliorer la montée en charge, en particulier sur un hôte ou un invité SMP portant de nombreux LUN ».

<domain>
<iothreads>2</iothreads>
<cputune>
<iothreadpin iothread='1' cpuset='5,6'/>
</cputune>
</domain>

<iothreadpin> épingle ensuite un IOThread donné sur des processeurs précis, avec la même syntaxe que le pinning des vCPU. Le gain est réel sur une machine qui porte plusieurs disques actifs ; il est négligeable sur une VM à un seul disque peu sollicité, où ajouter des threads complique sans rien apporter.

L'erreur la plus fréquente est d'appliquer une liste de réglages sans avoir constaté le problème. Trois commandes suffisent à savoir où regarder :

Fenêtre de terminal
virsh domstats ma-vm --vcpu --block --interface
virsh cpu-stats ma-vm --total
virsh domblkstat ma-vm vda

domstats donne le temps processeur consommé, les octets lus et écrits, les paquets réseau. Une VM dont le temps CPU stagne pendant que les écritures disque grimpent n'a pas un problème de processeur, quel que soit le nombre de vCPU que vous lui ajouterez ensuite.

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

  • host-model part du modèle QEMU le plus proche, ici un Skylake pour un processeur de 12e génération, et lui ajoute 117 features : c'est ce qui rend la migration vérifiable.
  • host-passthrough expose les 142 flags réels, et interdit la migration vers un hôte au processeur différent.
  • Sans topologie déclarée, l'invité croit souvent avoir autant de sockets que de vCPU, une machine qui n'existe pas.
  • NUMA ne sert à rien sur un hôte à un seul nœud : vérifiez avant d'appliquer une recette de bi-socket.
  • Les grandes pages travaillent peut-être déjà pour vous, via le THP en mode madvise, sans qu'aucune page statique ne soit réservée.
  • Côté disque, directsync écrit sept fois plus lentement que none sur ce banc : le mode de cache se choisit, il ne se subit pas.
  • Un disque sata sur une VM Linux moderne est une lenteur gratuite : les pilotes virtio sont déjà dans l'invité.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn