当前位置: 首页 > news >正文

001 Configuration结构体构造

目录

  • DramSys 代码分析
    • 1 Configuration结构体构造
      • 1.1 `from_path` 函数详解
      • 1.2 构造过程总结
      • 这种设计的好处
    • 2 Simulator 例化过程
      • 2.1 instantiateInitiator

DramSys 代码分析

1 Configuration结构体构造

好的,我们来详细解释一下 DRAMSysConfiguration.cpp 文件中 from_path 函数的配置构造过程。这个文件是 DRAMSys 从 JSON 配置文件加载配置的关键部分。

从代码来看,DRAMSys 采用了一种非常巧妙且强大的方式来处理配置:主配置文件中可以引用(嵌入)其他子配置文件。这使得配置模块化,更易于管理。

from_path 函数主要利用了 nlohmann/json 库的强大功能,特别是它的**解析回调(parser callback)**机制,实现了在解析过程中动态加载和替换 JSON 内容。

1.1 from_path 函数详解

#include "DRAMSysConfiguration.h" // 包含配置结构体的定义
#include "DRAMSys/config/MemSpec.h" // 包含 MemSpec 相关的常量和结构体#include <fstream> // 用于文件操作
#include <nlohmann/json.hpp> // nlohmann/json 库namespace DRAMSys::Config
{// 这是核心函数,从给定路径的配置文件中构造 Configuration 对象
Configuration from_path(std::filesystem::path baseConfig)
{// 1. 打开主配置文件std::ifstream file(baseConfig); // 使用 std::ifstream 打开 JSON 文件std::filesystem::path baseDir = baseConfig.parent_path(); // 获取配置文件所在的目录,用于构建子配置文件的绝对路径// 2. 定义内部枚举类,用于识别当前正在处理的子配置类型enum class SubConfig{MemSpec,AddressMapping,McConfig,SimConfig,TraceSetup,Unkown // 未知类型} current_sub_config; // 声明一个变量来存储当前识别到的子配置类型// 3. 定义自定义解析回调函数// 这是一个 std::function 对象,它会在 nlohmann::json 解析 JSON 文件时被调用// 它的作用是在遇到特定的键(例如 "MemSpec")时,将该键对应的值(通常是子配置文件的文件名字符串)// 替换为实际解析后的子配置文件 JSON 对象。std::function<bool(int depth, nlohmann::detail::parse_event_t event, json_t& parsed)>parser_callback;parser_callback = [&parser_callback, &current_sub_config, baseDir](int depth, nlohmann::detail::parse_event_t event, json_t& parsed) -> bool{using nlohmann::detail::parse_event_t;// nlohmann::json 的解析回调会在解析 JSON 文件的不同事件(如开始对象、遇到键、遇到值等)触发// depth 表示当前解析的 JSON 深度。// event 表示触发回调的事件类型。// parsed 表示当前解析到的 JSON 值(可能是键名、字符串、数字、对象等)。// 我们只关心深度为 2 的事件。DRAMSys 的主配置文件可能在顶层(深度0)有一个总键(如"DRAMSys"),// 接着是各个子配置的键(如"MemSpec"、"AddressMapping"),这些键的值是文件的路径字符串。// 比如:// {//   "DRAMSys": {  // depth 1//     "MemSpec": "memory.json", // depth 2: "MemSpec" 是 key,"memory.json" 是 value//     "AddressMapping": "address.json",//     // ...//   }// }if (depth != 2)return true; // 如果深度不是2,则不处理,直接返回 true 继续解析// 处理“键”(key)事件if (event == parse_event_t::key){assert(parsed.is_string()); // 断言当前解析到的值是字符串(即键名)// 根据键名识别当前正在处理的子配置类型if (parsed == MemSpecConstants::KEY) // 例如 "MemSpec"current_sub_config = SubConfig::MemSpec;else if (parsed == AddressMapping::KEY) // 例如 "AddressMapping"current_sub_config = SubConfig::AddressMapping;else if (parsed == McConfig::KEY) // 例如 "McConfig"current_sub_config = SubConfig::McConfig;else if (parsed == SimConfig::KEY) // 例如 "SimConfig"current_sub_config = SubConfig::SimConfig;else if (parsed == TraceSetupConstants::KEY) // 例如 "TraceSetup"current_sub_config = SubConfig::TraceSetup;elsecurrent_sub_config = SubConfig::Unkown; // 未识别的键}// 处理“值”(value)事件// 只有当当前识别到的子配置类型不是未知(即我们之前识别到了一个有效的子配置键)// 并且当前事件是 value 时才进入此逻辑。if (event == parse_event_t::value && current_sub_config != SubConfig::Unkown){// 在这里,`parsed` 变量包含了子配置文件的文件名字符串(例如 "memory.json")。// 我们的目标是将这个字符串替换为实际解析后的 JSON 对象。// 定义一个 lambda 表达式 `parse_json`,用于加载和解析子 JSON 文件auto parse_json = [&parser_callback, baseDir](std::string_view sub_config_key,const std::string& filename) -> json_t{// 构建子配置文件的完整路径std::filesystem::path path{baseDir}; // 以主配置文件所在目录为基础path /= filename; // 拼接子文件名std::ifstream json_file(path); // 打开子配置文件if (!json_file.is_open())throw std::runtime_error("Failed to open file " + std::string(path)); // 错误处理:文件无法打开// 递归地解析子 JSON 文件。// 注意这里再次使用了 `parser_callback`。这意味着子配置文件中如果也包含对其他子配置文件的引用,// 也可以被这个机制处理,形成一个递归加载的过程。// `json_t::parse(json_file, parser_callback, true, true)` 会解析文件并应用回调。// `.at(sub_config_key)` 是因为子配置文件可能也有一个顶层键,例如 `{"MemSpec": {...}}`。json_t json =json_t::parse(json_file, parser_callback, true, true).at(sub_config_key);return json;};// 根据之前识别到的 `current_sub_config` 类型,调用 `parse_json` 来加载对应的子文件// 并将 `parsed` 变量(它原本是文件名字符串)替换为解析后的 JSON 对象if (current_sub_config == SubConfig::MemSpec)parsed = parse_json(MemSpecConstants::KEY, parsed);else if (current_sub_config == SubConfig::AddressMapping)parsed = parse_json(AddressMapping::KEY, parsed);else if (current_sub_config == SubConfig::McConfig)parsed = parse_json(McConfig::KEY, parsed);else if (current_sub_config == SubConfig::SimConfig)parsed = parse_json(SimConfig::KEY, parsed);else if (current_sub_config == SubConfig::TraceSetup)parsed = parse_json(TraceSetupConstants::KEY, parsed);}return true; // 返回 true 继续解析过程};// 4. 开始解析主配置文件if (file.is_open()){// 调用 nlohmann::json 的 parse 函数,传入文件流和自定义的 parser_callback// `true, true` 参数表示:// 第一个 true: allow_exceptions - 允许抛出解析异常// 第二个 true: ignore_comments - 忽略 JSON 中的注释// `.at(Configuration::KEY)`: 主配置文件可能有一个顶层键(例如 "DRAMSys"),我们需要进入这个键对应的对象。json_t simulation = json_t::parse(file, parser_callback, true, true).at(Configuration::KEY);// 5. 将最终解析得到的完整 JSON 对象(包含了所有内嵌子配置)// 反序列化为 DRAMSys::Config::Configuration C++ 结构体。// 这需要 Configuration 结构体及其所有嵌套结构体(如 MemSpec、AddressMapping 等)// 都使用了 nlohmann::json 的 `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` 或类似的机制进行了序列化/反序列化定义。return simulation.get<Config::Configuration>();}// 6. 文件打开失败的错误处理throw std::runtime_error("Failed to open file " + std::string(baseConfig));
}} // namespace DRAMSys::Config

