并行高性能计算2并行化规划
2 并行化规划[*]并行项目的规划步骤
[*]版本控制和团队开发工作流程
[*]了解性能容量和限制
[*]制定程序并行化计划
开发并行应用程序或使现有应用程序并行运行,一开始可能会感觉具有挑战性。初涉并行化的开发人员往往不知道从何入手,也不知道可能会遇到什么陷阱。本章重点介绍开发并行应用程序的工作流模型。该模型提供了在开发并行程序时从何处入手以及如何保持进展的背景。一般来说,最好以小增量的方式实现并行性,这样如果遇到问题,可以回滚最后几次提交。这种模式适合敏捷项目管理技术。
让我们设想一下,您被分配了一个新项目,从空间网格(喀拉喀托火山示例)中加速和并行化一个应用程序。这可能是一个图像检测算法,也可能是一个火山灰羽流的科学模拟,或者是一个由此产生的海啸波浪的模型,也可能是所有这三种算法。要成功开展并行项目,可以采取哪些步骤?
直接进入项目是很有诱惑力的。但如果不经过深思熟虑和准备,就会大大降低成功的几率。首先,您需要为并行化工作制定一个项目计划,因此我们在这里首先对这一工作流程中的步骤进行一个高层次的概述。然后,随着本章的深入,我们将深入探讨每个步骤,重点关注并行项目的典型特征。
[*]快速开发: 并行工作流程
首先,您需要让您的团队和应用程序为快速开发做好准备。由于您现有的串行应用程序是在空间网格上运行的,因此可能会有很多小的改动,并需要经常进行测试以确保结果不会改变。代码准备工作包括设置版本控制、开发测试套件以及确保代码质量和可移植性。团队准备工作将围绕开发流程展开。一如既往,项目管理将涉及任务管理和范围控制。
要为开发周期做好准备,您需要确定可用计算资源的能力、应用程序的需求以及性能要求。系统基准测试有助于确定计算资源的限制,而剖析则有助于了解应用程序的需求及其最昂贵的计算内核。计算内核指的是应用程序中计算密集且概念独立的部分。
根据内核配置文件,您将规划例程并行化和实施更改的任务。只有当例程并行化且代码保持可移植性和正确性时,实施阶段才算完成。满足这些要求后,更改将提交到版本控制系统。在提交增量变更后,流程将再次从应用程序和内核配置文件开始。
2.1 接触新项目: 准备工作
在此阶段,您需要建立版本控制、为应用程序开发测试套件并清理现有代码。通过版本控制,您可以跟踪一段时间内对应用程序所做的更改。它允许您在日后快速撤销错误并追踪代码中的错误。通过测试套件,您可以在每次修改代码时验证应用程序的正确性。如果再加上版本控制,这将成为快速开发应用程序的强大设置。
有了版本控制和代码测试,您现在就可以着手清理代码了。好的代码易于修改和扩展,不会出现不可预测的行为。良好、整洁的代码可以通过模块化和内存问题检查得到保证。模块化是指将内核作为独立的子程序或函数来实现,并明确定义输入和输出。内存问题包括内存泄漏、越界内存访问和使用未初始化内存。以可预测的高质量代码开始并行工作,可促进快速进展和可预测的开发周期。如果最初的结果是由于编程错误造成的,则很难与串行代码相匹配。
最后,您需要确保代码的可移植性。这意味着多个编译器可以编译您的代码。拥有并保持编译器的可移植性,可以让您的应用程序面向更多的平台,而不是您目前所考虑的平台。此外,经验表明,开发可与多种编译器配合使用的代码有助于在代码版本历史记录中出现错误之前发现这些错误。高性能计算领域瞬息万变,可移植性可以让您更快地适应未来的变化。
准备时间与实际并行所花费的时间不相上下的情况并不少见,尤其是对于复杂代码而言。将准备工作纳入项目范围和时间估算,可避免项目进度受阻。在本章中,我们假定您是从串行或原型应用程序开始的。不过,即使您已经开始并行化代码,也能从这一工作流程策略中获益。接下来,我们将讨论项目准备工作的四个组成部分。
2.1.1 版本控制: 为并行代码创建安全保险库
在并行化过程中,不可避免地会发生许多变化,您会突然发现代码被破坏或返回不同的结果。在这种情况下,通过备份到工作版本来恢复是至关重要的。
在我们的场景中,您的图像检测项目已经有了一个版本控制系统。但灰羽模型从未有过任何版本控制。随着深入研究,您发现在不同开发人员的目录中实际上有四个版本的灰羽代码。当有一个版本控制系统在运行时,您可能需要检查一下团队在日常操作中使用的流程。也许团队认为改用 “拉取请求 ”模式是个好主意,在这种模式下,修改会在提交之前发布,供其他团队成员审核。或者,你和你的团队可能觉得 “推送 ”模式的直接提交更符合并行化任务的快速、小规模提交。在 “推送 ”模式中,提交是直接提交到版本库的,无需审核。在我们这个没有版本控制的灰羽应用程序例子中,当务之急是在开发人员之间建立起控制代码分歧的机制。
版本控制有很多选择。如果没有其他偏好,我们建议使用最常见的分布式版本控制系统 Git。分布式版本控制系统允许使用多个版本库数据库,而不是集中式版本控制系统中使用的单一集中式系统。分布式版本控制对于开源项目以及开发人员在笔记本电脑上、远程地点或其他无法连接到网络或靠近中央版本库的情况下工作非常有利。在当今的开发环境中,这是一个巨大的优势。但代价是增加了复杂性。集中式版本控制仍然很流行,而且更适合企业环境,因为只有一个地方存在源代码的所有信息。集中式控制还能为专有软件提供更好的安全性和保护。
关于如何使用 Git,有很多好书、博客和其他资源;我们在本章末尾列出了一些。在第 17 章中,我们还列出了其他一些常见的版本控制系统。这些系统包括免费的分布式版本控制系统(如 Mercurial 和 Git)、商业系统(如 PerForce 和 ClearCase)以及集中式版本控制系统(如 CVS 和 SVN)。无论使用哪种系统,你和你的团队都应该频繁提交。如果不想在主版本库中有大量的小提交,可以使用 Git 等版本控制系统折叠提交,或者为自己维护一个临时版本控制系统。
提交信息是提交者传达任务内容和做出某些改动的原因的地方,无论是为自己还是为当前或未来的团队成员。每个团队对这些信息的详细程度都有自己的偏好;我们建议在提交信息中尽可能多地使用细节。这是您今天勤奋工作、避免日后出现混乱的好机会。
一般来说,提交信息包括摘要和正文。摘要提供了一个简短的声明,清楚地说明了该提交涵盖了哪些新变更。此外,如果您使用的是问题跟踪系统,摘要行将引用该系统中的问题编号。最后,正文包含了提交背后的大部分 “原因 ”和 “方法”。
2.1.2 测试套件: 创建稳健可靠应用程序的第一步
测试套件是一组问题,用于测试应用程序的各个部分,以确保代码的相关部分仍能正常工作。除了最简单的代码外,测试套件对其他代码来说都是必需的。每次更改后,都应测试得到的结果是否相同。这听起来很简单,但有些代码在使用不同的编译器和不同数量的处理器时,结果可能会略有不同。
举例说明: 验证结果的喀拉喀托场景测试
您的项目有一个海洋波浪模拟应用程序,可生成经过验证的结果。验证结果是与实验数据或实际数据进行比较的仿真结果。经过验证的仿真代码非常宝贵。您不希望在并行化代码时失去这些。
在我们的方案中,您和您的团队在开发和生产中使用了两种不同的编译器。第一种是 GNU 编译器集 (GCC) 中的 C 编译器,这是一种无处不在、免费提供的编译器,散布在所有 Linux 发行版和许多其他操作系统中。C 编译器通常被称为 GCC 编译器。您的应用程序也使用市售的英特尔 C 编译器。
下图显示了预测波高和总质量的验证测试问题的假设结果。根据编译器和模拟中使用的处理器数量,输出结果会略有不同。
使用不同编译器和处理器数量进行计算时,哪些差异是可以接受的?
在本例中,程序报告的两个指标之间存在差异。在没有其他信息的情况下,很难确定哪个是正确的,以及解决方案中的哪些差异是可以接受的。一般来说,程序输出结果出现差异的原因可能是
[*]编译器或编译器版本的变化
[*]硬件变化
[*]编译器优化或编译器或编译器版本之间的微小差异
[*]操作顺序的变化,尤其是由于代码并行性造成的变化
2.1.2.1 了解并行化导致的结果变化
并行过程本质上会改变运算顺序,从而稍微修改数值结果。但并行中的错误也会产生微小的差异。在并行代码开发中,了解这一点至关重要,因为我们需要与单处理器运行进行比较,以确定我们的并行编码是否正确。我们将在第 5.7 节讨论全局求和技术时,讨论如何减少数值误差,使并行性误差更加明显。
对于我们的测试套件,我们需要一种工具来比较数值字段,并对差异有较小的容忍度。过去,测试套件开发人员必须为此创建一个工具,但近年来市场上出现了一些数值差异工具。其中两个工具是
[*]Numdiff 来自 https://www.nongnu.org/numdiff/
[*]ndiff 来自 https://www.math.utah.edu/~beebe/software/ndiff/
另外,如果您的代码以 HDF5 或 NetCDF 文件的形式输出状态,这些格式的工具也能让您以不同的公差比较文件中存储的值。
[*]HDF5® 是软件的第 5 版,最初称为层次数据格式,现在称为 HDF。它可从 HDF 集团(https://www .hdfgroup.org/)免费获取,是一种用于输出大型数据文件的通用格式。
[*]NetCDF 或网络通用数据格式是气候和地球科学界使用的另一种格式。当前版本的 NetCDF 建立在 HDF5 的基础之上。您可以在 Unidata 计划中心的网站(https://www.unidata.ucar.edu/software/netcdf/)上找到这些库和数据格式。
这两种文件格式都使用二进制数据,以提高速度和效率。二进制数据是数据的机器表示。这种格式对你我来说就像胡言乱语,但 HDF5 有一些有用的实用程序,可以让我们查看里面的内容。h5ls 工具列出了文件中的对象,例如所有数据数组的名称。h5dump 实用程序会转储每个对象或数组中的数据。对我们来说最重要的是,h5diff 实用程序会比较两个 HDF 文件,并报告超过数值容差的差异。第 16 章将详细讨论 HDF5 和 NetCDF 以及其他并行输入/输出(I/O)主题。
2.1.2.2 使用 cmake 和 CTEST 自动测试代码
近年来出现了许多测试系统。其中包括 CTest、Google test、pFUnit test 等。有关工具的更多信息,请参见第 17 章。现在,让我们来看看使用 CTest 和 ndiff 创建的系统。
CTest 是 CMake 系统的一个组件。CMake 是一个配置系统,能使生成的 makefile 适应不同的系统和编译器。将 CTest 测试系统整合到 CMake 中,可将两者紧密结合成一个统一的系统。这为开发人员提供了极大的便利。使用 CTest 实现测试的过程相对简单。单个测试以任意命令序列的形式编写。要将这些命令纳入 CMake 系统,需要在 CMakeLists.txt 中添加以下内容:
[*]enable_testing()
[*]add_test()
然后,你可以用 make test、ctest 调用测试,也可以用 ctest -R mpi 选择单个测试,其中 mpi 是一个正则表达式,用于运行任何匹配的测试名称。让我们以使用 CTest 系统创建测试为例进行说明。
示例: CTest 的先决条件
运行此示例需要安装 MPI、CMake 和 ndiff。对于 MPI(消息传递接口),我们将在 Mac 上使用 OpenMPI 4.0.0 和 CMake 3.13.3(包含 CTest),在 Ubuntu 上使用旧版本。我们将使用安装在 Mac 上的 GCC 第 8 版编译器,而不是默认编译器。然后使用软件包管理器安装 OpenMPI、CMake 和 GCC(GNU 编译器集)。我们将在 Mac 和 Apt 上使用 Homebrew,在 Ubuntu Linux 上使用 Synaptic。如果 libopenmpi-dev 的开发头文件与运行时文件分离,请务必从 libopenmpi-dev 获取这些头文件。ndiff 需要手动安装,方法是从 https://www.math.utah.edu/~beebe/ software/ndiff/ 下载工具,然后运行 ./configure、make 和 make install。
如清单 2.1 所示,制作两个源文件,为这个简单的测试系统创建应用程序。我们将使用一个定时器来产生串行和并行程序输出的微小差异。请注意,您可以在 https://github.com/EssentialsofParallelComputing/Chapter2 找到本章的源代码。
TimeIt.c
#include <unistd.h>
#include <stdio.h>
#include <time.h>
int main(int argc, char *argv[]){
struct timespec tstart, tstop, tresult;
// Start timer, call sleep and stop timer
clock_gettime(CLOCK_MONOTONIC, &tstart);
sleep(10);
clock_gettime(CLOCK_MONOTONIC, &tstop);
// Timer has two values for resolution and prevent overflows
tresult.tv_sec = tstop.tv_sec - tstart.tv_sec;
tresult.tv_nsec = tstop.tv_nsec - tstart.tv_nsec;
// Print calculated time from timers
printf("Elapsed time is %f secs\n", (double)tresult.tv_sec +
(double)tresult.tv_nsec*1.0e-9);
}MPITimeIt.c
#include <unistd.h>
#include <stdio.h>
#include <mpi.h>
int main(int argc, char *argv[]){
int mype;
// Initialize MPI and get processor rank
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &mype);
double t1, t2;
// Start timer, call sleep and stop timer
t1 = MPI_Wtime();
sleep(10);
t2 = MPI_Wtime();
// Print timing output from first processor
if (mype == 0) printf( "Elapsed time is %f secs\n", t2 - t1 );
// Shutdown MPI
MPI_Finalize();
}现在你需要一个测试脚本来运行应用程序并生成几个不同的输出文件。运行这些文件后,应该对输出进行数字比较。下面是一个过程示例,你可以将其放入名为 mympiapp.ctest 的文件中。您应该使用 chmod +x 命令使其可执行。
mympiapp.ctest
#!/bin/sh
# Run a serial test
./TimeIt > run0.out
# Run the first MPI test on 1 processor
mpirun -n 1 ./MPITimeIt > run1.out
# Run the second MPI test on 2 processors
mpirun -n 2 ./MPITimeIt > run2.out
# Compare the output for the two MPI jobs with a tolerance of 1%
# Reduce the tolerance to 1.0e-5 to get test to fail
ndiff --relative-error 1.0e-2 run1.out run2.out
# Capture the status set by the ndiff command
test1=$?
# Compare the output for the serial job and the 2 processor MPI run with a tolerance of 1%
ndiff --relative-error 1.0e-2 run0.out run2.out
# Capture the status set by the ndiff command
test2=$?
# Exit with the cumulative status code so CTest can report pass or fail
exit "$(($test1+$test2))"该测试首先在第 5 行比较 1 个和 2 个处理器的并行作业输出,公差为 0.1%。然后在第 7 行将串行运行与 2 处理器并行作业进行比较。要使测试失败,可尝试将容差减小到 1.0e-5。CTest 使用第 9 行的退出代码报告通过或失败。将大量 CTest 文件添加到测试套件的最简单方法是使用一个循环,查找所有以 .ctest 结尾的文件,并将这些文件添加到 CTest 列表中。下面是 CMakeLists.txt 文件的示例,其中包含创建两个应用程序的附加说明:
CMakeLists.txt
cmake_minimum_required (VERSION 3.0)
project (TimeIt)
# Enables CTest functionality in CMake
enable_testing()
# CMake has a built-in routine to find most MPI packages
# Defines MPI_FOUND if found
# MPI_INCLUDE_PATH (being replaced by MPI_<lang>_INCLUDE_PATH)
# MPI_LIBRARIES (being replaced by MPI_<lang>_LIBRARIES)
find_package(MPI)
# Adds build targets of TimeIt and MPITimeIt with source code file(s) TimeIt.c and MPITimeIt.c
add_executable(TimeIt TimeIt.c)
add_executable(MPITimeIt MPITimeIt.c)
# Need an include path to the mpi.h file and to the MPI library
target_include_directories(MPITimeIt PUBLIC ${MPI_INCLUDE_PATH})
target_link_libraries(MPITimeIt ${MPI_LIBRARIES})
# This gets all files with the extension 'ctest' and adds it to the test list for CTest
# The ctest file needs to be executable or explicitly launched with the 'sh' command as below
file(GLOB TESTFILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.ctest")
foreach(TESTFILE ${TESTFILES})
add_test(NAME ${TESTFILE} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/${TESTFILE})
endforeach()
# A custom command, distclean, to remove files that are created
add_custom_target(distclean COMMAND rm -rf CMakeCache.txt CMakeFiles
CTestTestfile.cmake Makefile Testing cmake_install.cmake)第 6 行的 find_package(MPI) 命令定义了 MPI_FOUND、MPI_INCLUDE_ PATH 和 MPI_LIBRARIES。这些变量包括较新 CMake 版本中 MPI_INCLUDE_PATH 和 MPI_LIBRARIES 的语言,因此 C、C++ 和 Fortran 有不同的路径。现在,只需使用以下命令运行测试
mkdir build && cd build
cmake ..
make
make test或
ctest也可以使用
ctest --output-on-failure您应该会得到如下结果:
Running tests...
Test project /Users/brobey/Programs/RunDiff
Start 1: mpitest.ctest
1/1 Test #1: mpitest.ctest .................... Passed 30.24 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) =30.24 sec该测试基于睡眠功能和计时器,因此可能通过也可能不通过。测试结果在 Testing/Temporary/* 中。
在该测试中,我们比较了应用程序单次运行的输出结果。另外,在测试脚本中存储一个运行的黄金标准文件也是一种很好的做法。这种比较可以检测出会导致新版本应用程序与早期版本结果不同的变化。一旦出现这种情况,就会引起警觉;请检查新版本是否仍然正确。如果是,则应更新黄金标准。
测试套件应尽可能多地测试代码的各个部分。代码覆盖率这一指标量化了测试套件完成任务的程度,用占源代码行数的百分比来表示。测试开发人员有一句老话:代码中没有测试的部分就是坏的,因为即使现在没有,最终也会坏。在并行化代码的过程中,所有的变化都会造成代码的破坏,这是不可避免的。虽然高代码覆盖率很重要,但对于我们的并行化工作来说,更关键的是要对并行化代码的各个部分进行测试。许多编译器都有生成代码覆盖率统计数据的功能。对于 GCC,gcov 是剖析工具,而对于英特尔,则是 Codecov。我们来看看 GCC 是如何工作的。
使用 GCC 进行代码覆盖:
[*]在编译和链接时添加 -fprofile-arcs 和 -ftest-coverage 标记
[*]在一系列测试中运行仪器化的可执行文件
[*]运行 gcov获取每个文件的覆盖率。注意:对于使用 CMake 的联编,请在源文件名中添加额外的 .c 扩展名;例如,gcov CMakeFiles/stream_triad.dir/stream_triad.c.c 处理 CMake 添加的扩展名。
您将得到类似下面的输出结果
88.89% of 9 source lines executed in file <source>.c
Creating <source>.c.gcovgcov 输出文件包含一个列表,每一行都以执行次数作为前缀。
了解不同类型的代码测试
测试系统也有不同种类。在本节中,我们将介绍以下类型:
[*]回归测试-定期运行,防止代码倒退。通常每晚或每周使用 cron 作业调度程序在指定时间启动作业。
[*]单元测试-在开发过程中测试子程序或其他小部分代码的运行。
[*]持续集成测试-越来越受欢迎,这些测试会在代码提交时自动触发运行。
[*]提交测试--可以在很短时间内通过命令行运行的小型测试集,在提交前使用。
所有这些测试类型对一个项目都很重要,因此应同时使用,而不是只依赖其中一种。测试对于并行程序尤为重要,因为在开发周期的早期发现错误意味着在运行 6 小时后就不用再调试 1000 个处理器了。
单元测试最好在开发代码时创建。单元测试的真正爱好者使用测试驱动开发(TDD),即首先创建测试,然后编写代码以通过这些测试。将这些类型的测试纳入并行代码开发,包括测试它们在并行语言中的运行和实现。在这一层面发现问题要容易得多。
提交测试是应添加到项目中的首批测试,在代码修改阶段会大量使用。这些测试应对代码中的所有例程进行测试。有了这些随时可用的测试,团队成员就可以在提交到版本库之前运行这些测试。我们建议开发人员在提交前通过 Bash 或 Python 脚本或 makefile 等命令行调用这些测试。
示例:使用 CMake 和 CTest 进行提交测试的开发工作流程
要在 CMakeLists.txt 中进行提交测试,请创建下表所示的三个文件。使用前一个测试中的 Timeit.c,但将睡眠间隔从 10 改为 30。
使用 CTest 创建提交测试
blur_short.CTest
#!/bin/sh
makeblur_long.ctest
#!/bin/sh
./TimeItCMakeLists.txt
cmake_minimum_required (VERSION 3.0)
project (TimeIt)
# Enables CTest functionality in CMake
enable_testing()
add_executable(TimeIt TimeIt.c)
# Add two tests, one with commit in the name
add_test(NAME blur_short_commit WORKING_DIRECTORY ${CMAKE_BINARY_DIRECTORY}
COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/blur_short.ctest)
add_test(NAME blur_long WORKING_DIRECTORY ${CMAKE_BINARY_DIRECTORY}
COMMAND sh ${CMAKE_CURRENT_SOURCE_DIR}/blur_long.ctest)
# Custom target "commit_tests" to run all tests with commit in the name
add_custom_target(commit_tests COMMAND ctest -R commit DEPENDS TimeIt)
# A custom command, distclean, to remove files that are created
add_custom_target(distclean COMMAND rm -rf CMakeCache.txt CMakeFiles
CTestTestfile.cmake Makefile Testing cmake_install.cmake)提交测试可通过 ctest -R commit 或通过 make commit_tests 在 CMakeLists.txt 中添加的自定义目标来运行。make test 或 ctest 命令会运行包括长测试在内的所有测试,长测试需要一段时间。commit test 命令会挑出名称中包含 commit 的测试,从而得到一组涵盖关键功能的测试,但运行速度会更快一些。现在的工作流程是
编辑源代码: vi mysource.c
编译代码:make
运行提交测试: make commit_tests
提交代码更改:git commit
重复上述步骤。持续集成测试由提交到主代码库的代码调用。这是防止提交错误代码的额外措施。这些测试可以与提交测试相同,也可以更广泛。用于此类测试的顶级持续集成工具有
Jenkins (https://www.jenkins.io)
用于 GitHub 和 Bitbucket 的 Travis CI (https://travis-ci.com)
GitLab CI (https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/)
CircleCI (https://circleci.com)
回归测试通常通过 cron 作业设置为通宵运行。这意味着测试套件可能比其他测试类型的测试套件更广泛。这些测试时间可能较长,但应在早上报告前完成。由于运行时间较长和报告的周期性,内存检查和代码覆盖等附加测试通常作为回归测试运行。回归测试的结果通常会随着时间的推移而被跟踪,“通过墙 ”被认为是项目良好的标志。
理想测试系统的进一步要求
虽然前面描述的测试系统足以满足大多数目的,但对于大型高性能计算项目来说,还有更多的要求。这些类型的高性能计算项目可能会有大量的测试套件,也可能需要在批处理系统中运行,以获取更多资源。
https://sourceforge.net/projects/ ctsproject/ 网站上的协作测试系统(CTS)提供了一个针对这些需求开发的系统实例。该系统使用 Perl 脚本来运行一组固定的测试服务器(通常为 10 台),与批处理系统并行启动测试。每个测试完成后,系统会启动下一个测试。这就避免了一次性向系统发送大量任务。CTS 系统还能自动检测批处理系统和 MPI 类型,并为每个系统调整脚本。报告系统使用 cron 作业,测试在夜间提前启动。跨平台报告在早上启动,然后发送出去。
举例说明: 针对高性能计算项目的 Krakatau 场景测试套件
在对应用程序进行审查后,您发现图像检测应用程序有很大的用户群。因此,您的团队决定在每次提交前设置大量回归测试,以避免对用户造成影响。运行时间较长的内存正确性测试通宵运行,每周对性能进行跟踪。然而,海浪模拟是新项目,用户较少,但您希望确保已验证的问题能继续给出相同的答案。由于提交测试时间过长,因此需要每周运行一个简短版本和完整版本。
对于这两个应用,都会设置一个持续集成测试来构建代码并运行一些较小的测试。灰羽模型刚刚开始开发,因此您决定使用单元测试来检查每段新添加的代码。
参考资料
[*]软件测试精品书籍文档下载持续更新 https://github.com/china-testing/python-testing-examples 请点赞,谢谢!
[*]本文涉及的python测试开发库 谢谢点赞! https://github.com/china-testing/python_cn_resouce
[*]python精品书籍下载 https://github.com/china-testing/python_cn_resouce/blob/main/python_good_books.md
[*]Linux精品书籍下载 https://www.cnblogs.com/testing-/p/17438558.html
2.1.3 查找并修复内存问题
良好的代码质量至关重要。并行化往往会导致代码缺陷的出现;这可能是未初始化内存或内存覆盖。
未初始化内存是指在设置内存值之前访问的内存。当你为程序分配内存时,它会获取这些内存位置中的任何值。如果在设置之前使用内存,就会导致不可预测的行为。
当数据被写入不属于变量的内存位置时,就会发生内存覆盖。写入超出数组或字符串边界的数据就是一个例子。
要抓住这类问题,我们建议使用内存正确性工具来彻底检查你的代码。其中最好的工具之一就是免费提供的 Valgrind 程序。Valgrind 是一个工具框架,它通过合成 CPU 执行指令,在机器代码级别上运行。Valgrind 旗下开发了许多工具。第一步是使用软件包管理器在系统中安装 Valgrind。如果你运行的是最新版本的 macOS,你可能会发现 Valgrind 需要几个月的时间才能移植到新内核。最好的办法是在另一台电脑、旧版 MacOS 或虚拟机或 Docker 镜像上运行 Valgrind。
要运行 Valgrind,请像往常一样执行程序,并在前面插入 valgrind 命令。对于 MPI 作业,valgrind 命令放在 mpirun 之后、可执行文件名之前。Valgrind 与 GCC 编译器配合使用效果最佳,这是因为 GCC 开发团队采用了 Valgrind,努力消除可能导致诊断输出混乱的误报。建议在使用英特尔编译器时,不进行矢量化编译,以避免矢量指令警告。您还可以尝试使用第 17.5 节中列出的其他内存正确性工具。
[*]使用 Valgrind Memcheck 查找内存问题
Memcheck 工具是 Valgrind 工具套件中的默认工具。它拦截每一条指令,检查是否存在各种内存错误,并在运行开始、运行过程中和运行结束时生成诊断报告。这可以将运行速度降低一个数量级。如果你以前没有使用过它,就要做好大量输出的准备。一个内存错误会导致许多其他错误。最好的策略是从第一个错误开始,修复它,然后再运行一次。要了解 Valgrind 如何工作,请尝试下面示例代码。要执行 Valgrind,请在可执行文件名之前插入 valgrind 命令,如
# valgrind <./my_app>
# 或
# mpirun -n 2 valgrind <./myapp>#includeint main(int argc, char *argv[]){ int ipos, ival; int *iarray = (int *) malloc(10*sizeof(int)); if (argc == 2) ival = atoi(argv); for (int i = 0; i
页:
[1]