Situation généraleComment faire pour accélérer la mémoire allouée en C++?
Une application qui est extrêmement intensive à la fois la bande passante, l'utilisation du processeur et l'utilisation du GPU doit transférer sur les 10-15GB par seconde d'un GPU à l'autre. Il utilise l'API DX11 pour accéder au GPU, donc le téléchargement vers le GPU ne peut se faire qu'avec des tampons nécessitant un mappage pour chaque téléchargement. Le téléchargement se fait par blocs de 25 Mo à la fois, et 16 threads écrivent des tampons simultanément dans les buffers mappés. Il n'y a pas grand-chose à faire à propos de tout cela. Le niveau de concurrence réel des écritures devrait être inférieur, s'il n'y avait pas le bogue suivant.
C'est un poste de travail costaud avec 3 GPU Pascal, un processeur Haswell haut de gamme et une RAM à quatre canaux. Pas beaucoup peut être amélioré sur le matériel. Il est en cours d'exécution d'une édition de bureau de Windows 10.
Le problème réel
Une fois que je passe ~ 50% de charge CPU, quelque chose dans MmPageFault()
(à l'intérieur du noyau Windows, appelé lors de l'accès mémoire qui a été cartographiée dans votre l'espace d'adressage, mais n'a pas encore été commis par le système d'exploitation) se brise horriblement, et la charge de CPU 50% restante est gaspillée sur un spin-lock à l'intérieur MmPageFault()
. Le processeur est utilisé à 100% et les performances de l'application se dégradent complètement.
Je dois supposer que cela est dû à l'immense quantité de mémoire qui doit être allouée au processus chaque seconde et qui est également complètement démappée du processus chaque fois que le tampon DX11 est démappé. En conséquence, il s'agit en fait des milliers d'appels à MmPageFault()
par seconde, se produisant séquentiellement que memcpy()
écrit séquentiellement dans le tampon. Pour chaque page non validée rencontrée.
Si la charge du processeur dépasse 50%, le spin-lock optimiste du noyau Windows protégeant la gestion des pages dégrade complètement les performances.
Considérations
La mémoire tampon est allouée par le conducteur DX11. Rien ne peut être modifié au sujet de la stratégie d'allocation. L'utilisation d'une API mémoire différente et surtout la réutilisation n'est pas possible.
Les appels à l'API DX11 (mappage/démappage des tampons) se font tous à partir d'un seul thread. Les opérations de copie réelles sont potentiellement multi-threadées sur plus de threads que de processeurs virtuels dans le système.
La réduction des besoins en bande passante mémoire n'est pas possible. C'est une application en temps réel. En fait, la limite dure est actuellement la bande passante PCIe 3.0 16x du GPU primaire. Si je pouvais, je devrais déjà pousser plus loin. Il n'est pas possible d'éviter les copies multithread, car il existe des files d'attente producteur-consommateur indépendantes qui ne peuvent pas être fusionnées de manière triviale. La dégradation des performances de spin-lock semble être si rare (parce que le cas d'utilisation pousse si loin) que sur Google, vous ne trouverez pas un seul résultat pour le nom de la fonction spin-lock.
La mise à niveau vers une API qui donne plus de contrôle sur les mappages (Vulkan) est en cours, mais elle ne convient pas comme solution à court terme. Passer à un meilleur noyau OS n'est actuellement pas une option pour la même raison.
La réduction de la charge du processeur ne fonctionne pas non plus; il y a trop de travail qui doit être fait autre que la copie du tampon (généralement triviale et peu coûteuse).
La question
Que peut-on faire?
Je dois réduire de manière significative le nombre d'erreurs de page individuelles. Je connais l'adresse et la taille du buffer qui a été mappé dans mon processus, et je sais aussi que la mémoire n'a pas encore été validée.
Comment puis-je m'assurer que la mémoire est validée avec le moins de transactions possible?
Drapeaux exotiques pour DX11 qui empêcheraient la désaffectation des tampons après le démappage, API Windows pour forcer la validation en une seule transaction, à peu près tout est le bienvenu.
L'état actuel
// In the processing threads
{
DX11DeferredContext->Map(..., &buffer)
std::memcpy(buffer, source, size);
DX11DeferredContext->Unmap(...);
}
il semble que vous êtes à environ 400 M pour tous les 16 threads tous ensemble. Relativement bas. Pouvez-vous vérifier que vous ne dépassez pas cela dans votre application? Quelle est la consommation de mémoire pick là-bas? Je me demande si vous avez une fuite de mémoire. – Serge
La consommation de pointe est d'environ 7-8 Go, mais c'est normal, étant donné que l'ensemble du pipeline de traitement a besoin de> 1s de mémoire tampon pour compenser toutes sortes de goulots d'étranglement. Oui, c'est "seulement" 400Mo, 25 fois par seconde. Et cela fonctionne très bien, jusqu'à ce que la charge du processeur de base dépasse 50% et que les performances de la verrou de rotation passent soudainement de 0 à 40-50% de l'utilisation totale du processeur. Affecte également d'autres processus sur le système en même temps. – Ext3h
1. Quelle est votre mémoire physique? pouvez-vous tuer tous les autres processus actifs? 2. devinez # 2 puisque vous voyez le seuil de 50%, vous pourriez entrer dans quelques problèmes avec hyperthreading. Combien de cœurs physiques avez-vous? 8? Pouvez-vous désactiver l'hyperthreading? Essayez d'exécuter autant de threads qu'il y a de processeurs physiques dans votre cas sur une machine propre. – Serge