1.2 构造过程总结

  1. 打开主配置文件: 函数首先尝试打开 baseConfig 指定的主 JSON 配置文件。
  2. 获取基础目录: 记录主配置文件的父目录 baseDir,这对于构建子配置文件的相对路径至关重要。
  3. 定义 SubConfig 枚举: 这是一个内部枚举,用于在解析过程中识别当前处理的是哪种类型的子配置(例如 MemSpec、AddressMapping 等)。
  4. 核心:自定义解析回调 parser_callback
    • 这个回调函数是整个机制的核心。它会在 nlohmann::json 解析 JSON 文件时,针对不同的事件(如遇到键、遇到值)和深度被调用。
    • 识别子配置键: 当解析深度为 2 且事件为 key 时(例如解析到 "MemSpec"),回调会根据键名设置 current_sub_config 变量,以识别当前要加载的子配置类型。
    • 替换文件名字符串为 JSON 对象: 当解析深度为 2 且事件为 value 时(此时 parsed 变量是子配置文件的文件名字符串,例如 "memory.json"),回调会执行以下操作:
      • 构建子配置文件的完整路径(baseDir + filename)。
      • 递归调用 json_t::parse 打开并解析这个子配置文件。关键在于,这里再次传入了 parser_callback。这意味着如果子配置文件内部也引用了其他子配置文件,这个机制可以递归地处理它们,实现多层级的配置嵌套。
      • 从解析后的子 JSON 中提取出对应子配置根键下的内容(例如 {"MemSpec": {...}}{...} 部分)。
      • parsed 变量(原始的文件名字符串)替换为这个新解析出来的子 JSON 对象。
  5. 启动主文件解析: 调用 json_t::parse(file, parser_callback, true, true) 来启动对主配置文件的解析,并将 parser_callback 应用于整个解析过程。
  6. 反序列化为 C++ 对象: 一旦整个 JSON 对象(包括所有内嵌的子配置)被成功解析和组装,最后一步是调用 .get<Config::Configuration>()。这会将完整的 nlohmann::json::json_t 对象反序列化(deserialize)成 DRAMSys::Config::Configuration 结构体的实例。这要求 Configuration 结构体及其所有成员(例如 MemSpec, AddressMapping, McConfig 等结构体)都必须使用 nlohmann/json 提供的宏(如 NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE)或者自定义的 to_json/from_json 函数进行了注册,以便 nlohmann/json 知道如何将 JSON 数据映射到 C++ 类型。

