AOT漫谈专题(第七篇): 聊一聊给C#打造的节点依赖图
一:背景
1. 讲故事
上一篇我们聊过AOT编程中可能会遇到的三大件问题,而这三大件问题又是考验你对AOT中节点图的理解,它是一切的原点,接下来我就画几张图以个人的角度来解读下吧,不一定对。
二:理解节点依赖图
1. 对节点的理解
按照官方的说法,构建依赖节点和GC的标记算法一样,都是采用深度优先,每一个节点都是一种类型,比如:
- MethodCodeNode 表示方法节点
- EETypeNode 表示 MethodTable 类型节点
同时节点的层级关系比较深,比如这样的链路, MethodCodeNode -> ObjectNode -> SortableDependencyNode -> DependencyNodeCore<DependencyContextType> -> DependencyNode -> IDependencyNode。
对了,最核心的节点依赖图算法来自于方法 DependencyAnalyzer.ComputeMarkedNodes(), 简化后如下:
public override void ComputeMarkedNodes()
{
do
{
// Run mark stack algorithm as much as possible
using (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 entered
ComputingDependencyPhaseChange?.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__Sound 和 Bird__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 还是有很大的优化空间,比如:
- 基于上下文的依赖推测。
- 未构造类型的推测。
- 还不知道的一些未知...
期待后续的 .NET9, .NET10 有更大的提升吧。

AOT漫谈专题(第七篇): 聊一聊给C#打造的节点依赖图的更多相关文章
- PerfView专题 (第七篇):如何洞察触发 GC 的 C# 代码?
一:背景 上一篇我们聊到了如何用 PerfView 洞察 GC 的变化,但总感觉还缺了点什么? 对,就是要跟踪到底是什么代码触发了 GC,这对我们分析由于 GC 导致的 CPU 爆高有非常大的参考价值 ...
- 无线安全专题_攻击篇--MAC泛洪攻击
上一篇讲解了无线安全专题_攻击篇--干扰通信,没在首页待多长时间就被拿下了,看来之后不能只是讲解攻击实战,还要进行技术原理和防御方法的讲解.本篇讲解的是局域网内的MAC泛洪攻击,这种攻击方式主要目的是 ...
- 跟我学SpringCloud | 第七篇:Spring Cloud Config 配置中心高可用和refresh
SpringCloud系列教程 | 第七篇:Spring Cloud Config 配置中心高可用和refresh Springboot: 2.1.6.RELEASE SpringCloud: Gre ...
- 解剖SQLSERVER 第七篇 OrcaMDF 特性概述(译)
解剖SQLSERVER 第七篇 OrcaMDF 特性概述(译) http://improve.dk/orcamdf-feature-recap/ 时间过得真快,这已经过了大概四个月了自从我最初介绍我 ...
- 第七篇 :微信公众平台开发实战Java版之如何获取微信用户基本信息
在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同). 公众号可通过本接口来根据O ...
- 第七篇 Replication:合并复制-订阅
本篇文章是SQL Server Replication系列的第七篇,详细内容请参考原文. 订阅服务器就是复制发布项目的所有变更将传送到的服务器.每一个发布需要至少一个订阅,但是一个发布可以有多个订阅. ...
- 第七篇 Integration Services:中级工作流管理
本篇文章是Integration Services系列的第七篇,详细内容请参考原文. 简介在上一篇文章,我们创建了一个新的SSIS包,学习了SSIS中的脚本任务和优先约束,并检查包的MaxConcur ...
- 第七篇 SQL Server安全跨数据库所有权链接
本篇文章是SQL Server安全系列的第七篇,详细内容请参考原文. Relational databases are used in an amazing variety of applicatio ...
- 第七篇 SQL Server代理作业活动监视器
本篇文章是SQL Server代理系列的第七篇,详细内容请参考原文 在这一系列的上一篇,你创建并配置SQL Server代理作业.每个作业有一个或多个步骤,可能包含大量的工作流.在这篇文章中,将查看作 ...
- 用仿ActionScript的语法来编写html5——第七篇,自定义按钮
第七篇,自定义按钮这次弄个简单点的,自定义按钮.其实,有了前面所定义的LSprite,LBitmap等类,定义按钮就很方便了.下面是添加按钮的代码, function gameInit(event){ ...
随机推荐
- 使用Linux桌面壁纸应用variety发现的一些问题
本人Ubuntu18.04 Desktop系统安装桌面壁纸应用variety,设置如下: 使用大致两个小时,主机为NVIDIA显卡,查看显存使用情况: 可以发现随着使用时间的增加variety会逐渐增 ...
- Spring Boot 基于 SCRAM 认证集成 Kafka 的详解
一.说明 在现代微服务架构中,Kafka 作为消息中间件被广泛使用,而安全性则是其中的一个关键因素.在本篇文章中,我们将探讨如何在 Spring Boot 应用中集成 Kafka 并使用 SCRAM ...
- centos7系统 通过编译安装gcc7.5.0
背景: 现有的centos7 gcc的最高版本为4.8.5 项目需要升级到7.1.0以上 正常方式可以通过以下命令即可完成升级: $ sudo yum install centos-release-s ...
- 9组-Alpha冲刺-5/6
一.基本情况 队名:不行就摆了吧 组长博客: https://www.cnblogs.com/Microsoft-hc/p/15546711.html 小组人数: 8 二.冲刺概况汇报 谢小龙 过去两 ...
- 2023 SMU RoboCom-CAIP 选拔赛
前言 更详细题解可以参考咱学长的( 2023 SMU RoboCom-CAIP 选拔赛.zip A. 小斧头 f_k 表示满足条件的j = k 的(i,j)对的数量.如上图中第四行即为f1至f5的元素 ...
- [APIO2019] 路灯 题解
LG5445 把询问 \(x,y\) 看作平面上的点 记当前时刻 \(t\),\(l\) 是与 \(i\) 连通的最左端,\(r\) 是与 \(i+1\) 连通的最右端,可以通过 set 维护断边找到 ...
- C# 菜单项添加复选标记
在网上查找都是说直接用菜单项的Checked属性, toolMenuItem.Checked=!toolMenuItem.Checked; 但是我用了也切换不过来. 有点晕菜了,我用的是vs2017. ...
- zabbix4.0配置短信报警
1.准备工作 #访问短信网址:172.16.98.1,网线插LAN口 #账号&密码:admin 安装ubuntu系统模拟http请求工具(命令行模式) # apt-get install ht ...
- 【YashanDB数据库】YAS-02143 invalid username/password, login denied
[问题分类]错误码处理 [关键字]jdbc,02143 [问题描述]应用启动后,报错YAS-02143 invalid username/password, login denied [问题原因分析] ...
- WebShell流量特征检测_哥斯拉篇
80后用菜刀,90后用蚁剑,95后用冰蝎和哥斯拉,以phpshell连接为例,本文主要是对这四款经典的webshell管理工具进行流量分析和检测. 什么是一句话木马? 1.定义 顾名思义就是执行恶意指 ...