可以对 gem5 模拟的能量和功耗使用情况进行建模和监控。这是通过使用 gem5 已经记录的各种统计信息,并在 MathExprPowerModel 中使用它们来实现的;MathExprPowerModel 是一种通过数学方程来模拟功耗的方法。本教程章节++详细介绍了功耗建模所需的各个组件,并解释了如何将它们添加到现有的 ARM 模拟中++。
本章节借鉴了 configs/example/arm 目录中提供的 fs_power.py 配置脚本,并提供了如何扩展此脚本或其他脚本的说明。
请注意,功耗模型只能在使用了更详细的"timing" CPU 时应用。
关于功耗建模如何内置于 gem5 中以及它们与模拟器其他部分如何交互的概述,可以在 Sascha Bischoff 在 2017 年 ARM 研究峰会上的演示文稿中找到。
动态功耗状态
功耗模型由两个函数组成,这两个函数描述了如何计算不同功耗状态下的功耗消耗。这些功耗状态是(来自 src/sim/PowerState.py):
-
UNDEFINED:无效状态,没有可用的功耗状态派生信息。此状态是默认状态。 -
ON:根据所需处理的数量,逻辑块主动运行,并消耗动态的和泄漏的能量。 -
CLK_GATED:逻辑块内的时钟电路被门控 以节省动态能量,块的电源仍然开启,并且逻辑块正在消耗泄漏能量。 -
SRAM_RETENTION:逻辑块内的SRAM 被置于保持状态,以进一步减少泄漏能量。 -
OFF:逻辑块被电源门控,不消耗任何能量。
使用 PowerModel 类的 pm 字段,为除 UNDEFINED 之外的每个状态分配一个功耗模型。它是一个包含 4 个功耗模型的列表,按以下顺序每个状态一个:
-
ON -
CLK_GATED -
SRAM_RETENTION -
OFF
请注意,尽管有 4 个不同的条目,但它们不必是不同的功耗模型。提供的 fs_power.py 文件对 ON 状态使用一个功耗模型,然后对其余状态使用相同的功耗模型。
功耗使用类型
gem5 模拟器模拟两种类型的功耗使用:
-
static:模拟系统无论活动与否都会消耗的功耗。 -
dynamic:系统由于各种类型的活动而消耗的功耗。
一个功耗模型必须包含用于模拟这两者的方程(尽管该方程可以像 st = "0" 一样简单,例如,如果在该功耗模型中不需要或不关心静态功耗)。
MathExprPowerModels
fs_power.py 中提供的功耗模型扩展了 MathExprPowerModel 类。MathExprPowerModel 被指定为包含如何计算系统所用功耗的数学表达式的字符串。它们通常包含统计信息和**自动变量(例如温度)**的混合,例如:
python
class CpuPowerOn(MathExprPowerModel):
def __init__(self, cpu_path, **kwargs):
super(CpuPowerOn, self).__init__(**kwargs)
# 2A per IPC, 3pA per cache miss
# and then convert to Watt
self.dyn = "voltage * (2 * {}.ipc + 3 * 0.000000001 * " \
"{}.dcache.overall_misses / sim_seconds)".format(cpu_path,
cpu_path)
self.st = "4 * temp"
(上面的功耗模型取自提供的 fs_power.py 文件。)
我们可以看到自动变量(voltage 和 temp)不需要路径,而特定组件的统计信息(CPU 的每周期指令数 ipc)则需要。在文件更靠下的 main 函数中,我们可以看到 CPU 对象有一个 path() 函数,它返回组件在系统中的"路径",例如 system.bigCluster.cpus0。path 函数由 SimObject 提供,因此可以被系统中任何扩展了此类的对象使用,例如 L2 缓存对象在 CPU 对象使用它的几行之后也使用了它。
(注意将 dcache.overall_misses 除以 sim_seconds 以转换为瓦特。这是一个功耗模型,即能量随时间的变化率,而不是能量模型。在使用这些术语时要谨慎,因为它们经常互换使用,但在涉及功率和能量模拟/建模时具有非常具体的含义。)
扩展现有模拟
提供的 fs_power.py 脚本通过导入现有的 fs_bigLITTLE.py 脚本并修改其值来扩展它。作为其中的一部分,使用了几个循环来遍历 SimObject 的后代以应用功耗模型。因此,为了扩展现有模拟以支持功耗模型,定义一个辅助函数来做这件事会很有帮助:
python
def _apply_pm(simobj, power_model, so_class=None):
for desc in simobj.descendants():
if so_class is not None and not isinstance(desc, so_class):
continue
desc.power_state.default_state = "ON"
desc.power_model = power_model(desc.path())
上面的函数接受一个**SimObject、一个功耗模型**,以及一个可选的类,该 SimObject 的后代必须实例化此类才能应用功耗模型。如果未指定类,则功耗模型将应用于所有后代。
无论您决定是否使用辅助函数,现在都需要定义一些功耗模型。这可以通过遵循 fs_power.py 中的模式来完成:
-
为您感兴趣的每个功耗状态定义一个类。这些类应扩展
MathExprPowerModel,并包含一个dyn和一个st字段 。每个字段应包含一个描述在此状态下如何计算相应类型功耗的字符串。它们的构造函数应接受一个路径,该路径将通过format用于描述功耗计算方程的字符串中,以及一些要传递给父类构造函数的kwargs。 -
定义一个类来保存在上一步中定义的所有功耗模型。这个类应该扩展
PowerModel并包含一个名为pm的字段,该字段包含一个包含 4 个元素的列表:pm[0]应该是用于 "ON" 功耗状态的功耗模型实例;pm[1]应该是用于 "CLK_GATED" 功耗状态的功耗模型实例;等等。这个类的构造函数应该接受要传递给各个功耗模型的路径,以及一些要传递给父类构造函数的kwargs。 -
定义了辅助函数和上述类之后,您可以在
build函数中扩展以考虑这些,并可选地在addOptions函数中添加一个命令行标志,如果您希望能够切换模型的使用。
示例实现:
python
class CpuPowerOn(MathExprPowerModel):
def __init__(self, cpu_path, **kwargs):
super(CpuPowerOn, self).__init__(**kwargs)
self.dyn = "voltage * 2 * {}.ipc".format(cpu_path)
self.st = "4 * temp"
class CpuPowerClkGated(MathExprPowerModel):
def __init__(self, cpu_path, **kwargs):
super(CpuPowerClkGated, self).__init__(**kwargs) # 注意:这里修正了父类名
self.dyn = "voltage / sim_seconds"
self.st = "4 * temp"
class CpuPowerOff(MathExprPowerModel):
dyn = "0"
st = "0"
class CpuPowerModel(PowerModel):
def __init__(self, cpu_path, **kwargs):
super(CpuPowerModel, self).__init__(**kwargs)
self.pm = [
CpuPowerOn(cpu_path), # ON
CpuPowerClkGated(cpu_path), # CLK_GATED
CpuPowerOff(), # SRAM_RETENTION
CpuPowerOff(), # OFF
]
[...]
def addOptions(parser):
[...]
parser.add_argument("--power-models", action="store_true",
help="Add power models to the simulated system. "
"Requires using the 'timing' CPU.")
return parser
def build(options):
root = Root(full_system=True)
[...]
if options.power_models:
if options.cpu_type != "timing":
m5.fatal("The power models require the 'timing' CPUs.")
_apply_pm(root.system.bigCluster.cpus, CpuPowerModel,
so_class=m5.objects.BaseCpu)
_apply_pm(root.system.littleCluster.cpus, CpuPowerModel)
return root
[...]
统计信息名称
统计信息名称通常与模拟后在 m5out 目录中生成的 stats.txt 文件中看到的名称相同。但是,也有一些例外:
- CPU 时钟在
stats.txt中被称为clk_domain.clock,但在功耗模型中使用clock_period而不是clock来访问。
统计信息转储频率
默认情况下,gem5 每模拟一秒将模拟统计信息转储到 stats.txt 文件。这可以通过 m5.stats.periodicStatDump 函数来控制,该函数接受以模拟 tick 数(而不是秒数)为单位的期望转储频率。幸运的是,m5.ticks 提供了一个 fromSeconds 函数以便于使用。
以下是一个示例,说明统计信息转储频率如何影响结果分辨率,取自 Sascha Bischoff演示文稿的第 16 张幻灯片:
【注,此处暂缺图片】
(一张图片比较了细节较少的功耗图和细节较多的功耗图;1 秒采样间隔与 1 毫秒采样间隔。)
统计信息转储的频率直接影响基于 stats.txt 文件生成的图的分辨率。然而,它也会影响输出文件的大小。每模拟一秒转储统计信息与每模拟一毫秒转储相比,文件大小会增加数百倍。因此,控制统计信息转储频率是有意义的。
使用提供的 fs_power.py 脚本,可以按如下方式完成:
python
[...]
def addOptions(parser):
[...]
parser.add_argument("--stat-freq", type=float, default=1.0,
help="Frequency (in seconds) to dump stats to the "
"'stats.txt' file. Supports scientific notation, "
"e.g. '1.0E-3' for milliseconds.")
return parser
[...]
def main():
[...]
m5.stats.periodicStatDump(m5.ticks.fromSeconds(options.stat_freq))
bL.run()
[...]
然后可以在调用模拟时使用以下命令指定统计信息转储频率:
python
--stat-freq <val>
常见问题
-
gem5 在使用提供的
fs_power.py时崩溃,并显示消息fatal: statistic '' (160) was not properly initialized by a regStats() function -
gem5 在使用提供的
fs_power.py时崩溃,并显示消息fatal: Failed to evaluate power expressions: [...]
这是由于 gem5 的统计框架最近进行了重构。获取最新版本的 gem5 源代码并重新构建应该可以解决这个问题。如果这不可行,则需要以下两组补丁:
可以按照它们各自链接处的下载说明来检出和应用这些补丁。