这种设计的好处

  • 模块化和可重用性: 不同的子配置(如内存规格、地址映射)可以存储在独立的文件中,便于管理和在不同项目中重用。
  • 清晰的结构: 主配置文件可以像目录一样,组织和引用各个部分的配置。
  • 灵活性: 支持多层级的配置嵌套。
  • 解耦: 允许配置文件的编写者专注于特定模块的参数,而不需要在一个巨大的文件中定义所有内容。
  • 易于扩展: 当需要添加新的配置类型时,只需在 SubConfig 枚举中添加新类型,并在回调中增加相应的处理逻辑即可。

这个 DRAMSysConfiguration.cpp 文件展示了 nlohmann/json 库在复杂配置管理方面的强大应用,特别是其解析回调机制,使得实现动态加载和组装配置成为可能。

2 Simulator 例化过程

  1. 首先通过teminateInitiator设置了终止仿真条件。
  2. 通过finishTransaction设置了仿真进度条。
  3. 关键是如何设置仿真的激励,即trace

2.1 instantiateInitiator

  
Simulator通过configure配置发起方针的initiator,这里的initiator可以有多个,有多少个取决于配置的json文件;
  
instantiateInitiator函数本身并不是一个构造函数,而是一个负责根据配置创建不同类型Initiator对象(流量发起器)的工厂方法。它根据不同的配置创建对应的流量发起器。

  
使用std::visit来处理std::variant类型的配置(这里initiator为什么是std::variant类型的配置?)

  1. 获取公共参数: 在开始创建具体的 Initiator 之前,函数首先从 dramSys 模块获取一些所有发起器都可能需要的公共参数,例如模拟内存的总大小、DRAM 接口的时钟周期以及默认的每次突发传输的字节数。

  2. std::variant 和 std::visit 的使用:

    • DRAMSys::Config::Initiator 结构体内部包含一个 std::variant 成员(通过 initiator.getVariant() 访问),这个 variant 可以持有不同类型的发起器配置(TrafficGenerator, TracePlayer, RowHammer)。

    • std::visit 是 C++17 引入的一个工具,它允许你对 std::variant 中当前激活的类型执行相应的操作。它会根据 variant 中实际存储的类型,调用 lambda 表达式中对应的 if constexpr 分支。

  3. 类型判别与实例化:

    • if constexpr 语句在编译时判断 config 的具体类型 (T)。

    • TrafficGenerator: 如果配置是 TrafficGenerator 或 TrafficGeneratorStateMachine 类型,它会直接创建并返回一个 TrafficGenerator 对象,并传入其特有的配置以及公共的模拟参数、内存管理器和回调函数。

    • TracePlayer: 如果配置是 TracePlayer 类型,函数会进一步根据轨迹文件的扩展名(.stl 或 .rstl)来确定轨迹类型(绝对时间或相对时间)。然后,它会创建一个 StlPlayer 对象(负责读取和解析轨迹文件),并将其包装在一个 SimpleInitiator 中返回。SimpleInitiator 是一个更通用的发起器模板,可以适配不同的流量源。

    • RowHammer: 如果配置是 RowHammer 类型,它会创建一个 RowHammer 对象(实现 Row Hammer 逻辑),同样将其包装在一个 SimpleInitiator 中返回。

  4. 返回 std::unique_ptr: 无论是哪种类型的发起器,instantiateInitiator 函数最终都会返回一个 std::unique_ptr。这意味着它返回一个指向基类 Initiator 的智能指针,确保了内存的安全管理和多态性,允许 Simulator 以统一的方式管理不同类型的发起器。

