哈哈哈,我们就从main函数开始,看看BookSim2都调用了哪些核心函数。
视频教程:1.2 BookSim2源码解读1:核心函数调用逻辑_哔哩哔哩_bilibili
一、main()
我们从main()函数开始,前面都是获取,监控输出啥的,重点关注
bool result = Simulate( config );
Simulate这个函数,配置并执行仿真器。
源码速递:
源码位置:booksim2-master\src\main.cpp
// 主函数,接受命令行参数
int main( int argc, char **argv )
{
// 创建一个BookSimConfig类型的对象config,用于存储和配置模拟器的参数
BookSimConfig config;
// 调用ParseArgs函数,尝试解析命令行参数并填充到config对象中
// 如果解析失败(可能是参数格式不正确或缺少必要的参数),则输出使用说明并返回0
if ( !ParseArgs( &config, argc, argv ) ) {
cerr << "Usage: " << argv[0] << " configfile... [param=value...]" << endl;
return 0;
}
// 初始化路由、流量和注入函数(这些函数的具体实现没有在这段代码中给出)
/*initialize routing, traffic, injection functions*/
InitializeRoutingMap( config );
// 根据config中的"print_activity"参数值来设置gPrintActivity变量的值
// 如果"print_activity"大于0,则gPrintActivity为true,否则为false
gPrintActivity = (config.GetInt("print_activity") > 0);
// 根据config中的"viewer_trace"参数值来设置gTrace变量的值
// 如果"viewer_trace"大于0,则gTrace为true,否则为false
gTrace = (config.GetInt("viewer_trace") > 0);
// 获取config中的"watch_out"参数值,这是一个字符串,可能用于指定一个输出文件的名称
string watch_out_file = config.GetStr( "watch_out" );
// 根据watch_out_file的值来设置gWatchOut变量的值
// 如果watch_out_file为空字符串,则gWatchOut为NULL
// 如果watch_out_file为"-",则gWatchOut指向cout(即标准输出)
// 否则,创建一个新的ofstream对象,并将gWatchOut指向它,用于将输出写入到指定的文件中
if(watch_out_file == "") {
gWatchOut = NULL;
} else if(watch_out_file == "-") {
gWatchOut = &cout;
} else {
gWatchOut = new ofstream(watch_out_file.c_str());
}
// 配置并运行模拟器,返回一个bool值表示模拟是否成功
/*configure and run the simulator*/
bool result = Simulate( config );
// 如果模拟成功(result为true),则返回-1,否则返回0
// 返回值的具体意义可能根据程序的上下文而定,但通常非零值表示某种错误或异常
return result ? -1 : 0;
}
二、Simulate()
核心逻辑:
1、获取配置,初始化Network和TrafficManager
2、仿真 trafficManager->Run() ;
哈哈哈,就这两步,是不是很简单,其它获取程序运行时间,销毁资源先不管。
源码速递:
源码位置:booksim2-master\src\main.cpp
// Simulate函数,用于配置并运行模拟器
bool Simulate( BookSimConfig const & config )
{
// 创建一个vector,用于存储指向Network对象的指针
vector<Network *> net;
// 从配置中获取子网的数量
int subnets = config.GetInt("subnets");
// 注释说明:要添加新的网络,需要在这里注册网络
// 如果要添加新的网络类型,可以在下面添加else if语句,并使用相应的网络名称
// 根据子网数量调整net vector的大小
net.resize(subnets);
// 遍历每个子网,并创建对应的Network对象
for (int i = 0; i < subnets; ++i) {
ostringstream name; // 创建一个输出字符串流,用于构建网络名称
name << "network_" << i; // 将"network_"和子网索引添加到输出字符串流中
net[i] = Network::New( config, name.str() ); // 调用Network类的静态方法New来创建新的Network对象,并将配置和名称作为参数
}
// 注释说明:tcc和characterize是遗留的(legacy)代码,不确定如何使用它们
// 断言确保trafficManager当前为NULL,以避免内存泄漏或意外覆盖
assert(trafficManager == NULL);
// 创建TrafficManager对象,并传入配置和net vector作为参数
trafficManager = TrafficManager::New( config, net ) ;
// 注释说明:开始模拟运行
// 定义总运行时间变量
double total_time; /* 运行的总时间 */
// 定义两个timeval结构体,用于获取开始和结束时间
struct timeval start_time, end_time; /* 时间在用户代码执行前/后 */
total_time = 0.0; // 初始化总运行时间为0
// 获取当前时间作为模拟开始时间
gettimeofday(&start_time, NULL);
// 运行TrafficManager的Run方法,开始模拟
bool result = trafficManager->Run() ;
// 获取模拟结束时间
gettimeofday(&end_time, NULL);
// 计算总运行时间(秒)
total_time = ((double)(end_time.tv_sec) + (double)(end_time.tv_usec)/1000000.0)
- ((double)(start_time.tv_sec) + (double)(start_time.tv_usec)/1000000.0);
// 输出总运行时间
cout<<"Total run time "<<total_time<<endl;
// 遍历每个子网
for (int i=0; i<subnets; ++i) {
// 如果配置中指定了进行功耗分析(sim_power大于0)
/// 功耗分析
if(config.GetInt("sim_power") > 0){
// 创建Power_Module对象,并传入net[i]和config作为参数
Power_Module pnet(net[i], config);
// 运行Power_Module的run方法,进行功耗分析
pnet.run();
}
// 删除当前子网的Network对象,释放内存
delete net[i];
}
// 删除TrafficManager对象,释放内存
delete trafficManager;
trafficManager = NULL; // 将trafficManager设置为NULL,以避免悬挂指针
// 返回模拟运行的结果(成功为true,失败为false)
return result;
}
三、TrafficManager::Run
核心逻辑:
1、清除上次仿真的内容,重置参数
2、_SingleSim()进行本次仿真
以上过程循环多次,仿真多次。
源码速递:
booksim2-master\src\trafficmanager.cpp
// 定义TrafficManager类的Run方法,该方法返回一个布尔值
bool TrafficManager::Run( )
{
// 开始一个循环,执行_total_sims次模拟
for ( int sim = 0; sim < _total_sims; ++sim ) {
// 重置模拟时间为0
_time = 0;
// 清除任何前一次模拟中待处理的请求
// 使用assign方法将_requestsOutstanding数组中的所有元素设置为0
_requestsOutstanding.assign(_nodes, 0);
// 遍历所有节点
for (int i=0;i<_nodes;i++) {
// 当_repliesPending[i](某个节点的待处理回复队列)不为空时
while(!_repliesPending[i].empty()) {
// 释放队列中第一个回复所占用的资源(假设Free方法执行此操作)
_repliesPending[i].front()->Free();
// 从队列中移除已释放的回复
_repliesPending[i].pop_front();
}
}
// 重置所有源的队列时间
for ( int s = 0; s < _nodes; ++s ) {
// 使用assign方法将_qtime[s](某个源的队列时间数组)中的所有元素设置为0
_qtime[s].assign(_classes, 0);
// 将_qdrained[s](某个源是否已排空队列的标记数组)中的所有元素设置为false
_qdrained[s].assign(_classes, false);
}
// 模拟的预热阶段开始...
// 重置统计信息,所有在warmup_time之后标记的数据包将被视为收敛的
// (这里可能还有关于排空的逻辑,但代码没有直接显示)
_sim_state = warming_up; // 设置模拟状态为预热中
// 清除统计信息
_ClearStats( );
// 重置所有类别的流量模式和注入过程
for(int c = 0; c < _classes; ++c) {
_traffic_pattern[c]->reset();
_injection_process[c]->reset();
}
// 执行单次模拟
if ( !_SingleSim( ) ) {
// 如果单次模拟不稳定,输出错误消息并结束整个Run方法
cout << "Simulation unstable, ending ..." << endl;
return false;
}
// 清空网络中的剩余数据包
cout << "Draining remaining packets ..." << endl;
_empty_network = true; // 设置标志为正在清空网络
int empty_steps = 0; // 记录清空步骤的计数器
bool packets_left = false; // 标记是否还有剩余数据包
// 检查每个类别的_total_in_flight_flits队列是否为空
for(int c = 0; c < _classes; ++c) {
// 如果某个类别的队列不为空,则将packets_left设置为true
packets_left |= !_total_in_flight_flits[c].empty();
}
// 当还有剩余数据包时,持续执行
while( packets_left ) {
// 执行模拟的一步
_Step( );
// 增加清空步骤的计数器
++empty_steps;
// 每1000步显示一次剩余数据包的信息
if ( empty_steps % 1000 == 0 ) {
_DisplayRemaining( );
}
// 再次检查是否还有剩余数据包
packets_left = false;
for(int c = 0; c < _classes; ++c) {
// 如果某个类别的队列不为空,则将packets_left设置为true
packets_left |= !_total_in_flight_flits[c].empty();
}
}
// 等待直到所有的信用都被消耗掉
while(Credit::OutStanding()!=0){
_Step(); // 继续执行模拟步骤直到没有信用剩余
}
// 清空网络模式结束
_empty_network = false;
// 设置标志为不再清空网络
_empty_network = false;
// 这是一个注释,警告不要在其他地方说"Time taken",可能是因为某个脚本或工具依赖于
// 这个特定的输出格式或位置,以确保正确的解析或处理
// "for the love of god don't ever say "Time taken" anywhere else"
// "为了上帝的爱,千万不要在其他地方说'Time taken'"
//the power script depend on it // 这里的脚本依赖于这个输出
// 输出模拟所花费的时间(以周期为单位)
cout << "Time taken is " << _time << " cycles" << endl;
// 如果_stats_out是一个有效的输出流(可能是文件流),则写入统计信息
if(_stats_out) {
WriteStats(*_stats_out);
}
// 更新全局或总体的统计信息
_UpdateOverallStats();
// 显示总体统计信息
DisplayOverallStats();
}
// 如果设置了_print_csv_results标志,则以CSV格式显示总体统计信息
if(_print_csv_results) {
DisplayOverallStatsCSV();
}
// 如果所有模拟都成功执行,则返回true
return true;
}
四、TrafficManager::_SingleSim
我们先大概理解三个阶段
warming_up:预热阶段,正式开始信息统计之前,先模拟一段时间,进行模拟预热。
running:运行阶段,模拟并且按周期输出数据统计报告。
draining:排放阶段,运行阶段完成后处理未处理的数据包。
核心逻辑
1、先预热
2、运行并统计信息
3、排放阶段,处理未处理的数据包。
哈哈哈,是不是很简单。其实这三个过程,核心都是在执行_Step()这个函数,只是目的不一样。一个为了预热、一个为了运行并统计信息、一个是处理未处理的数据包。
未什么这个代码看起来很复杂呢,因为它有很多情况要处理。
1、预热阶段
1a.如果满足运行条件,进入运行阶段。
1b.如果出现重大异常,结束循环。
1c.如果模拟的总阶段数大于_max_samples,退出主循环。
2、运行阶段
2a.如果三次收敛运行(未出现任何异常),进入排放阶段,否则继续运行。
2b.如果出现重大异常,结束循环。
2c.如果模拟的总阶段数大于_max_samples,进入排放阶段。
3、排放阶段
源码速递
源码位置:booksim2-master\src\trafficmanager.cpp
// TrafficManager类的_SingleSim方法,用于模拟交通管理系统的单次运行
bool TrafficManager::_SingleSim() {
int converged = 0; // 初始化收敛计数器为0
// 创建一个存储上一次延迟的向量,大小为_classes(交通类别的数量)
// 并将所有元素初始化为0.0
vector<double> prev_latency(_classes, 0.0);
// 创建一个存储上一次接受率的向量,大小为_classes,并将所有元素初始化为0.0
vector<double> prev_accepted(_classes, 0.0);
bool clear_last = false; // 标记是否需要清除最后一次统计数据的布尔值,初始化为false
int total_phases = 0; // 初始化模拟的总阶段数为0
// 当模拟的总阶段数小于_max_samples且模拟状态不是running或未达到连续3次收敛时,继续循环
while( ( total_phases < _max_samples ) &&
( ( _sim_state != running ) ||
( converged < 3 ) ) ) {
// 如果需要清除最后一次的统计数据或模拟状态为warming_up且总阶段数为偶数,则清除统计数据
if ( clear_last || (( ( _sim_state == warming_up ) && ( ( total_phases % 2 ) == 0 ) )) ) {
clear_last = false; // 清除后标记为不再需要清除
_ClearStats(); // 调用清除统计数据的方法
}
// 模拟执行_sample_period次迭代(可能是时间步或模拟事件)
for ( int iter = 0; iter < _sample_period; ++iter )
_Step(); // 调用模拟的单个步骤方法
// 更新统计信息
UpdateStats();
// 显示统计信息
DisplayStats();
// 初始化一些用于记录变化的变量
int lat_exc_class = -1; // 延迟异常的类别,初始化为-1(表示无异常)
int lat_chg_exc_class = -1; // 延迟变化异常的类别,初始化为-1
int acc_chg_exc_class = -1; // 接受率变化异常的类别,初始化为-1
// 遍历每个交通类别
for(int c = 0; c < _classes; ++c) {
// 如果该类别的度量统计为0,则跳过
if(_measure_stats[c] == 0) {
continue;
}
// 计算当前延迟
double cur_latency = _plat_stats[c]->Average();
// 计算总接受率(需要_ComputeStats方法提供total_accepted_count)
int total_accepted_count;
_ComputeStats( _accepted_flits[c], &total_accepted_count );
double total_accepted_rate = (double)total_accepted_count / (double)(_time - _reset_time);
double cur_accepted = total_accepted_rate / (double)_nodes;
// 计算延迟变化
double latency_change = fabs((cur_latency - prev_latency[c]) / cur_latency);
prev_latency[c] = cur_latency; // 更新上一次延迟
// 计算当前类别的接受率变化
double accepted_change = fabs((cur_accepted - prev_accepted[c]) / cur_accepted);
// 更新上一次接受率
prev_accepted[c] = cur_accepted;
// 从_plat_stats中获取当前类别的累积延迟
double latency = (double)_plat_stats[c]->Sum();
// 从_plat_stats中获取当前类别的样本数量
double count = (double)_plat_stats[c]->NumSamples();
// 遍历当前类别中所有在飞行中的Flit
map<int, Flit *>::const_iterator iter;
for(iter = _total_in_flight_flits[c].begin();
iter != _total_in_flight_flits[c].end();
iter++) {
// 对于每个在飞行中的Flit,将其从创建时间到当前时间的延迟加到latency中
latency += (double)(_time - iter->second->ctime);
// 增加计数,因为又考虑了一个Flit
count++;
}
// 检查当前类别的延迟是否超过阈值
if((lat_exc_class < 0) &&
(_latency_thres[c] >= 0.0) &&
((latency / count) > _latency_thres[c])) {
// 如果超过阈值,则记录该类别为延迟异常的类别
lat_exc_class = c;
}
// 输出当前类别的延迟变化
cout << "latency change = " << latency_change << endl;
// 检查当前类别的延迟变化是否超过阈值
if(lat_chg_exc_class < 0) {
// 如果模拟状态是warming_up,并且warmup_threshold阈值有效且超过阈值
if((_sim_state == warming_up) &&
(_warmup_threshold[c] >= 0.0) &&
(latency_change > _warmup_threshold[c])) {
// 记录该类别为延迟变化异常的类别
lat_chg_exc_class = c;
}
// 如果模拟状态是running,并且stopping_threshold阈值有效且超过阈值
else if((_sim_state == running) &&
(_stopping_threshold[c] >= 0.0) &&
(latency_change > _stopping_threshold[c])) {
// 记录该类别为延迟变化异常的类别
lat_chg_exc_class = c;
}
}
// 输出当前类别的接受率变化
cout << "throughput change = " << accepted_change << endl;
// 检查当前类别的接受率变化是否超过阈值
if(acc_chg_exc_class < 0) {
// 如果模拟状态是warming_up,并且acc_warmup_threshold阈值有效且超过阈值
if((_sim_state == warming_up) &&
(_acc_warmup_threshold[c] >= 0.0) &&
(accepted_change > _acc_warmup_threshold[c])) {
// 记录该类别为接受率变化异常的类别
acc_chg_exc_class = c;
}
// 如果模拟状态是running,并且acc_stopping_threshold阈值有效且超过阈值
else if((_sim_state == running) &&
(_acc_stopping_threshold[c] >= 0.0) &&
(accepted_change > _acc_stopping_threshold[c])) {
// 记录该类别为接受率变化异常的类别
acc_chg_exc_class = c;
}
}
// 如果正在测量延迟,并且延迟异常类别(lat_exc_class)是非负的(表示有延迟异常)
if ( _measure_latency && ( lat_exc_class >= 0 ) ) {
// 打印出哪个类别的平均延迟超过了设定的阈值,并宣布终止模拟
cout << "Average latency for class " << lat_exc_class << " exceeded " << _latency_thres[lat_exc_class] << " cycles. Aborting simulation." << endl;
// 将收敛标志设置为0,表示模拟没有收敛
converged = 0;
// 将模拟状态设置为draining,意味着模拟开始排空阶段
_sim_state = draining;
// 设置_drain_time为当前时间_time,可能是为了记录模拟排空开始的时间
_drain_time = _time;
// 如果启用了统计输出(_stats_out非空),则写入统计信息到_stats_out
if(_stats_out) {
WriteStats(*_stats_out);
}
// 跳出当前循环(可能是_SingleSim函数的主体循环)
break;
}
// 如果模拟状态是warming_up(预热阶段)
if ( _sim_state == warming_up ) {
// 如果设置了预热周期(_warmup_periods大于0),则根据总阶段数(total_phases)判断是否达到预热周期
// 否则,如果不测量延迟或延迟变化没有异常,并且接受率变化也没有异常,则也判断为预热完成
if ( ( _warmup_periods > 0 ) ?
( total_phases + 1 >= _warmup_periods ) :
( ( !_measure_latency || ( lat_chg_exc_class < 0 ) ) &&
( acc_chg_exc_class < 0 ) ) ) {
// 打印出预热完成的消息,并显示使用了多少周期
cout << "Warmed up ..." << "Time used is " << _time << " cycles" << endl;
// 可能是为了清空某些上一阶段的状态或数据
clear_last = true;
// 将模拟状态设置为running,表示预热阶段结束,进入运行阶段
_sim_state = running;
}
}
// 如果模拟状态是running(运行阶段)
else if(_sim_state == running) {
// 如果不测量延迟或延迟变化没有异常,并且接受率变化也没有异常,则增加收敛计数器
if ( ( !_measure_latency || ( lat_chg_exc_class < 0 ) ) &&
( acc_chg_exc_class < 0 ) ) {
++converged;
}
// 如果有任何异常(延迟或接受率),则重置收敛计数器
else {
converged = 0;
}
}
// 无论模拟处于哪个阶段,都增加总阶段数
++total_phases;
}
// 检查当前模拟状态是否为running
if ( _sim_state == running ) {
// 如果模拟是running状态,则converged计数器加1
++converged;
// 将模拟状态更改为draining
_sim_state = draining;
// 记录draining状态开始的时间为当前时间_time
_drain_time = _time;
// 如果_measure_latency为真(即需要测量延迟)
if ( _measure_latency ) {
// 在控制台上打印消息,表明正在清空所有已记录的数据包
cout << "Draining all recorded packets ..." << endl;
// 初始化一个计数器empty_steps,用于跟踪清空数据包的步骤数
int empty_steps = 0;
// 当还有未处理的数据包时,继续循环
while( _PacketsOutstanding( ) ) {
// 执行模拟的一个步骤
_Step( );
// 每执行一个步骤,empty_steps计数器加1
++empty_steps;
// 如果empty_steps是1000的倍数(即每1000个步骤检查一次)
if ( empty_steps % 1000 == 0 ) {
// 初始化一个变量lat_exc_class,用于存储超过延迟阈值的类索引
int lat_exc_class = -1;
// 遍历所有类
for(int c = 0; c < _classes; c++) {
// 获取当前类的延迟阈值
double threshold = _latency_thres[c];
// 如果阈值为负,跳过当前类
if(threshold < 0.0) {
continue;
}
// 计算当前类的累积延迟
double acc_latency = _plat_stats[c]->Sum();
// 计算当前类的样本数量
double acc_count = (double)_plat_stats[c]->NumSamples();
// 遍历当前类所有在飞行中的Flit(可能是数据包片段)
map<int, Flit *>::const_iterator iter;
for(iter = _total_in_flight_flits[c].begin();
iter != _total_in_flight_flits[c].end();
iter++) {
// 将每个Flit的延迟加到累积延迟中
acc_latency += (double)(_time - iter->second->ctime);
// 样本数量加1
acc_count++;
}
// 如果当前类的平均延迟超过了阈值
if((acc_latency / acc_count) > threshold) {
// 将lat_exc_class设置为当前类的索引
lat_exc_class = c;
// 跳出循环,不再检查其他类
break;
}
}
// 如果lat_exc_class非负,说明有类的延迟超过了阈值
if(lat_exc_class >= 0) {
// 在控制台上输出消息,表明指定类的平均延迟超过了阈值,并决定中止模拟
cout << "Average latency for class " << lat_exc_class << " exceeded " << _latency_thres[lat_exc_class] << " cycles. Aborting simulation." << endl;
// 重置converged为0
converged = 0;
// 将模拟状态重置为warming_up
_sim_state = warming_up;
// 如果_stats_out指向一个有效的输出流,则写入统计信息
if(_stats_out) {
WriteStats(*_stats_out);
}
// 跳出循环,不再继续清空数据包
break;
}
// 注意:这里原本的代码被截断了,但似乎应该有一个_DisplayRemaining()的调用,用于显示剩余的某些内容或数据
// _DisplayRemaining( );
}
}
}
}
下期预告:
_Step()仿真一步(可能有)