La STL doit-elle être évitée dans les grandes applications?

24

Cela peut sembler une question étrange, mais dans mon département, nous avons des problèmes avec la situation suivante:

Nous travaillons ici sur une application serveur, qui s'agrandit de plus en plus, même au moment où nous envisageons de la diviser en différentes parties (fichiers DLL), en chargeant dynamiquement en cas de besoin et en déchargeant par la suite, afin de pouvoir gérer les problèmes de performance.

Mais: les fonctions que nous utilisons passent des paramètres d'entrée et de sortie en tant qu'objets STL, et comme mentionné dans une réponse Stack Overflow , c'est une très mauvaise idée. (Le message contient quelques ± solutions et hacks, mais tout ne semble pas très solide.)

Évidemment, nous pourrions remplacer les paramètres d'entrée / sortie par des types C ++ standard et créer des objets STL à partir de ceux une fois à l'intérieur des fonctions, mais cela pourrait entraîner des baisses de performances.

Est-il correct de conclure que, si vous envisagez de créer une application, qui pourrait devenir si grande qu'un seul PC ne peut plus la gérer, vous ne devez pas du tout utiliser STL comme technologie?

Plus d'informations sur cette question:
il semble y avoir des malentendus sur la question: le problème est le suivant:
mon application utilise une énorme quantité de performances (CPU, mémoire) afin de terminer son travail, et je voudrais diviser ce travail en différentes parties (comme le programme est déjà divisé en plusieurs fonctions), il n'est pas si difficile de créer des DLL à partir de mon application et de mettre certaines des fonctions dans la table d'exportation de ces DLL. Cela entraînerait la situation suivante:

+-----------+-----------+----
| Machine1  | Machine2  | ...
| App_Inst1 | App_Inst2 | ...
|           |           |    
| DLL1.1    | DLL2.1    | ...
| DLL1.2    | DLL2.2    | ...
| DLL1.x    | DLL2.x    | ...
+-----------+-----------+----

App_Inst1 est l'instance de l'application, installée sur Machine1, tandis que App_Inst2 est l'instance de la même application, installée sur Machine2.
DLL1.x est une DLL, installée sur Machine1, tandis que DLL2.x est une DLL, installée sur Machine2.
DLLx.1 couvre la fonction exportée 1.
DLLx.2 couvre la fonction exportée2.

Maintenant, sur Machine1, j'aimerais exécuter function1 et function2. Je sais que cela surchargera Machine1, donc je voudrais envoyer un message à App_Inst2, demandant à cette instance d'application d'exécuter function2.

