Système flexible pour tester et collecter des métriques de programme en utilisant la suite de tests LLVM comme exemple

Présentation


La plupart des développeurs ont clairement entendu parler de certains développements open source assez importants tels que le système LLVM et le compilateur clang. Cependant, LLVM n'est plus seulement le système lui-même pour créer des compilateurs, mais aussi un vaste écosystème qui comprend de nombreux projets pour résoudre divers problèmes qui surviennent à n'importe quelle étape de la création du compilateur (généralement, chaque projet de ce type a son propre référentiel séparé). Une partie de l'infrastructure comprend naturellement des outils de test et d'analyse comparative, lors du développement d'un compilateur, son efficacité est un indicateur très important. L'un de ces projets individuels d'infrastructure de test LLVM est la suite de tests ( documentation officielle ).

Suite de tests LLVM


À première vue sur le référentiel de la suite de tests, il semble que ce ne soit qu'un ensemble de références en C / C ++, mais ce n'est pas entièrement vrai. En plus du code source des programmes sur lesquels les mesures de performances seront effectuées, la suite de tests comprend une infrastructure flexible pour la construction, l'exécution et la collecte de métriques. Par défaut, il collecte les métriques suivantes: temps de compilation, temps d'exécution, temps de liaison, taille du code (en sections).