简而言之,instantiateInitiator 函数是一个动态的工厂,它根据配置文件中指定的确切类型,“构造”出并返回相应功能的流量发起器对象,这些对象都以 Initiator 接口的形式提供给模拟器使用。

http://www.dtcms.com/a/304868.html

相关文章:

  • 【C++篇】“内存泄露”的宝藏手段:智能指针
  • OpenCV 学习探秘之三:从图像读取到特征识别,再到机器学习等函数接口的全面实战应用与解析
  • Excel批量加密工具,一键保护多个文件
  • 【图像处理基石】如何对遥感图像进行实例分割?
  • 【RAG搭建Agent应用实战】基于检索增强生成(RAG)搭建特定场景Agent应用
  • Spring Boot 防重放攻击全面指南:原理、方案与最佳实践
  • AI产品经理手册(Ch3-5)AI Product Manager‘s Handbook学习笔记
  • 【Linux基础】find在linux中查找文件
  • Jenkins 详解
  • 准大一GIS专业新生,如何挑选电脑?
  • 【Kotlin】const 修饰的编译期常量
  • 医疗超声成像专用AFE模拟前端
  • 【CSS】盒子类型
  • Qwen3-Coder:介绍及使用 -- 超强AI编程助手
  • CSRF漏洞原理及利用
  • 镜像源加速下载
  • 编辑距离:理论基础、算法演进与跨领域应用
  • 百度前端面试题目整理
  • 通过Power Automate获取SharePoint的Share Link
  • 计算机视觉(CV方向)算法基础
  • Apache Ignite 的连续查询(Continuous Queries)功能的详细说明
  • Apache Ignite 关于 容错(Fault Tolerance)的核心机制
  • 零件边界线提取处理原理详解
  • 如何解决人工智能在社会治理中面临的技术和伦理挑战?
  • 【工具】图床完全指南:从选择到搭建的全方位解决方案
  • 赢在AI时代:从创造力到编程力的教育突围
  • 聊聊自动化测试用例维护成本高应对策略
  • IntelliJ IDEA 配置 Maven 阿里云镜像加速源全流程
  • AAA 与 FTP:网络认证授权及文件传输的原理与实践
  • AT8236-4A单通道直流有刷电机驱动芯片