该项目现已存档且只读
GitHub 上的 timemory(源代码)
timemory 通用文档 (ReadTheDocs)
timemory源代码文档(Doxygen)
timemory测试仪表板(CDash)
时间记忆教程
ECP 2021 教程第 1 天(YouTube)
ECP 2021 教程第 2 天(YouTube)
维基百科
GitHub | git clone https://github.com/NERSC/timemory.git |
皮皮 | pip install timemory |
斯派克 | spack install timemory |
康达锻造公司 | conda install -c conda-forge timemory |
timemory的目标是创建一个具有模块化和可重用组件的开源性能测量和分析包,可用于适应任何现有的C/C++性能测量和分析API,并且用户可以在其应用程序中任意扩展。 Timemory 不仅仅是另一个分析工具,它还是一个分析工具包,它通过模块化简化了自定义分析工具的构建,然后利用该工具包提供了多个预构建的工具。
换句话说,timemory 提供了许多预构建的工具、库和接口,但由于它的模块化,代码只能重用单独的部分——例如用于测量不同时间间隔、内存使用情况和硬件计数器的类—— - 没有timemory“运行时管理”。
Timemory 使用标准 CMake 安装。可以在 Wiki 中找到几个安装示例。有关 CMake 选项的详细信息,请参阅安装文档。
完整文档可在 timemory.readthedocs.io 上获取。完整文档的 doygen 部分提供了详细的源文档。教程可在 github.com/NERSC/timemory-tutorials 中找到。
timemory的主要目标是开发一个通用框架,将软件监控代码(即性能分析、调试、日志记录)绑定到一个紧凑且高效的接口中。
Timemory 的出现是为了满足对各种 API 的通用适配器套件的需求,提供了几种现有工具以及用于创建新工具的直接直观的方法。 Timemory 可以将确定性性能测量、统计性能测量(即采样)、调试消息、数据记录和数据验证捆绑到同一接口中,用于自定义特定于应用程序的软件监控接口,轻松构建time
、 netstat
、 Instrumentation 等工具分析器、采样分析器以及 MPI-P、MPI-T、OMPT、KokkosP 等的编写实现。此外,timemory 可以将其标记转发给多个第三方分析器,例如 LIKWID、Caliper、 TAU、gperftools、Perfetto、VTune、Allinea-MAP、CrayPAT、Nsight-Systems、Nsight-Compute 和 NVProf。
Timemory 提供前端 C/C++/Fortran API 和 Python API,允许任意选择 50 多个不同的组件,从定时器到硬件计数器,再到与第三方工具的接口。这全部都是通过工具包 API 使用类型安全的工具包构建的,例如: component_tuple<wall_clock, papi_vector, nvtx_marker, user_bundle>
其中wall_clock
是挂钟计时器, papi_vector
是硬件计数器的句柄, nvxt_marker
在中创建符号NVIDIA CUDA 分析器,而user_bundle
是一个通用组件,下游用户可以在运行时插入更多组件。
使用 timemory 编写的性能测量组件可以任意扩展至任意数量的线程和进程,并完全支持在程序内的不同位置混合不同的测量结果——这使得能够部署 timemory 以在 HPC 中大规模收集性能数据,因为收集的数据非常详细可能发生在程序内的特定位置,其中无处不在的收集会同时显着降低性能并需要大量内存。
Timemory 可以用作后端,将检测和采样工具捆绑在一起,支持序列化为 JSON/XML,并提供其他用途的统计信息。它还可以用作调用自定义仪器和采样工具的前端。 Timemory 使用抽象术语“组件”来表示封装了某些性能分析操作的结构。该结构可能封装对另一个工具的函数调用,记录计时时间戳,记录应用程序提供的值,提供用于动态替换代码中函数的运算符,审核函数的传入参数和/或传出返回值,或者只是提供可以由链接器重载的存根。
timemory原生输出格式为JSON和文本;还支持其他输出格式,例如 XML。文本格式旨在便于人类阅读。 JSON 数据用于分析,有两种类型:分层数据和扁平数据。基本的绘图功能可以通过timemory-plotting
获得,但强烈鼓励用户使用 hashet 来分析 pandas 数据框中的层次结构 JSON 数据。 Hatchet 支持过滤、并集、加法、减法、输出为dot
和火焰图格式以及交互式 Jupyter 笔记本。目前,timemory支持45+种指标类型在Hatchet中进行分析。
timemory 有 4 个主要类别:组件、操作、捆绑器和存储。组件提供如何执行特定行为的细节,操作提供用于请求组件在复杂场景中执行操作的支架,捆绑器将组件分组到单个通用句柄中,存储管理应用程序生命周期内的数据收集。当所有四个类别组合在一起时,timemory 实际上类似于标准性能分析工具,它被动收集数据并在应用程序终止时提供报告和分析。然而,TiMemory 可以非常轻松地从方程中减去存储空间,从而将 TiMemory 转变为用于定制数据收集的工具包。
tim::component::wall_clock
:一个简单的挂钟定时器tim::component::vtune_profiler
:一个简单的组件,用于打开和关闭 VTune Profiler(当 VTune 主动分析应用程序时)tim::component::data_tracker_integer
:在应用程序执行时将整数值与标签相关联(例如,某处使用的循环迭代次数)tim::component::papi_vector
:使用 PAPI 库收集硬件计数器值tim::component::user_bundle
:封装了用户可以在运行时动态操作的组件数组tim::operation::start<wall_clock>
将尝试在wall_clock
组件实例上调用start()
成员函数wall_clock
是否有store(int)
函数? store()
?)tim::operation::start
:指示组件开始收集tim::operation::sample
:指示组件进行单独测量tim::operation::derive
:来自其他组件的额外数据(如果可用)tim::auto_tuple
tim::component_tuple
tim::component_list
tim::lightweight_tuple
auto_tuple
在构造时启动所有组件,在析构时停止所有组件,而component_tuple
需要显式启动component_tuple
在堆栈上分配所有组件,并且组件“始终处于打开状态”,而component_list
在堆上分配组件,因此可以在运行时激活/停用组件lightweight_tuple
不会隐式执行任何昂贵的操作,例如“存储”中的调用堆栈跟踪注意:对于那些寻求使用 timemory 作为实现自定义工具和接口的工具包的人,推荐使用
tim::lightweight_tuple
包
timemory-avail
中的每个组件都作为独立的 Python 类提供time
命令行工具的扩展版本,包括有关内存使用情况、上下文切换和硬件计数器的附加信息nvidia-smi
数据收集timemory-python-profiler
from timemory.profiler import Profile
timemory-python-trace
from timemory.trace import Trace
timemory-python-line-profiler
from timemory.line_profiler import LineProfiler
在source/timemory/compat/timemory_c.h 和source/timemory/variadic/macros.hpp 中为C 定义了各种宏。在示例中可以找到它们的许多用法示例。
# include " timemory/timemory.hpp "
namespace comp = tim::component;
using namespace tim ;
// specific set of components
using specific_t = component_tuple<comp::wall_clock, comp::cpu_clock>;
using generic_t = component_tuple<comp::user_global_bundle>;
int
main ( int argc, char ** argv)
{
// configure default settings
settings::flat_profile () = true ;
settings::timing_units () = " msec " ;
// initialize with cmd-line
timemory_init (argc, argv);
// add argparse support
timemory_argparse (&argc, &argv);
// create a region "main"
specific_t m{ " main " };
m. start ();
m. stop ();
// pause and resume collection globally
settings::enabled () = false ;
specific_t h{ " hidden " };
h. start (). stop ();
settings::enabled () = true ;
// Add peak_rss component to specific_t
mpl:: push_back_t < specific_t , comp::peak_rss> wprss{ " with peak_rss " };
// create region collecting only peak_rss
component_tuple<comp::peak_rss> oprss{ " only peak_rss " };
// convert component_tuple to a type that starts/stops upon construction/destruction
{
scope::config _scope{};
if ( true ) _scope += scope::flat{};
if ( false ) _scope += scope::timeline{};
convert_t < specific_t , auto_tuple<>> scoped{ " scoped start/stop + flat " , _scope };
// will yield auto_tuple<comp::wall_clock, comp::cpu_clock>
}
// configure the generic bundle via set of strings
runtime::configure<comp::user_global_bundle>({ " wall_clock " , " peak_rss " });
// configure the generic bundle via set of enumeration ids
runtime::configure<comp::user_global_bundle>({ TIMEMORY_WALL_CLOCK, TIMEMORY_CPU_CLOCK });
// configure the generic bundle via component instances
comp::user_global_bundle::configure<comp::page_rss, comp::papi_vector>();
generic_t g{ " generic " , quirk::config<quirk::auto_start>{} };
g. stop ();
// Output the results
timemory_finalize ();
return 0 ;
}
# include " timemory/library.h "
# include " timemory/timemory.h "
int
main ( int argc, char ** argv)
{
// configure settings
int overwrite = 0 ;
int update_settings = 1 ;
// default to flat-profile
timemory_set_environ ( " TIMEMORY_FLAT_PROFILE " , " ON " , overwrite, update_settings);
// force timing units
overwrite = 1 ;
timemory_set_environ ( " TIMEMORY_TIMING_UNITS " , " msec " , overwrite, update_settings);
// initialize with cmd-line
timemory_init_library (argc, argv);
// check if inited, init with name
if (! timemory_library_is_initialized ())
timemory_named_init_library ( " ex-c " );
// define the default set of components
timemory_set_default ( " wall_clock, cpu_clock " );
// create a region "main"
timemory_push_region ( " main " );
timemory_pop_region ( " main " );
// pause and resume collection globally
timemory_pause ();
timemory_push_region ( " hidden " );
timemory_pop_region ( " hidden " );
timemory_resume ();
// Add/remove component(s) to the current set of components
timemory_add_components ( " peak_rss " );
timemory_remove_components ( " peak_rss " );
// get an identifier for a region and end it
uint64_t idx = timemory_get_begin_record ( " indexed " );
timemory_end_record (idx);
// assign an existing identifier for a region
timemory_begin_record ( " indexed/2 " , &idx);
timemory_end_record (idx);
// create region collecting a specific set of data
timemory_begin_record_enum ( " enum " , &idx, TIMEMORY_PEAK_RSS, TIMEMORY_COMPONENTS_END);
timemory_end_record (idx);
timemory_begin_record_types ( " types " , &idx, " peak_rss " );
timemory_end_record (idx);
// replace current set of components and then restore previous set
timemory_push_components ( " page_rss " );
timemory_pop_components ();
timemory_push_components_enum ( 2 , TIMEMORY_WALL_CLOCK, TIMEMORY_CPU_CLOCK);
timemory_pop_components ();
// Output the results
timemory_finalize_library ();
return 0 ;
}
program fortran_example
use timemory
use iso_c_binding, only : C_INT64_T
implicit none
integer (C_INT64_T) :: idx
! initialize with explicit name
call timemory_init_library( " ex-fortran " )
! initialize with name extracted from get_command_argument( 0 , ...)
! call timemory_init_library( " " )
! define the default set of components
call timemory_set_default( " wall_clock, cpu_clock " )
! Start region " main "
call timemory_push_region( " main " )
! Add peak_rss to the current set of components
call timemory_add_components( " peak_rss " )
! Nested region " inner " nested under " main "
call timemory_push_region( " inner " )
! End the " inner " region
call timemory_pop_region( " inner " )
! remove peak_rss
call timemory_remove_components( " peak_rss " )
! begin a region and get an identifier
idx = timemory_get_begin_record( " indexed " )
! replace current set of components
call timemory_push_components( " page_rss " )
! Nested region " inner " with only page_rss components
call timemory_push_region( " inner (pushed) " )
! Stop " inner " region with only page_rss components
call timemory_pop_region( " inner (pushed) " )
! restore previous set of components
call timemory_pop_components()
! end the " indexed " region
call timemory_end_record(idx)
! End " main "
call timemory_pop_region( " main " )
! Output the results
call timemory_finalize_library()
end program fortran_example
from timemory . bundle import marker
@ marker ([ "cpu_clock" , "peak_rss" ])
def foo ():
pass
from timemory . profiler import profile
def bar ():
with profile ([ "wall_clock" , "cpu_util" ]):
foo ()
from timemory . component import WallClock
def spam ():
wc = WallClock ( "spam" )
wc . start ()
bar ()
wc . stop ()
data = wc . get ()
print ( data )
import argparse
parser = argparse . ArgumentParser ( "example" )
# ...
timemory . add_arguments ( parser )
args = parser . parse_args ()
from timemory . storage import WallClockStorage
# data for current rank
data = WallClockStorage . get ()
# combined data on rank zero but all ranks must call it
dmp_data = WallClockStorage . dmp_get ()
Timemory 最初是一个非常简单的工具,用于用 C、C++ 和 Python 记录计时和内存测量(因此得名),在 3.0.0 版本之前仅支持三种模式:一组固定的计时器、一对内存测量、以及两者的结合。在 3.0.0 版本之前,timemory 几乎完全从头开始重写,唯一的例外是一些 C/C++ 宏,例如TIMEMORY_AUTO_TIMER
,以及一些 Python 装饰器和上下文管理器,例如timemory.util.auto_timer
,其行为能够在新版本中完全复制。因此,虽然 timemory 在 v3.0+ 上看起来是一个成熟的项目,但它本质上仍处于第一个主要版本。
要在出版物中引用 timemory,请引用以下论文:
有关更多信息,请参阅文档。