La suite de tests est naturellement utile pour tester et comparer les compilateurs, mais elle peut également être utilisée pour d'autres tâches de recherche où une base de code C / C ++ est nécessaire. Ceux qui ont déjà tenté de faire quelque chose dans le domaine de l'analyse des données, je pense, ont été confrontés au problème du manque et de la fragmentation des données sources. Une suite de tests, bien qu'elle ne se compose pas d'un grand nombre d'applications, mais dispose d'un mécanisme de collecte de données unifié. Ajouter vos propres applications à la collection, collecter les métriques nécessaires à votre tâche particulière est très simple. Par conséquent, à mon avis, la suite de tests (en plus des tâches principales de test et d'analyse comparative) est une bonne option pour un projet de base, sur la base de laquelle vous pouvez créer votre propre collection de données pour des tâches où vous devez analyser certaines fonctionnalités du code de programme ou certaines caractéristiques des programmes.

Structure de la suite de tests LLVM


test-suite |----CMakeLists.txt //  CMake ,   ,  | //   .. | |---- cmake | |---- .modules //        , | //   API    | |---- litsupport //  Python,      test-suite, | //    lit (  LLVM) | |---- tools //   :    | //     (    | // ),    .. | | //     | |---- SingleSource //   ,       | // .        . | |---- MultiSource //   ,      | //  .        | //  . | |---- MicroBenchmarks // ,   google-benchmark.   | //  ,    ,  | //       | |---- External //    ,     test-suite,  | // ,     (  ) | // -    

La structure est simple et directe.

Principe de fonctionnement


Comme vous pouvez le voir, CMake et un format spécial de test éclairé sont responsables de tout le travail de description de l'assemblage, du lancement et de la collecte des métriques.

Si nous le considérons de manière très abstraite, il est clair que le processus de benchmarking utilisant ce système semble simple et très prévisible:


À quoi cela ressemble-t-il plus en détail? Dans cet article, je voudrais m'attarder sur le rôle exact de CMake dans l'ensemble du système et sur le seul fichier que vous devez écrire si vous souhaitez ajouter quelque chose à ce système.

1. Création d'applications de test.

En tant que système de construction, il est devenu la norme de facto pour les programmes CMake C / C ++. CMake configure le projet et génère des fichiers make, ninja, etc. en fonction des préférences de l'utilisateur. pour la construction directe.
Cependant, dans la suite de tests, CMake génère non seulement des règles sur la façon de créer des applications, mais configure également les tests eux-mêmes.

Après le démarrage de CMake, un autre fichier (avec l'extension .test) sera écrit dans le répertoire de construction avec une description de la façon dont l'application doit être exécutée et vérifiée pour être correcte.

Exemple du fichier .test le plus standard

 RUN: cd <some_path_to_build_directory>/MultiSource/Benchmarks/Prolangs-C/football ; <some_path_to_build_directory>/MultiSource/Benchmarks/Prolangs-C/football/football VERIFY: cd <some_path_to_build_directory>/MultiSource/Benchmarks/Prolangs-C/football ; <some_path_to_build_directory>/tools/fpcmp %o football.reference_output 

Le fichier avec l'extension .test peut contenir les sections suivantes:

  • PREPARE - décrit toutes les actions qui doivent être effectuées avant de lancer l'application, très similaire à la méthode Before existant dans différents cadres de tests unitaires;
  • RUN - décrit comment exécuter l'application;
  • VERIFY - décrit comment vérifier le bon fonctionnement de l'application;
  • METRIQUE - décrit les métriques qui doivent être collectées en plus dans la norme.

N'importe laquelle de ces sections peut être omise.

Mais puisque ce fichier est généré automatiquement, il se trouve dans le fichier CMake du benchmark qui décrit: comment obtenir les fichiers objets, comment les assembler dans l'application, puis ce qui doit être fait avec cette application.

Pour une meilleure compréhension du comportement par défaut et de sa description, considérons un exemple de certains CMakeLists.txt

 list(APPEND CFLAGS -DBREAK_HANDLER -DUNICODE-pthread) #      (         ..     CMak,       ) list(APPEND LDFLAGS -lstdc++ -pthread) #       

Les indicateurs peuvent être définis en fonction de la plate-forme, le fichier DetectArchitecture est inclus dans les modules cmake de la suite de tests, qui détermine la plate-forme cible sur laquelle les tests sont exécutés, vous pouvez donc simplement utiliser les données déjà collectées. D'autres données sont également disponibles: système d'exploitation, ordre des octets, etc.

 if(TARGET_OS STREQUAL "Linux") list(APPEND CPPFLAGS -DC_LINUX) endif() if(NOT ARCH STREQUAL "ARM") if(ENDIAN STREQUAL "little") list(APPEND CPPFLAGS -DFPU_WORDS_BIGENDIAN=0) endif() if(ENDIAN STREQUAL "big") list(APPEND CPPFLAGS -DFPU_WORDS_BIGENDIAN=1) endif() endif() 

En principe, cette partie ne devrait pas être nouvelle pour les personnes qui ont au moins une fois vu ou écrit un simple fichier CMake. Naturellement, vous pouvez utiliser les bibliothèques, les construire vous-même, en général, utiliser tous les moyens fournis par CMake afin de décrire le processus de construction de votre application.

Et puis vous devez vous assurer de la génération du fichier .test. Quels outils l'interface tets-suite fournit-elle pour cela?

Il existe 2 macros de base llvm_multisource et llvm_singlesource , qui sont suffisantes pour la plupart des cas triviaux.

  • llvm_multisource est utilisé si l'application se compose de plusieurs fichiers. Si vous ne passez pas les fichiers de code source en tant que paramètres lors de l'appel de cette macro dans votre CMake, tous les fichiers de code source situés dans le répertoire en cours seront utilisés comme base pour la construction. En fait, des modifications sont actuellement en cours dans l'interface de cette macro dans la suite de tests, et la méthode décrite de transfert de fichiers source en tant que paramètres de macro est la version actuelle située dans la branche principale. Auparavant, il y avait un autre système: les fichiers avec le code source devaient être écrits dans la variable Source (comme c'était le cas dans la version 7.0), et la macro n'acceptait aucun paramètre. Mais la logique de base de la mise en œuvre est restée la même.
  • llvm_singlesource considère que chaque fichier .c / .cpp est une référence distincte et pour chacun recueille un fichier exécutable distinct.

Par défaut, les deux macros décrites ci-dessus pour lancer une application intégrée génèrent une commande qui appelle simplement cette application. Et la vérification de l'exactitude se produit en raison de la comparaison avec la sortie attendue située dans le fichier avec l'extension .reference_output (également avec les suffixes possibles .reference_output.little-endian, .reference_output.big-endian).

Si cela vous convient, c'est tout simplement génial, une ligne supplémentaire (appelant llvm_multisource ou llvm_singlesource) vous suffit pour démarrer l'application et obtenir les métriques suivantes: taille du code (en sections), temps de compilation, temps de lien, temps d'exécution.

Mais, bien sûr, cela arrive rarement aussi bien. Vous devrez peut-être modifier une ou plusieurs étapes. Et cela est également possible à l'aide d'actions simples. La seule chose dont vous devez vous souvenir est que si vous redéfinissez une certaine étape, vous devez décrire toutes les autres (même si l'algorithme par défaut de leur travail est satisfait, ce qui, bien sûr, est un peu bouleversant).

Il existe des macros dans l'API pour décrire les actions à chaque étape.

Il n'y a rien à écrire sur la macro llvm_test_prepare pour la phase préparatoire, les commandes que vous devez exécuter y sont simplement passées en paramètre.

De quoi pourrait-il avoir besoin dans la section de lancement? Le cas le plus prévisible est que l'application accepte certains arguments, des fichiers d'entrée. Pour cela, il existe la macro llvm_test_run , qui n'accepte que les arguments de démarrage de l'application (sans le nom du fichier exécutable) comme paramètres.

 llvm_test_run(--fixed 400 --cpu 1 --num 200000 --seed 1158818515 run.hmm) 

Pour modifier les actions au stade de la validation, la macro llvm_test_verify est utilisée , qui accepte toutes les commandes comme paramètres. Bien sûr, pour vérifier l'exactitude, il est préférable d'utiliser les outils inclus dans le dossier tools. Ils offrent de bonnes opportunités pour comparer la sortie générée avec celle attendue (il existe un traitement séparé pour comparer les nombres réels avec une erreur, etc.). Mais vous pouvez quelque part et vérifier simplement que la demande s'est terminée avec succès, etc.

 llvm_test_verify("cat %o | grep -q 'exit 0'") # %o -   placeholder   ,   lit.          lit,    ,    .    lit (  ,   LLVM)      (   <a href="https://llvm.org/docs/CommandGuide/lit.html"> </a>) 

Mais qu'en est-il s'il est nécessaire de collecter des métriques supplémentaires? Il existe une macro llvm_test_metric pour cela .

 llvm_test_metric(METRIC < > <,   >) 

Par exemple, pour la dhrystone, une métrique qui lui est spécifique peut être obtenue.

 llvm_test_metric(METRIC dhry_score grep 'Dhrystones per Second' %o | awk '{print $4}') 

Bien sûr, si vous devez collecter des métriques supplémentaires pour tous les tests, cette méthode est quelque peu gênante. Soit vous devez ajouter l'appel llvm_test_metric aux macros de niveau supérieur fournies par l'interface, soit vous pouvez utiliser TEST_SUITE_RUN_UNDER (la variable CMake) et un script spécifique pour collecter les mesures. La variable TEST_SUITE_RUN_UNDER est très utile et peut être utilisée, par exemple, pour s'exécuter sur des simulateurs, etc. En fait, une commande y est écrite qui acceptera l'application avec ses arguments en entrée.

En conséquence, nous obtenons certains CMakeLists.txt du formulaire

 #       llvm_test_run(--fixed 400 --cpu 1 --num 200000 --seed 1158818515 run.hmm) llvm_test_verify("cat %o | grep -q 'exit 0'") llvm_test_metric(METRIC score grep 'Score' %o | awk '{print $4}') llvm_multisource() # llvm_multisource(my_application)    

L'intégration ne nécessite pas d'efforts supplémentaires, si l'application est déjà construite à l'aide de CMake, alors dans CMakeList.txt dans la suite de tests, vous pouvez inclure le CMake existant pour l'assemblage et ajouter quelques appels de macro simples.

2. Exécution de tests

À la suite de son travail, CMake a généré un fichier de test spécial selon la description spécifiée. Mais comment ce fichier est-il exécuté?

lit utilise toujours un fichier de configuration lit.cfg, qui, par conséquent, existe dans la suite de tests. Dans ce fichier de configuration, divers paramètres pour l'exécution des tests sont indiqués, y compris le format des tests exécutables. La suite de tests utilise son propre format, qui se trouve dans le dossier litsupport.

 config.test_format = litsupport.test.TestSuiteTest() 

Ce format est décrit comme une classe de test héritée du test allumé standard et remplaçant la méthode principale de l'interface d'exécution. Des composants importants de litsupport sont également une classe avec une description du plan d'exécution du test TestPlan, qui stocke toutes les commandes qui doivent être exécutées à différentes étapes et connaissent l'ordre des étapes. Pour fournir la flexibilité nécessaire, des modules ont également été introduits dans l'architecture qui devraient fournir la méthode mutatePlan, à l'intérieur de laquelle ils peuvent modifier le plan de test, en introduisant simplement une description de la collection des mesures nécessaires, en ajoutant des commandes supplémentaires pour mesurer le temps de lancement de l'application, etc. Grâce à cette solution, l'architecture se développe bien.



Un exemple de l'opération de test de la suite de tests (à l'exception des détails sous la forme de classes TestContext, de diverses configurations éclairées et de tests eux-mêmes, etc.) est présenté ci-dessous.



Allumé provoque l'exécution du type de test spécifié dans le fichier de configuration. TestSuiteTest analyse le fichier de test CMake généré et reçoit une description des principales étapes. Ensuite, tous les modules trouvés sont appelés pour modifier le plan de test actuel, le lancement est instrumenté. Ensuite, le plan de test reçu est exécuté: ils sont effectués dans l'ordre de l'étape de préparation, de lancement et de validation. Si nécessaire, le profilage peut être effectué (ajouté par l'un des modules, si une variable a été définie lors de la configuration qui indique le besoin de profilage). L'étape suivante consiste à collecter des métriques, les fonctions de collecte qui ont été ajoutées par des modules standard dans le champ metric_collectors dans TestPlan, puis des métriques supplémentaires décrites par l'utilisateur dans CMake sont collectées.

