AOT漫谈专题(第七篇): 聊一聊给C#打造的节点依赖图

news/2024/10/24 10:50:18

一:背景

1. 讲故事

上一篇我们聊过AOT编程中可能会遇到的三大件问题,而这三大件问题又是考验你对AOT中节点图的理解,它是一切的原点,接下来我就画几张图以个人的角度来解读下吧,不一定对。

二:理解节点依赖图

1. 对节点的理解

按照官方的说法,构建依赖节点和GC的标记算法一样,都是采用深度优先,每一个节点都是一种类型,比如:

  1. MethodCodeNode 表示方法节点
  2. EETypeNode 表示 MethodTable 类型节点

同时节点的层级关系比较深,比如这样的链路, MethodCodeNode -> ObjectNode -> SortableDependencyNode -> DependencyNodeCore<DependencyContextType> -> DependencyNode -> IDependencyNode

对了,最核心的节点依赖图算法来自于方法 DependencyAnalyzer.ComputeMarkedNodes(), 简化后如下:

public override void ComputeMarkedNodes(){do{// Run mark stack algorithm as much as possibleusing (PerfEventSource.StartStopEvents.DependencyAnalysisEvents()){ProcessMarkStack();}// Compute all dependencies which were not ready during the ProcessMarkStack step_deferredStaticDependencies.TryGetValue(_currentDependencyPhase, out var deferredDependenciesInCurrentPhase);if (deferredDependenciesInCurrentPhase != null){ComputeDependencies(deferredDependenciesInCurrentPhase);foreach (DependencyNodeCore<DependencyContextType> node in deferredDependenciesInCurrentPhase){Debug.Assert(node.StaticDependenciesAreComputed);GetStaticDependenciesImpl(node);}deferredDependenciesInCurrentPhase.Clear();}if (_markStack.Count == 0){// Time to move to next deferred dependency phase.// 1. Remove old deferred dependency list(if it exists)if (deferredDependenciesInCurrentPhase != null){_deferredStaticDependencies.Remove(_currentDependencyPhase);}// 2. Increment current dependency phase_currentDependencyPhase++;// 3. Notify that new dependency phase has been enteredComputingDependencyPhaseChange?.Invoke(_currentDependencyPhase);}} while ((_markStack.Count != 0) || (_deferredStaticDependencies.Count != 0));}

在遍历的过程中,它是先用 ProcessMarkStack() 处理所有的静态节点,在处理完后再处理那些在上一阶段产生的新节点或者在上一阶段还没预备好的节点,这里叫 延迟节点,这个说起来有点懵,举个例子: A 是必达节点,C 只有在 B 进入依赖图时才进去,否则不进入,所以这叫条件依赖。最后我再配一张图,大家可以观赏下:

再往下编我就编不下去了,写一个小例子直观的感受下吧。

2. 一个小例子

代码非常简单,大家可以看看这段代码构建的依赖图可能是个什么样子?

internal class Program{static int Main(string[] args){Animal animal = new Bird();animal.Sound();return animal is Dog ? 1 : 0;}}public abstract class Animal{public virtual void Fly() { }public abstract void Sound();}public class Bird : Animal{public override void Sound() { }public override void Fly() { }}public class Dog : Animal{public override void Sound() { }}

就不吊着大家了,最后的依赖图大概是这个样子。

上图稍微解释一下:

  • 矩形: 方法体
  • 椭圆: 类
  • 虚线矩形: 虚方法
  • 点状椭圆形: 未构造的类
  • 虚线边: 条件依赖关系

从图中可以看到,起点是在 Program::Main 函数上,这里要稍微提醒一下,这是逻辑上的托管入口,在 ilc 层面真正的入口是非托管函数 {[Example_21_1]<Module>.StartupCodeMain(int32,native int)} 上,大家可以对 DependencyAnalyzerBase<DependencyContextType>.AddRoot 上下一个断点即可,截图如下:

眼尖得朋友可能会有一个疑问,这个 Bird.Fly() 在依赖图中被移走了是能够说得通得,但有没有什么证据让我眼见为实一下呢?

3. 如何观察节点移走了

aot在调试支持上做了很多的努力,比如通过 IlcGenerateMapFile 就可以让你看到每一个依赖图的节点类型,在 csproj 上配置如下:


<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net8.0</TargetFramework><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable><PublishAot>true</PublishAot><InvariantGlobalization>true</InvariantGlobalization><IlcGenerateMapFile>true</IlcGenerateMapFile></PropertyGroup>
</Project>

接下来打开生成好的 obj\Debug\net8.0\win-x64\native\Example_21_1.map.xml 文件,搜索对应的 Bird__SoundBird__Fly 方法。

对了,上面的 MethodCode 节点我稍微解释一下,完整的如下:

<MethodCode Name="Example_21_1_Example_21_1_Bird__Sound" Length="16" Hash="5e2f1c14edcffc6459b012c27e0e8410215a90cfa5dda68376042264d59e6252" />

刚才也说了 MethodCode 是一个方法节点,Name 不用说了,Length 是方法的汇编代码长度,Hash是对字节码的hash表示,这个在源码上的 XmlObjectDumper.DumpObjectNode 上能够找到答案的。

4. 未构造类型解读

这个指的是上面的 return animal is Dog ? 1 : 0; 这句话,我个人觉得AOT团队这一块没做好,为什么呢?因为 Animal is Dog 底层调用的是 CastHelpers.IsInstanceOfClass 方法,而这个方法底层只需要保存 MethodTable.ParentMethodTable 信息就行了,截图如下:

但遗憾的是AOT居然把 Example_21_1.Dog.Sound() 也追加到依赖图,这就完全没有必要了。

退一万步说生成就生成吧,但恶心的是又不给生成 Dog::Dog 构造函数,这就导致这个 Dog 无法实例化,造成 Dog.Sound 成了一个孤岛函数,无语了,在 csproj 上配置 <IlcGenerateMapFile>true</IlcGenerateMapFile> 节点可以更直观的观察到。

三:总结

节点依赖图的生成是一个比较复杂的过程,目前.NET8 中的 AOT Compiler 还是有很大的优化空间,比如:

  1. 基于上下文的依赖推测。
  2. 未构造类型的推测。
  3. 还不知道的一些未知...

期待后续的 .NET9, .NET10 有更大的提升吧。
图片名称

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/75394.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

ton 通过queryId关联交易

js发起交易代码: async function sendTransaction(item, queryId) {// return false;// console.log(sendTransaction, item, address);const {beginCell,toNano,Address,TonClient,StateInit,storeStateInit,JettonMaster,Cell,} = await import("@ton/ton");const…

MD文档转幻灯片软件:Deckset MacOS电脑版Markdown文档无缝转换为演示文稿

Deckset是一款基于Mac平台的幻灯片制作软件,它允许用户将Markdown文档无缝转换为精美的演示文稿。软件内置多种主题和模板,支持自定义样式,能够满足学术或商务会议等多种需求。Deckset界面简洁直观,操作简便,支持Markdown和LaTeX语法,并提供自动布局、实时预览、演讲者笔…

火山引擎数据飞轮线上研讨会即将开启,助力消费品牌双十一造爆款

随着双十一的临近,各大品牌方的备战工作已进入紧张而有序的倒计时阶段。随着双十一的临近,各大品牌方的备战工作已进入紧张而有序的倒计时阶段。这场持续十多年的电商大促,对消费者来说是购物狂欢节,对各大品牌方来说,则是更是品牌实力与策略的比拼。面对日益激烈的市场竞…

[图像处理] 基于CleanVision库清洗图像数据集

CleanVision是一个开源的Python库,旨在帮助用户自动检测图像数据集中可能影响机器学习项目的常见问题。该库被设计为计算机视觉项目的初步工具,以便在应用机器学习之前发现并解决数据集中的问题。CleanVision的核心功能包括检测完全重复、近似重复、模糊、低信息量、过暗、过…

资料:工业定制化仪器设计方案

Camera Link 输出子卡 , FPGA逻辑视频采集 , FPGA实时计算平台 , 实物仿真平台 , 工业定制化仪器

为啥我的mysql根目录没有my.ini

为啥我的mysql根目录没有my.ini? 正确的是:

媲美PS的修图软件推荐:Pixelmator Pro 保持图片原始质量修图 macOS电脑激活软件

Pixelmator Pro是一款专为Mac用户设计的图像编辑软件。它拥有直观易用的界面和丰富的工具集,支持非破坏性编辑,确保原始文件质量不受影响。该软件集成了先进的机器学习技术,提供智能调色、抠图等功能,并支持多种文件格式,包括JPEG、PNG及Photoshop的PSD文件。无论是摄影后…

linux提交之6e90b6-开源之耻!

本周合并到 Linux 6.12-rc4 内核中的一个补丁删除了一些内核维护者,使其不再出现在官方 MAINTAINERS 文件中,该文件可识别所有驱动程序和子系统维护者。 其中包括宏碁 Aspire 1 EC 驱动程序、Cirrus Logic CLPS711X ARM 架构、Baikal-T1 PVT 硬件监控器驱动程序、Libata PATA…