Chisel编译时间优化实例——将香山的双核RTL生成时间从36分钟压缩到7分钟

Table of Contents

背景

自从香山新后端合并后,Chisel编译时间大幅增加,特别是在双核的DefaultConfig下,在我的13900K机器+OpenJDK 11的条件下,FIR的生成时间已经达到的34分钟,具体可以见该issue,而在之前,FIR生成时间仅为2分钟不到。这样的编译时间大幅增加导致开发流程不再敏捷,大幅时间浪费在等待编译上。

观察

我首先怀疑是电路规模也存在大幅增加,因此我首先观测了FIR的大小。通过git checkoutgit reset --hard使用新后端合并前的commit进行观察可发现,在使用DefaultConfig双核的情况下,旧后端的FIR大小为1.4GB,新后端为2.4GB。尽管FIR大小不能完全反映电路大小,但时间并不应该是小于2分钟至34分钟的区别。

因此,我怀疑问题出在以下两点:

  • Scala的写法不同导致了CPU微架构层面出现了巨大的IPC差异
  • 存在大量重复的代码执行,而RTL的生成过程中存在非常多可以复用的部分

Profiling

鉴于Scala最终也会在JVM里执行,我首先调研了一些Java的Profiling工具,最后发现我们常用的IDEA idea自带该功能。使用方法非常简单:

在打开的Profile窗口后,去终端执行编译(例如 make verilog NUM_CORES=2 MFC=1),此时建议设置IDEA布局将终端放在Profiler右边,时刻观测状态。

此时可在Profiler窗口中观察到Mill执行编译的Java进程。

等待Scala编译完成后,可在Profiler窗口中观测到进程名变为TopMain,此时我们点击右键来Attach。

然后等待编译执行完成,观察结果:

(注,第一次使用时可能遇到Java的-Xmx不足导致进程崩溃的问题,但Profiler阶段不影响,如果遇到idea进程退出依然可以在~/IdeaSnapshots/中观测到Profiling结果)

在此我们可以观察大量的信息:

对比采样结果

旧后端:

新后端:

观测图右侧的Stack Trace进度条可以看出,在emit FIR时,存在非常大的调用栈深度差异。

然后我观察了Stack Trace,发现,在新后端的代码中随机选几个点,总是存在XSBundle的初始化,且后续的栈中存在大量的Scala容器操作。

然后去Scala代码里找到这个class,发现它with HasXSParameters,再去看trait HasXSParameters,发现它存在大量的val参数,然而在每个XSBundle中,我们通常仅会使用极少数的参数。

解决

因此解决方法显而易见,那就是将val换成lazy val/def。但经过实测发现,lazy val比def慢,可能和Scala的lazy val实现依然需要初始化指针有关。

最后在XiangShan#2952 PR 中解决。尽管依然比旧后端要慢,但7分钟能完成双核Verilog生成,已经基本满足敏捷调试的需求了。

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to Top