3. Exécution de la suite de tests

Il existe deux façons d'exécuter la suite de tests:

  • Manuel, c.-à-d. invocation séquentielle des commandes.
     cmake -DCMAKE_CXX_COMPILER:FILEPATH=clang++ -DCMAKE_C_COMPILER:FILEPATH=clang test-suite #  make #   llvm-lit . -o <output> #   
  • en utilisant LNT (un autre système de l'écosystème LLVM qui vous permet d'exécuter des tests de performances, d'enregistrer les résultats dans la base de données, d'analyser les résultats dans l'interface Web). LNT, au sein de son équipe de test, effectue les mêmes étapes que dans le paragraphe précédent.
     lnt runtest test-suite --sandbox SANDBOX --cc clang --cxx clang++ --test-suite test-suite 

Le résultat de chaque test est affiché sous la forme

 PASS: test-suite :: MultiSource/Benchmarks/Prolangs-C/football/football.test (m of n) ********** TEST 'test-suite :: MultiSource/Benchmarks/Prolangs-C/football/football.test' RESULTS ********** compile_time: 1.1120 exec_time: 0.0014 hash: "38254c7947642d1adb9d2f1200dbddf7" link_time: 0.0240 size: 59784 size..bss: 99800 … size..text: 37778 ********** 

Les résultats de différents lancements peuvent être comparés sans LNT (bien que ce cadre offre de grandes opportunités pour analyser les informations à l'aide de différents outils, mais il a besoin d'un examen séparé), en utilisant le script inclus dans la suite de tests

 test-suite/utils/compare.py results_a.json results_b.json 

Un exemple de comparaison de la taille de code d'une seule et même référence à partir de deux lancements: avec les indicateurs -O3 et -Os

 test-suite/utils/compare.py -m size SANDBOX1/build/O3.json SANDBOX/build/Os.json Tests: 1 Metric: size Program O3 Os diff test-suite...langs-C/football/football.test 59784 47496 -20.6% 

Conclusion


L'infrastructure de description et d'exécution des benchmarks implémentés dans la suite de tests est facile à utiliser et à prendre en charge, évolue bien et, en principe, à mon avis, il utilise des solutions assez élégantes dans son architecture, ce qui, bien sûr, fait de la suite de tests un outil très utile pour les développeurs compilateurs, ainsi que ce système peuvent être modifiés pour être utilisés dans certaines tâches d'analyse de données.

Source: https://habr.com/ru/post/fr428421/


All Articles