以LLVM测试套件为例,用于测试和收集程序指标的灵活系统

引言


大多数开发人员显然已经听说过一些相当重要的开源开发,例如LLVM系统和clang编译器。 但是,LLVM现在不仅是用于创建编译器的系统本身,而且还是一个大型生态系统,其中包含许多项目,用于解决在编译器创建的任何阶段出现的各种问题(通常,每个此类项目都有其自己的独立存储库)。 基础架构的一部分自然包括测试和基准测试工具,例如 在开发编译器时,其有效性是非常重要的指标。 这些单独的LLVM测试基础结构项目之一是测试套件( 官方文档 )。

LLVM测试套件


乍看之下,测试套件存储库似乎只是C / C ++中的一组基准测试,但这并不是完全正确的。 除了要进行性能评估的程序的源代码之外,测试套件还包括用于构建,运行和收集指标的灵活基础架构。 默认情况下,它收集以下指标:编译时间,执行时间,链接时间,代码大小(以节为单位)。

测试套件对测试和基准测试编译器自然很有用,但是它也可以用于需要某些C / C ++代码库的其他一些研究任务。 我认为那些曾经尝试在数据分析领域中做点事情的人面临着源数据缺乏和碎片化的问题。 测试套件虽然不包含大量应用程序,但是具有统一的数据收集机制。 将自己的应用程序添加到集合中,收集完成特定任务所需的度量标准非常简单。 因此,我认为,测试套件(除了测试和基准测试的主要任务之外)是基础项目的好选择,在此基础上,您可以为需要分析程序代码某些功能或程序某些特征的任务构建数据收集。

LLVM测试套件结构


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

结构简单明了。

工作原理


如您所见,CMake和特殊的测试格式负责描述度量标准的组装,启动和收集的所有工作。

如果我们以一种非常抽象的方式考虑它,那么很明显,使用该系统的基准测试过程看起来非常简单且可预测:


如何看得更详细? 在本文中,我想详细介绍CMake在整个系统中所扮演的角色,以及如果要向该系统中添加内容,必须写的唯一文件是什么。

1.构建测试应用程序。

作为构建系统,它已成为C / C ++ CMake程序的事实上的标准。 CMake会配置项目,并根据用户的喜好生成make,ninja等文件。 直接施工。
但是,在测试套件中,CMake不仅会生成有关如何构建应用程序的规则,而且还会自行配置测试。

启动CMake之后,另一个文件(扩展名为.test)将被写入构建目录,其中描述了应如何执行应用程序并检查其正确性。

最标准的.test文件的示例

 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 

扩展名为.test的文件可能包含以下部分:

  • PREPARE-描述启动应用程序之前必须执行的所有操作,与不同单元测试框架中存在的Before方法非常相似;
  • RUN-描述如何运行应用程序;
  • 验证-描述如何检查应用程序的正确运行;
  • 度量标准-描述需要在标准中另外收集的度量标准。

这些部分均可以省略。

但是由于此文件是自动生成的,因此它位于CMake文件中,作为基准的描述如下:如何获取目标文件,如何将它们组合到应用程序中,然后需要使用此应用程序完成什么。

为了更好地理解默认行为及其描述方式,请考虑一些CMakeLists.txt的示例。

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

可以根据平台设置标志,TestSuite cmake模块中包含DetectArchitecture文件,该文件确定运行基准测试的目标平台,因此您可以简单地使用已收集的数据。 其他数据也可用:操作系统,字节顺序等。

 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() 

原则上,对于至少曾经看过或编写过简单CMake文件的人来说,此部分不应是什么新鲜事物。 自然,您可以使用这些库,自行构建它们,通常,使用CMake提供的任何方法来描述构建应用程序的过程。

然后,您需要确保生成.test文件。 tets-suite界面为此提供了哪些工具?

有2个基本宏llvm_multisourcellvm_singlesource ,它们对于大多数琐碎的情况就足够了。

  • 如果应用程序包含多个文件, 使用llvm_multisource 。 如果在CMake中调用此宏时未将源代码文件作为参数传递,则位于当前目录中的所有源代码文件都将用作构建基础。 实际上,当前在测试套件中此宏的界面中正在进行更改,并且所描述的将源文件作为宏参数进行传输的方法是位于master分支中的当前版本。 以前,存在另一个系统:必须将带有源代码的文件写入Source变量(与7.0版一样),并且该宏不接受任何参数。 但是执行的基本逻辑保持不变。
  • llvm_singlesource认为每个.c / .cpp文件都是一个单独的基准,并且每个文件都收集一个单独的可执行文件。

默认情况下,上述两个用于启动已构建应用程序的宏均会生成一个简单调用此应用程序的命令。 并且由于与位于扩展名为.reference_output(还带有可能的后缀.reference_output.little-endian,.reference_output.big-endian)的文件中的预期输出进行比较而进行了正确性检查。

如果这适合您,那很好,只需多一行(调用llvm_multisource或llvm_singlesource)就可以启动应用程序并获取以下指标:代码大小(以节为单位),编译时间,链接时间,执行时间。