Les paramètres d'entrée / sortie de function1 et function2 sont des objets STL (C ++ Standard Type Library), et régulièrement je pourrais m'attendre à ce que le client fasse des mises à jour d'App_Inst1, App_Inst2, DLLx.y (mais pas tous, le client peut mettre à niveau Machine1 mais pas Machine2, ou seulement mettre à niveau les applications mais pas les DLL ou vice versa, ...). Évidemment, si l'interface (paramètres d'entrée / sortie) change, le client est obligé de procéder à des mises à niveau complètes.

Cependant, comme mentionné dans l'URL StackOverflow référencée, une simple recompilation d'App_Inst1 ou de l'une des DLL peut entraîner l'effondrement de l'ensemble du système, d'où mon titre d'origine de cet article, déconseillant l'utilisation de STL (C ++ Standard Template Library) pour les grandes applications.

J'espère que par la présente, j'ai dissipé certaines questions / doutes.

Dominique
la source
44
Êtes-vous sûr de rencontrer des problèmes de performances en raison de la taille de votre exécutable ? Pouvez-vous ajouter quelques détails sur le fait qu'il soit réaliste de supposer que tous vos logiciels sont compilés avec le même compilateur (par exemple en une seule fois sur le serveur de build) ou si vous voulez réellement vous scinder en équipes indépendantes?
nvoigt
5
Fondamentalement, vous avez besoin d'une personne dont le travail dédié est "build manager" et "release manager", pour vous assurer que tous les projets C ++ sont compilés sur la même version de compilateur et avec des paramètres de compilateur C ++ identiques, compilés à partir d'un instantané cohérent (version) de la source code, etc. Généralement, cela est pris en charge sous la bannière de «l'intégration continue». Si vous effectuez une recherche en ligne, vous trouverez de nombreux articles et outils. Les pratiques obsolètes peuvent se renforcer elles-mêmes - une pratique obsolète peut entraîner le dépassement de toutes les pratiques.
rwong
8
La réponse acceptée dans la question liée indique que le problème concerne les appels C ++ en général. Donc, "C ++ mais pas STL" n'aide pas, vous devez utiliser le C nu pour être sûr (mais aussi voir les réponses, la sérialisation est probablement une meilleure solution).
Frax
52
chargement dynamique en cas de besoin et déchargement par la suite, afin de pouvoir gérer les problèmes de performances Quels «problèmes de performances»? Je ne connais aucun problème autre que l'utilisation de trop de mémoire qui peut être corrigée en déchargeant des choses comme les DLL de la mémoire - et si tel est le problème, la solution la plus simple consiste à simplement acheter plus de RAM. Avez-vous profilé votre application pour identifier les goulots d'étranglement des performances réelles? Parce que cela ressemble à un problème XY - vous avez des "problèmes de performances" non spécifiés et quelqu'un a déjà décidé de la solution.
Andrew Henle
4
@MaxBarraclough "La STL" est parfaitement acceptée comme nom alternatif pour les conteneurs et les fonctions modèles qui ont été inclus dans la bibliothèque standard C ++. En fait, les directives de base C ++, écrites par Bjarne Stroustrup et Herb Sutter, font à plusieurs reprises référence à "la STL" quand elles en parlent. Vous ne pouvez pas obtenir une source bien plus fiable que cela.
Sean Burton

Réponses:

110

Il s'agit d'un problème XY classique et froid.

Votre vrai problème, ce sont les problèmes de performances. Cependant, votre question indique clairement que vous n'avez effectué aucun profilage ni aucune autre évaluation de l'origine des problèmes de performances. Au lieu de cela, vous espérez que le fractionnement de votre code en DLL résoudra comme par magie le problème (ce qu'il ne fera pas, pour mémoire), et maintenant vous vous inquiétez d'un aspect de cette non-solution.

Au lieu de cela, vous devez résoudre le vrai problème. Si vous avez plusieurs exécutables, vérifiez lequel est à l'origine du ralentissement. Pendant que vous y êtes, assurez-vous que c'est votre programme qui prend tout le temps de traitement, et non un pilote Ethernet mal configuré ou quelque chose comme ça. Et après cela, commencez à profiler les différentes tâches de votre code. La minuterie de haute précision est votre amie ici. La solution classique consiste à surveiller les temps de traitement moyens et les pires cas pour un morceau de code.

Lorsque vous avez des données, vous pouvez déterminer comment résoudre le problème, puis déterminer où optimiser.

Graham
la source
54
"Au lieu de cela, vous espérez que la division de votre code en DLL résoudra comme par magie le problème (ce qu'il ne fera pas, pour mémoire)" - +1 pour cela. Votre système d'exploitation implémente presque certainement la pagination de la demande qui atteint exactement le même résultat que les fonctionnalités de chargement et de déchargement dans les DLL, uniquement automatiquement plutôt que de nécessiter une intervention manuelle. Même si vous êtes mieux à même de prédire combien de temps un morceau de code devrait rester une fois utilisé que le système de mémoire virtuelle du système d'exploitation (ce qui est en fait peu probable), le système d'exploitation mettra en cache le fichier DLL et annulera vos efforts de toute façon .
Jules
@Jules See update - ils ont précisé que les DLL n'existent que sur des machines distinctes, donc je peux peut-être voir cette solution fonctionner. Il y a maintenant des frais de communication cependant, si difficile à être sûr.
Izkata
2
@Izkata - ce n'est pas encore tout à fait clair, mais je pense que ce qui est décrit, c'est qu'ils veulent sélectionner dynamiquement (en fonction de la configuration d'exécution) une version de chaque fonction qui est locale ou distante. Mais aucune partie du fichier EXE qui n'est jamais utilisée sur une machine donnée ne sera simplement jamais chargée en mémoire, donc l'utilisation de DLL à cette fin n'est pas nécessaire. Il vous suffit d'inclure les deux versions de toutes les fonctions dans la version standard et de créer une table de pointeurs de fonction (ou d'objets appelables C ++, ou la méthode que vous préférez) pour appeler la version appropriée de chaque fonction.
Jules
38

Si vous devez diviser un logiciel entre plusieurs machines physiques, vous devez avoir une certaine forme de sérialisation lors du transfert de données entre machines car seulement dans certains cas, vous pouvez simplement envoyer le même binaire exact entre machines. La plupart des méthodes de sérialisation n'ont aucun problème à gérer les types STL, donc ce cas ne m'inquiète pas.

Si vous devez diviser une application en bibliothèques partagées (DLL) (avant de le faire pour des raisons de performances, vous devez vraiment vous assurer que cela résoudrait réellement vos problèmes de performances) la transmission d'objets STL peut être un problème mais ne doit pas l'être. Comme le lien que vous avez fourni décrit déjà, le passage d'objets STL fonctionne si vous utilisez le même compilateur et les mêmes paramètres de compilateur. Si les utilisateurs fournissent les DLL, vous ne pourrez peut-être pas facilement compter sur cela. Cependant, si vous fournissez toutes les DLL et compilez tout ensemble, vous pourrez peut-être compter dessus et utiliser des objets STL au-delà des limites des DLL devient tout à fait possible. Vous devez toujours faire attention à vos paramètres de compilation afin de ne pas obtenir plusieurs tas différents si vous passez la propriété de l'objet, bien que ce ne soit pas un problème spécifique à la STL.

Pierre Andersson
la source
1
Oui, et en particulier la partie sur le passage des objets alloués à travers les limites DLL / so. De manière générale, la seule façon d'éviter absolument le problème des allocateurs multiples est de s'assurer que la DLL / so (ou la bibliothèque!) Qui a alloué la structure la libère également. C'est pourquoi vous voyez beaucoup, beaucoup d'API de style C écrites de cette façon: une API libre explicite pour chaque API renvoyant un tableau / structure alloué. Le problème supplémentaire avec STL est que l'appelant peut s'attendre à pouvoir modifier la structure de données complexe passée (ajouter / supprimer des éléments) et cela ne peut pas non plus être autorisé. Mais c'est difficile à appliquer.
davidbak
1
Si je devais diviser une application comme celle-ci, j'utiliserais probablement COM, mais cela augmente généralement la taille du code car chaque composant apporte ses propres bibliothèques C et C ++ (qui peuvent être partagées lorsqu'elles sont identiques, mais peuvent diverger si nécessaire, Par exemple, pendant les transitions, je ne suis pas convaincu que ce soit la solution appropriée au problème du PO
Simon Richter
2
À titre d'exemple spécifique, le programme est très susceptible quelque part de vouloir envoyer du texte à une autre machine. À un moment donné, il y aura un pointeur vers certains caractères impliqués dans la représentation de ce texte. Vous ne pouvez absolument pas simplement transmettre les bits de ces pointeurs et vous attendre à un comportement défini du côté de la réception
Caleth
20

Nous travaillons ici sur une application serveur, qui grandit de plus en plus, même au moment où nous envisageons de la diviser en différentes parties (DLL), de charger dynamiquement en cas de besoin et de décharger ensuite, afin de pouvoir gérer les les problèmes de performance

La RAM est bon marché et donc le code inactif est bon marché. Le chargement et le déchargement de code (en particulier le déchargement) est un processus fragile et il est peu probable qu'il ait un effet significatif sur les performances de vos programmes sur du matériel de bureau / serveur moderne.

Le cache est plus cher, mais cela n'affecte que le code récemment actif, pas le code qui est en mémoire inutilisé.

En général, les programmes dépassent leurs ordinateurs en raison de la taille des données ou du temps processeur, et non de la taille du code. Si la taille de votre code devient si grande qu'elle cause des problèmes majeurs, vous voudrez probablement chercher pourquoi cela se produit en premier lieu.

Mais: les fonctions que nous utilisons passent des paramètres d'entrée et de sortie en tant qu'objets STL, et comme mentionné dans cette URL StackOverflow, c'est une très mauvaise idée.

Cela devrait être correct tant que les DLL et l'exécutable sont tous construits avec le même compilateur et liés dynamiquement à la même bibliothèque d'exécution C ++. Il s'ensuit que si l'application et ses DLL associées sont construites et déployées comme une seule unité, cela ne devrait pas poser de problème.

Là où cela peut devenir un problème, c'est lorsque les bibliothèques sont construites par différentes personnes ou peuvent être mises à jour séparément.

Est-il permis de conclure que, si vous envisagez de créer une application, qui pourrait devenir si grande qu'un seul PC ne peut plus la gérer, vous ne devez pas du tout utiliser STL en tant que technologie?

Pas vraiment.

Une fois que vous avez commencé à répartir une application sur plusieurs machines, vous avez toute une série de considérations sur la façon dont vous transmettez les données entre ces machines. Les détails sur l'utilisation de types STL ou de types plus basiques risquent d'être perdus dans le bruit.

Peter Green
la source
2
Le code inactif n'est probablement jamais chargé dans la RAM en premier lieu. La plupart des systèmes d'exploitation ne chargent les pages des exécutables que si elles sont réellement nécessaires.
Jules
1
@Jules: Si le code mort est mélangé avec du code en direct (avec une taille de page = 4 k de granularité), il sera mappé + chargé. Le cache fonctionne sur une granularité beaucoup plus fine (64B), il est donc toujours vrai que les fonctions inutilisées ne font pas beaucoup de mal. Cependant, chaque page a besoin d'une entrée TLB et (contrairement à la RAM) qui est une ressource d'exécution rare. (Les mappages basés sur des fichiers n'utilisent généralement pas d'énormes pages, du moins pas sur Linux; Une énorme page fait 2 Mo sur x86-64, vous pouvez donc couvrir beaucoup plus de code ou de données sans obtenir de TLB manquant avec d'énormes pages.)
Peter Cordes
1
Ce que @PeterCordes note: Alors, assurez-vous d'utiliser "PGO" dans le cadre de votre processus de génération pour publication!
JDługosz
13

Non, je ne pense pas que cette conclusion s'ensuit. Même si votre programme est distribué sur plusieurs machines, il n'y a aucune raison que l'utilisation de la STL en interne vous oblige à l'utiliser dans la communication inter-modules / processus.

En fait, je dirais que vous devriez séparer la conception des interfaces externes de l'implémentation interne dès le départ, car les premières seront plus solides / difficiles à changer par rapport à ce qui est utilisé en interne

Bwmat
la source
7

Vous manquez le point de cette question.

Il existe essentiellement deux types de DLL. Le vôtre et celui de quelqu'un d'autre. Le «problème STL» est que vous et eux n'utilisez peut-être pas le même compilateur. Évidemment, ce n'est pas un problème pour votre propre DLL.

MSalters
la source
5

Si vous créez les DLL à partir de la même arborescence source en même temps avec le même compilateur et les mêmes options de génération, cela fonctionnera correctement.

Cependant, la façon "Windows" de diviser une application en plusieurs parties dont certaines sont réutilisables est les composants COM . Ceux-ci peuvent être petits (contrôles individuels ou codecs) ou grands (IE est disponible en tant que contrôle COM, dans mshtml.dll).

chargement dynamique en cas de besoin et déchargement après

Pour une application serveur, cela va probablement avoir une efficacité terrible ; ce n'est vraiment viable que si vous avez une application qui passe par plusieurs phases sur une longue période de temps afin que vous sachiez quand quelque chose ne sera plus nécessaire. Cela me rappelle les jeux DOS utilisant le mécanisme de superposition.

En outre, si votre système de mémoire virtuelle fonctionne correctement, il s'en occupera en paginant les pages de codes inutilisées.

pourrait devenir si grand qu'un seul PC ne peut plus le gérer

Achetez un PC plus grand.

N'oubliez pas qu'avec la bonne optimisation, un ordinateur portable peut surpasser un cluster hadoop.

Si vous avez vraiment besoin de plusieurs systèmes, vous devez réfléchir très attentivement à la frontière entre eux, car c'est là que se situe le coût de sérialisation. C'est là que vous devriez commencer à regarder des frameworks comme MPI.

pjc50
la source
1
"ce n'est vraiment viable que si vous avez une application qui passe par plusieurs phases sur une longue période de temps afin que vous sachiez quand quelque chose ne sera plus nécessaire" - même alors, il est peu probable que cela aide beaucoup, car le système d'exploitation mettre en cache les fichiers DLL, qui finiront probablement par prendre plus de mémoire que simplement inclure les fonctions directement dans votre exécutable de base. Les superpositions ne sont utiles que dans les systèmes sans mémoire virtuelle, ou lorsque l'espace d'adressage virtuel est le facteur limitant (je suppose que cette application est 64 bits, pas 32 ...).
Jules
3
"Achetez un PC plus grand" +1. Vous pouvez désormais acquérir des systèmes avec plusieurs téraoctets de RAM. Vous pouvez en louer un sur Amazon pour moins que le taux horaire d'un seul développeur. Combien de temps de développeur allez-vous consacrer à l'optimisation de votre code pour réduire l'utilisation de la mémoire?
Jules
2
Le plus gros problème auquel j'ai été confronté avec "acheter un PC plus gros" était lié à la question "dans quelle mesure votre application évoluera-t-elle?". Ma réponse était "combien êtes-vous prêt à dépenser pour un test? Parce que je m'attends à ce qu'il évolue si loin que la location d'une machine appropriée et la mise en place d'un test correctement volumineux coûteront des milliers de dollars. Aucun de nos clients n'est même proche à ce qu'un PC à processeur unique peut faire. ". De nombreux programmeurs plus âgés n'ont aucune idée réaliste de la quantité de PC qui ont grandi; la carte vidéo seule dans les PC modernes est un supercalculateur selon les normes du 20e siècle.
MSalters
Composants COM? Dans les années 1990 peut-être, mais maintenant?
Peter Mortensen
@MSalters - à droite ... toute personne ayant des questions sur la mesure dans laquelle une application peut évoluer sur un seul PC doit consulter les spécifications du type d'instance Amazon EC2 x1e.32xlarge - 72 cœurs de processeur physique au total dans la machine fournissant 128 cœurs virtuels à 2,3 GHz (éclatable à 3,1 GHz), potentiellement jusqu'à 340 Go / s de bande passante mémoire (selon le type de mémoire installée, ce qui n'est pas décrit dans les spécifications), et 3,9 To de RAM. Il a suffisamment de cache pour exécuter la plupart des applications sans jamais toucher à la RAM principale. Même sans GPU, il est aussi puissant qu'un cluster de superordinateurs à 500 nœuds de 2000.
Jules
0

Nous travaillons ici sur une application serveur, qui s'agrandit de plus en plus, même au moment où nous envisageons de la diviser en différentes parties (fichiers DLL), en chargeant dynamiquement en cas de besoin et en déchargeant par la suite, afin de pouvoir gérer les problèmes de performance.

La première partie est logique (fractionnement de l'application sur différentes machines, pour des raisons de performances).

La deuxième partie (chargement et déchargement des bibliothèques) n'a pas de sens, car c'est un effort supplémentaire à faire, et cela n'améliorera pas (vraiment) les choses.

Le problème que vous décrivez est mieux résolu avec des machines de calcul dédiées, mais celles-ci ne devraient pas fonctionner avec la même application (principale).

La solution classique ressemble à ceci:

[user] [front-end] [machine1] [common resources]
                   [machine2]
                   [machine3]

Entre les machines frontales et de calcul, vous pouvez avoir des choses supplémentaires, telles que des équilibreurs de charge et une surveillance des performances, et le traitement spécialisé sur des machines dédiées est bon pour la mise en cache et les optimisations de débit.

Cela n'implique en aucun cas un chargement / déchargement supplémentaire des DLL, ni rien à voir avec la STL.

Autrement dit, utilisez STL en interne selon les besoins et sérialisez vos données entre les éléments (voir grpc et tampons de protocole et le type de problèmes qu'ils résolvent).

Cela dit, avec les informations limitées que vous avez fournies, cela ressemble au problème xy classique (comme l'a dit @Graham).

utnapistim
la source