💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
# 4.8 并行测试 **NOTE**:*此示例代码可以在 https://github.com/dev-cafe/cmake-cookbook/tree/v1.0/chapter-04/recipe-08 中找到。该示例在CMake 3.5版(或更高版本)中是有效的,并且已经在GNU/Linux、macOS和Windows上进行过测试。* 大多数现代计算机都有4个或更多个CPU核芯。CTest有个非常棒的特性,能够并行运行测试,如果您有多个可用的核。这可以减少测试的总时间,而减少总测试时间才是真正重要的,从而开发人员频繁地进行测试。本示例中,我们将演示这个特性,并讨论如何优化测试以获得最大的性能。 其他测试可以进行相应地表示,我们把这些测试脚本放在`CMakeLists.txt`同目录下面的test目录中。 ## 准备工作 我们假设测试集包含标记为a, b,…,j的测试用例,每一个都有特定的持续时间: | 测试用例 | 该单元的耗时 | | ---------- | ------------ | | a, b, c, d | 0.5 | | e, f, g | 1.5 | | h | 2.5 | | i | 3.5 | | j | 4.5 | 时间单位可以是分钟,但是为了保持简单和简短,我们将使用秒。为简单起见,我们可以用Python脚本表示`test a`,它消耗0.5个时间单位: ```python import sys import time # wait for 0.5 seconds time.sleep(0.5) # finally report success sys.exit(0) ``` 其他测试同理。我们将把这些脚本放在`CMakeLists.txt`下面,一个名为`test`的目录中。 ## 具体实施 对于这个示例,我们需要声明一个测试列表,如下: 1. `CMakeLists.txt`非常简单: ```cmake # set minimum cmake version cmake_minimum_required(VERSION 3.5 FATAL_ERROR) # project name project(recipe-08 LANGUAGES NONE) # detect python find_package(PythonInterp REQUIRED) # define tests enable_testing() add_test(a ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/a.py) add_test(b ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/b.py) add_test(c ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/c.py) add_test(d ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/d.py) add_test(e ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/e.py) add_test(f ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/f.py) add_test(g ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/g.py) add_test(h ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/h.py) add_test(i ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/i.py) add_test(j ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/j.py) ``` 2. 我们可以配置项目,使用`ctest`运行测试,总共需要17秒: ```shell $ mkdir -p build $ cd build $ cmake .. $ ctest Start 1: a 1/10 Test #1: a ................................ Passed 0.51 sec Start 2: b 2/10 Test #2: b ................................ Passed 0.51 sec Start 3: c 3/10 Test #3: c ................................ Passed 0.51 sec Start 4: d 4/10 Test #4: d ................................ Passed 0.51 sec Start 5: e 5/10 Test #5: e ................................ Passed 1.51 sec Start 6: f 6/10 Test #6: f ................................ Passed 1.51 sec Start 7: g 7/10 Test #7: g ................................ Passed 1.51 sec Start 8: h 8/10 Test #8: h ................................ Passed 2.51 sec Start 9: i 9/10 Test #9: i ................................ Passed 3.51 sec Start 10: j 10/10 Test #10: j ................................ Passed 4.51 sec 100% tests passed, 0 tests failed out of 10 Total Test time (real) = 17.11 sec ``` 3. 现在,如果机器有4个内核可用,我们可以在不到5秒的时间内在4个内核上运行测试集: ```shell $ ctest --parallel 4 Start 10: j Start 9: i Start 8: h Start 5: e 1/10 Test #5: e ................................ Passed 1.51 sec Start 7: g 2/10 Test #8: h ................................ Passed 2.51 sec Start 6: f 3/10 Test #7: g ................................ Passed 1.51 sec Start 3: c 4/10 Test #9: i ................................ Passed 3.63 sec 5/10 Test #3: c ................................ Passed 0.60 sec Start 2: b Start 4: d 6/10 Test #6: f ................................ Passed 1.51 sec 7/10 Test #4: d ................................ Passed 0.59 sec 8/10 Test #2: b ................................ Passed 0.59 sec Start 1: a 9/10 Test #10: j ................................ Passed 4.51 sec 10/10 Test #1: a ................................ Passed 0.51 sec 100% tests passed, 0 tests failed out of 10 Total Test time (real) = 4.74 sec ``` ## 工作原理 可以观察到,在并行情况下,测试j、i、h和e同时开始。当并行运行时,总测试时间会有显著的减少。观察` ctest --parallel 4`的输出,我们可以看到并行测试运行从最长的测试开始,最后运行最短的测试。从最长的测试开始是一个非常好的策略。这就像打包移动的盒子:从较大的项目开始,然后用较小的项目填补空白。a-j测试在4个核上的叠加比较,从最长的开始,如下图所示: ```shell --> time core 1: jjjjjjjjj core 2: iiiiiiibd core 3: hhhhhggg core 4: eeefffac ``` 按照定义测试的顺序运行,运行结果如下: ```shell --> time core 1: aeeeiiiiiii core 2: bfffjjjjjjjjj core 3: cggg core 4: dhhhhh ``` 按照定义测试的顺序运行测试,总的来说需要更多的时间,因为这会让2个核大部分时间处于空闲状态(这里的核3和核4)。CMake知道每个测试的时间成本,是因为我们先顺序运行了测试,将每个测试的成本数据记录在`test/Temporary/CTestCostData.txt`文件中: ```shell a 1 0.506776 b 1 0.507882 c 1 0.508175 d 1 0.504618 e 1 1.51006 f 1 1.50975 g 1 1.50648 h 1 2.51032 i 1 3.50475 j 1 4.51111 ``` 如果在配置项目之后立即开始并行测试,它将按照定义测试的顺序运行测试,在4个核上的总测试时间明显会更长。这意味着什么呢?这意味着,我们应该减少的时间成本来安排测试?这是一种决策,但事实证明还有另一种方法,我们可以自己表示每次测试的时间成本: ```cmake add_test(a ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/a.py) add_test(b ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/b.py) add_test(c ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/c.py) add_test(d ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/d.py) set_tests_properties(a b c d PROPERTIES COST 0.5) add_test(e ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/e.py) add_test(f ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/f.py) add_test(g ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/g.py) set_tests_properties(e f g PROPERTIES COST 1.5) add_test(h ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/h.py) set_tests_properties(h PROPERTIES COST 2.5) add_test(i ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/i.py) set_tests_properties(i PROPERTIES COST 3.5) add_test(j ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/j.py) set_tests_properties(j PROPERTIES COST 4.5) ``` 成本参数可以是一个估计值,也可以从`test/Temporary/CTestCostData.txt`中提取。 ## 更多信息 除了使用` ctest --parallel N`,还可以使用环境变量`CTEST_PARALLEL_LEVEL`将其设置为所需的级别。