返回介绍

4.8 并行测试

发布于 2025-05-06 21:45:56 字数 6423 浏览 0 评论 0 收藏

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 个时间单位:

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 非常简单:
    # 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 秒:
    $ 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 个内核上运行测试集:
    $ 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 个核上的叠加比较,从最长的开始,如下图所示:

--> time
core 1: jjjjjjjjj
core 2: iiiiiiibd
core 3: hhhhhggg
core 4: eeefffac

按照定义测试的顺序运行,运行结果如下:

--> time
core 1: aeeeiiiiiii
core 2: bfffjjjjjjjjj
core 3: cggg
core 4: dhhhhh

按照定义测试的顺序运行测试,总的来说需要更多的时间,因为这会让 2 个核大部分时间处于空闲状态(这里的核 3 和核 4)。CMake 知道每个测试的时间成本,是因为我们先顺序运行了测试,将每个测试的成本数据记录在 test/Temporary/CTestCostData.txt 文件中:

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 个核上的总测试时间明显会更长。这意味着什么呢?这意味着,我们应该减少的时间成本来安排测试?这是一种决策,但事实证明还有另一种方法,我们可以自己表示每次测试的时间成本:

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 将其设置为所需的级别。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。