Il y a quelque temps (à l'automne 2016), lors du développement d'une nouvelle version de la plateforme technologique 1C:Entreprise, la question de la prise en charge de la nouvelle norme s'est posée au sein de l'équipe de développement. dans notre code. La transition vers la nouvelle norme, comme nous le pensions, nous permettrait d'écrire de nombreuses choses de manière plus élégante, simple et fiable, facilitant la maintenance et le support du code. Et dans cette traduction, il n'y a apparemment rien d'extraordinaire, si ce n'est l'ampleur de la base de code et les caractéristiques spécifiques de notre code.
Pour ceux qui ne le savent pas, 1C:Entreprise est un environnement de développement rapide d'applications métiers multiplateformes et un runtime pour leur exécution sur différents systèmes d'exploitation et SGBD. En gros, le produit comprend :
- , fonctionnant sous Windows et Linux
- , interagissant avec le serveur via http(s) ou via son propre protocole binaire, fonctionnant sous Windows, Linux, macOS
- , fonctionnant dans les navigateurs Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari (écrit en JavaScript)
- Un environnement de développement (), fonctionnant sous Windows, Linux, macOS
- des serveurs d'applications, fonctionnent sous Windows, Linux, macOS
- , se connectant au serveur via http(s), fonctionnant sur des appareils mobiles sous Android, iOS, Windows
- — un framework pour la création d'applications mobiles hors ligne avec possibilité de synchronisation, fonctionnant sous Android, iOS, Windows
- Environnement de développement , écrit en Java
- Serveur
Nous essayons au maximum d'écrire un seul code pour différents systèmes d'exploitation — la base de code du serveur est commune à 99%, celle du client — environ 95%. La plateforme technologique 1C:Entreprise est principalement écrite en C++ et voici les caractéristiques approximatives du code :
- 10 millions de lignes de code C++
- 14 000 fichiers,
- 60 000 classes,
- 500 000 méthodes.
Et tout cela devait être traduit vers C++14. Nous allons aujourd'hui vous raconter comment nous avons fait cela et les défis que nous avons rencontrés en cours de route.

Avertissement
Tout ce qui est écrit ci-dessous concernant le fonctionnement lent/rapide et la consommation (non) significative de mémoire des réalisations des classes standard dans diverses bibliothèques signifie une chose : c'est valable POUR NOUS. Il est tout à fait possible que pour vos tâches, les réalisations standard conviennent le mieux. Nous, en revanche, nous appuyons sur nos propres tâches : nous prenons des données typiques pour nos clients, nous les testons avec des scénarios typiques, nous examinons les performances, le volume de mémoire consommée, etc., et nous analysons si ces résultats nous conviennent à nous et à nos clients ou non. Et nous agissons en conséquence.
Ce que nous avions
Au départ, nous avons écrit le code de la plateforme 1C:Enterprise 8 sur Microsoft Visual Studio. Le projet a commencé au début des années 2000 et nous n'avions qu'une version pour Windows. Naturellement, depuis lors, le code a évolué activement, de nombreux mécanismes ont été complètement réécrits. Mais le code était écrit selon la norme de 1998, et par exemple, nos crochets droits étaient séparés par des espaces pour permettre une compilation réussie, comme ceci :
vector<vector> IntV;En 2006, avec la sortie de la version 8.1 de la plateforme, nous avons commencé à prendre en charge Linux et avons migré vers une bibliothèque standard tierce . L'une des raisons de cette migration était la gestion des chaînes larges. Dans notre code, nous utilisons massivement std::wstring, basé sur le type wchar_t. Sa taille sous Windows est de 2 octets, tandis qu'elle est par défaut de 4 octets sous Linux. Cela entraînait une incompatibilité de nos protocoles binaires entre le client et le serveur, ainsi que divers types de données persistantes. Avec les options de gcc, il est possible d'indiquer que la taille de wchar_t lors de la compilation doit également être de 2 octets, mais alors, on peut oublier l'utilisation de la bibliothèque standard du compilateur, car elle utilise glibc, qui à son tour est compilée pour un wchar_t de 4 octets. D'autres raisons incluaient une implémentation de meilleure qualité des classes standard, le support des tables de hachage et même l'émulation de la sémantique de déplacement à l'intérieur des conteneurs, que nous utilisions activement. Une autre raison, comme on dit, last but not least, était la performance des chaînes. Nous avions notre propre classe pour les chaînes, car en raison de la spécificité de notre logiciel, les opérations sur les chaînes sont utilisées de manière très intensive et sont critiques pour nous.
Notre chaîne est fondée sur des idées d'optimisation des chaînes qui ont été exprimées dès le début des années 2000 . Plus tard, lorsque Alexandrescu a travaillé chez Facebook, à sa demande, une ligne fonctionnant sur des principes similaires a été utilisée dans le moteur de Facebook (voir la bibliothèque ).
. Dans notre ligne, nous avons utilisé deux technologies principales d'optimisation :
- Pour les valeurs courtes, un tampon interne au sein même de l'objet string est utilisé (ne nécessitant pas d'allocation mémoire supplémentaire).
- Pour toutes les autres, un mécanisme est utilisé : . La valeur de la chaîne est stockée à un seul endroit, lors de l'affectation/modification, un compteur de références est utilisé.
Pour accélérer la compilation de la plateforme, nous avons exclu de notre version de STLPort l'implémentation de stream (que nous n'utilisions pas), ce qui nous a donné un gain de compilation d'environ 20 %. Par la suite, nous avons dû utiliser de manière limitée . Boost utilise activement le stream, notamment dans ses API de service (par exemple, pour la journalisation), ce qui nous a obligés à le modifier en excluant l'utilisation de stream. Cela, à son tour, compliquait notre transition vers de nouvelles versions de Boost.
La troisième voie
Lors de la transition vers la norme C++14, nous avons envisagé les options suivantes :
- Faire fonctionner notre STLPort modifié sur la norme C++14. L'option est très compliquée, car le support de STLPort a été interrompu en 2010, et nous aurions dû porter tout son code nous-mêmes.
- Passer à une autre implémentation de STL, compatible avec C++14. Il est extrêmement souhaitable que cette implémentation soit compatible avec Windows et Linux.
- Utiliser la bibliothèque intégrée au compilateur approprié lors de la compilation pour chaque OS.
La première option a été immédiatement rejetée en raison de l'ampleur des travaux.
Nous avons réfléchi pendant un certain temps à la deuxième option ; comme candidat, nous avons envisagé , mais à l'époque, il ne fonctionnait pas sous Windows. Pour porter libc++ sur Windows, il aurait fallu effectuer beaucoup de travail — par exemple, écrire nous-mêmes tout ce qui concerne les flux, la synchronisation des threads et l'atômisme, car dans libc++, ces domaines utilisaient .
Et nous avons choisi la troisième voie.
Transition
Ainsi, nous devions remplacer l'utilisation de STLPort par les bibliothèques des compilateurs correspondants (Visual Studio 2015 pour Windows, gcc 7 pour Linux, clang 8 pour macOS).
Heureusement, notre code a été principalement écrit selon les directives et n'utilisait pas de trucs astucieux, donc la migration vers les nouvelles bibliothèques s'est déroulée de manière relativement fluide, grâce à des scripts remplaçant dans les fichiers sources les noms de types, de classes, d'espaces de noms et d'inclusions. La migration a concerné 10 000 fichiers sources (sur 14 000). wchar_t a été remplacé par char16_t ; nous avons décidé d'abandonner l'utilisation de wchar_t car char16_t occupe 2 octets sur tous les systèmes d'exploitation et ne compromet pas la compatibilité du code entre Windows et Linux.
Il n'a pas manqué de petites aventures. Par exemple, dans STLPort, un itérateur pouvait être implicitement transformé en pointeur sur un élément, et à certains endroits de notre code, cela était utilisé. Dans les nouvelles bibliothèques, cela n'était plus possible, et ces endroits devaient être analysés et réécrits manuellement.
Ainsi, la migration du code est terminée, le code se compile pour tous les systèmes d'exploitation. Il est temps de tester.
Les tests après la transition ont montré une baisse de performance (jusqu'à 20-30% à certains endroits) et une augmentation de la consommation de mémoire (jusqu'à 10-15%) par rapport à l'ancienne version du code. Cela était, en particulier, lié à un fonctionnement sous-optimal des chaînes de caractères standard. Nous avons donc dû réutiliser notre propre chaîne, légèrement améliorée.
Une caractéristique intéressante de l'implémentation des conteneurs dans les bibliothèques intégrées a également été mise au jour : les std::map et std::set vides (sans éléments) des bibliothèques intégrées allouent de la mémoire. Dans notre cas, en raison des particularités de l'implémentation, un grand nombre de conteneurs vides de ce type sont créés à certains endroits du code. Les conteneurs standards allouent un peu de mémoire, pour un élément racine, mais cela s'est avéré critique pour nous - dans plusieurs scénarios, nous avons constaté une perte significative de performance et une augmentation de la consommation de mémoire (par rapport à STLPort). Nous avons donc remplacé dans notre code ces deux types de conteneurs des bibliothèques intégrées par leur implémentation de Boost, où ces conteneurs n'avaient pas cette particularité, et cela a résolu le problème de ralentissement et d'augmentation de la consommation de mémoire.
Comme cela arrive souvent après des changements à grande échelle dans de grands projets, la première itération des sources a rencontré quelques problèmes, et ici le support des itérateurs de débogage dans l'implémentation Windows s'est révélé très utile. Pas à pas, nous avons avancé, et au printemps 2017 (version 8.3.11 1C:Enterprise), la migration a été achevée.
Résultats
Le passage à la norme C++14 a pris environ 6 mois. La majeure partie du temps, un seul développeur (très qualifié) a travaillé sur le projet, et à la phase finale, des membres des équipes responsables de domaines spécifiques se sont joints — UI, serveur de cluster, outils de développement et d'administration, etc.
Cette transition a considérablement simplifié notre travail de migration vers les dernières versions de la norme. Ainsi, la version 1C:Entreprise 8.3.14 (en cours de développement, la sortie est prévue pour le début de l'année prochaine) est déjà traduite dans cette norme. .
Après la migration, les développeurs ont eu plus de possibilités. Autrefois, nous avions notre propre version modifiée de la STL et un seul espace de noms std, maintenant notre espace de noms std contient les classes standards des bibliothèques intégrées du compilateur, dans l'espace de noms stdx – nos chaînes et conteneurs optimisés pour nos tâches, et dans boost – la dernière version de boost. Et le développeur utilise les classes qui conviennent le mieux à ses besoins.
L'implémentation « native » des constructeurs de déplacement () pour plusieurs classes aide également au développement. Si une classe a un constructeur de déplacement et que cette classe est placée dans un conteneur, la STL optimise la copie des éléments à l'intérieur du conteneur (par exemple, lorsque le conteneur est agrandi et qu'il faut modifier la capacité et réallouer la mémoire).
Une cuillère de cendre
Le résultat peut-être le plus désagréable (mais non critique) de la migration — nous avons constaté une augmentation du volume , et le résultat complet de la construction avec tous les fichiers intermédiaires prend désormais entre 60 et 70 Go. Ce comportement est lié aux caractéristiques des bibliothèques standard modernes, qui sont devenues moins critiques quant au volume des fichiers de service générés. Cela n'affecte pas le fonctionnement de l'application compilée, mais entraîne certains désagréments dans le développement, notamment une augmentation du temps de compilation. Les exigences en matière d'espace disque libre sur les serveurs de build et sur les machines des développeurs augmentent également. Nos développeurs travaillent parallèlement sur plusieurs versions de la plateforme, et des centaines de gigaoctets de fichiers intermédiaires créent parfois des difficultés au travail. Ce problème est désagréable, mais non critique, et nous avons jusqu'à présent reporté sa solution. L'une des options envisagées pour sa résolution est la technique (elle est notamment utilisée par Google dans le développement du navigateur Chrome).
Source : habr.com