但是,当然,这种情况很少能顺利进行。 您可能需要更改一个或多个阶段。 借助简单的操作,这也是可能的。 您唯一需要记住的是,如果您重新定义某个阶段,则需要描述所有其他阶段(即使它们工作服的默认算法也是如此,这当然有点令人不快)。

API中有宏来描述每个阶段的操作。

关于llvm_test_prepare宏在准备阶段没有什么要写的,您需要执行的命令只是作为参数传递到那里。

启动部分可能需要什么? 最可预测的情况是应用程序接受一些参数,即输入文件。 为此,有一个llvm_test_run宏,该宏仅接受应用程序启动参数(不包含可执行文件的名称)作为参数。

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

要在验证阶段更改操作,将使用llvm_test_verify宏,该宏接受任何命令作为参数。 当然,要验证正确性,最好使用tools文件夹中包含的工具。 它们为将生成的输出与预期的输出进行比较提供了很好的机会(存在单独的处理,用于将实数与某些误差进行比较等)。 但是您可以在某个地方检查应用程序是否成功完成,等等。

 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>) 

但是,如果需要收集一些其他指标怎么办? 有一个llvm_test_metric宏。

 llvm_test_metric(METRIC < > <,   >) 

例如,对于脚石,可以获得特定于它的度量。

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

当然,如果您需要为所有测试收集其他指标,则此方法有些不便。 您需要将llvm_test_metric调用添加到接口提供的更高级别的宏,或者可以使用TEST_SUITE_RUN_UNDER(CMake变量)和特定的脚本来收集度量。 TEST_SUITE_RUN_UNDER变量非常有用,并且可以用于例如在模拟器上运行等。 实际上,将命令写入其中,该命令将接受带有其参数的应用程序作为输入。

结果,我们得到了以下形式的CMakeLists.txt

 #       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)    

集成不需要额外的努力,如果应用程序已经使用CMake构建,则在测试套件的CMakeList.txt中,您可以包括现有的CMake进行组装并添加一些简单的宏调用。

2.运行测试

作为其工作的结果,CMake根据指定的描述生成了一个特殊的测试文件。 但是该文件如何执行?

lit始终使用一些配置文件lit.cfg,该文件相应地存在于测试套件中。 在此配置文件中,指示了用于运行测试的各种设置,包括可执行测试的格式。 测试套件使用其自己的格式,该格式位于litsupport文件夹中。

 config.test_format = litsupport.test.TestSuiteTest() 

这种格式被描述为继承自标准点亮测试的测试类,并且该类覆盖了execute接口的主要方法。 litsupport的重要组成部分还有一个类,其中描述了TestPlan测试执行计划,该计划存储必须在不同阶段执行的所有命令并知道阶段的顺序。 为了提供必要的灵活性,还向体系结构中引入了模块,该模块应提供mutatePlan方法,在其中可以更改测试计划,仅介绍必要指标集合的描述,添加用于测量启动应用程序时间的其他命令等。 由于这种解决方案,该体系结构可以很好地扩展。



下面介绍了测试套件测试操作的示例(TestContext类形式的详细信息,各种灯光配置以及测试本身等除外)。



点亮将导致执行配置文件中指定的测试类型。 TestSuiteTest解析生成的CMake测试文件,并接收主要阶段的描述。 然后,调用所有找到的模块以更改当前的测试计划,并检测启动情况。 然后执行接收到的测试计划:按照准备,启动和验证的阶段顺序执行它们。 如有必要,可以执行性能分析(如果在配置过程中设置了指示需要性能分析的变量,则可以添加模块之一)。 下一步是收集度量,收集功能由标准模块在TestPlan的metric_collectors字段中添加,然后收集用户在CMake中描述的其他度量。

3.运行测试套件

有两种方法可以运行测试套件:

  • 手册,即 顺序调用命令。
     cmake -DCMAKE_CXX_COMPILER:FILEPATH=clang++ -DCMAKE_C_COMPILER:FILEPATH=clang test-suite #  make #   llvm-lit . -o <output> #   
  • 使用LNT(LLVM生态系统中的另一个系统,它允许您运行基准测试,将结果保存到数据库,在Web界面中分析结果)。 LNT在其试运行团队中,执行与上一段相同的步骤。
     lnt runtest test-suite --sandbox SANDBOX --cc clang --cxx clang++ --test-suite test-suite 

每个测试的结果显示为

 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 ********** 

使用测试套件中包含的脚本,可以在不使用LNT的情况下比较来自不同启动的结果(尽管此框架提供了使用不同工具分析信息的巨大机会,但需要单独审核)。

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

比较两个启动中一个基准和相同基准的代码大小的示例:带有-O3和-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% 

结论


在测试套件中实现的用于描述和运行基准的基础结构易于使用和支持,可以很好地扩展,并且从原则上讲,在我看来,它在其体系结构中使用了非常优雅的解决方案,这当然使测试套件成为对开发人员非常有用的工具可以修改编译器以及该系统,以用于某些数据分析任务。

Source: https://habr.com/ru/post/zh-CN428421/


All Articles