高等数学(导数、梯度)概念梳理

2025-7-15 评论(0) 分类:人工智能 Tags:

前言

从毕业到现在已经很久没有做过高等数学相关的内容,但是机器学习和神经网络里面有大量的关于导数的推倒公式,所以系统性的对于导数和相关的知识点做一个梳理,以及它的应用做一个说明。


导数的基本概念

$$\frac{\partial f}{\partial l}(x_0, y_0) = \lim_{\Delta l \to 0} \frac{f(x_0 + \Delta x, y_0 + \Delta y) – f(x_0, y_0)}{\Delta l}$$

什么是导数?

导数本质上是当前函数在某一点的线性逼近的变化率,也就是该点处切线的斜率。

导数的作用

在机器学习领域,导数的主要作用是通过计算函数的变化率来找到极值点,这在优化算法中极为重要。

驻点

驻点是导数为0的点,在函数图像上可能是极大值或极小值点。要进一步判断驻点的性质,需要观察导数在该点左右两侧的符号变化。

基本导数公式表

 

说明 公式 例子
常数的导数

\((C)’ = 0\)

\((3)’ = 0\)

幂函数的导数

\((x^α)’ = αx^{α-1}\)

\((x^3)’ = 3x^2\)

指数函数的导数

\((a^x)’ = a^x \ln a\)

\((3^x)’ = 3^x \ln 3\)

\((e^x)’ = e^x\)

对数函数的导数

\((\log_a x)’ = \frac{1}{x \ln a}\)

\((\log_3 x)’ = \frac{1}{x \ln 3}\)

\((\ln x)’ = \frac{1}{x}\)

三角函数的导数

\((\sin x)’ = \cos x\)

\((\cot x)’ = -\csc^2 x = -\frac{1}{\sin^2 x}\)

导数运算法则

 

说明 公式
两函数之和求导

\((f + g)’ = f’ + g’\)

两函数之积求导

\((fg)’ = f’g + fg’\)

两函数之商求导

\(\left(\frac{f}{g}\right)’ = \frac{f’g – fg’}{g^2}\)

复合函数的导数

若 \(f(x) = h(g(x))\),则 \(f'(x) = h'(g(x)) \cdot g'(x)\)

 

二阶导数

概述

二阶导数是对一阶导数继续求导得到的结果。

含义

  • 一阶导数:表示某点处切线斜率的大小,即该点的变化率
  • 二阶导数:表示变化率的变化率

在物理学中,二阶导数的含义就是加速度

二阶导数的几何意义

二阶导数可以判断函数的凹凸性:

  • 二阶导数恒为正数:函数向上弯曲(凹函数/下凸函数)
  • 二阶导数恒为负数:函数向下弯曲(凸函数/上凸函数)

拐点

拐点是二阶导数左右异号的点,在这个点处:

  • 函数的变化率从递增变为递减(或相反)
  • 二阶导数必定为0
  • 但二阶导数为0的点不一定是拐点,需要观察该点附近二阶导数的变化趋势

二阶导数的判别法

利用一阶导数和二阶导数可以判断函数的极值:

  • 一阶导数为0,二阶导数大于0:极小值点
  • 一阶导数为0,二阶导数小于0:极大值点
  • 一阶导数为0,二阶导数等于0:可能是拐点

偏导数

概述

当函数有多个自变量时,如 f(x,y) = xy + x² + y²,我们可以针对其中一个变量求导,这就是偏导数

计算方法

求偏导数时,将其他变量视为常量:

  • 对y求偏导:∂f/∂y = x + 2y(将x视为常量)
  • 对x求偏导:∂f/∂x = y + 2x(将y视为常量)

偏导数的数学定义

$$\frac{\partial f}{\partial x_i}(a_1, a_2, …, a_n) = \lim_{\Delta x_i \to 0} \frac{f(a_1, …, a_i + \Delta x_i, …, a_n) – f(a_1, …, a_i, …, a_n)}{\Delta x_i}$$

偏导数与方向导数的关系

偏导数实际上是特殊的方向导数:

  • 当方向角α = 0°时,得到x方向的偏导数
  • 当方向角β = 0°时,得到y方向的偏导数

方向导数

定义

方向导数不是针对某一个坐标轴方向求导,而是针对任意方向L进行求导,表示二元函数在某点沿着特定方向的变化率。

方向导数的几何意义

  • α是方向l与x轴的夹角
  • β是方向l与y轴的夹角
  • 微小改变量的关系:Δx = Δl·cos α, Δy = Δl·cos β

方向导数的数学定义

$$\frac{\partial f}{\partial l}(x_0, y_0) = \lim_{\Delta l \to 0} \frac{f(x_0 + \Delta x, y_0 + \Delta y) – f(x_0, y_0)}{\Delta l}$$

方向导数的计算公式

$$\frac{\partial f}{\partial l}(x_0, y_0) = f_x(x_0, y_0)\cos α + f_y(x_0, y_0)\cos β$$

全微分公式

方向导数可以通过全微分公式来表示:

$$\frac{\partial f}{\partial l}(x_0, y_0) = f_x(x_0, y_0)\cos α + f_y(x_0, y_0)\cos β = |\nabla f| \cdot |l| \cdot \cos θ$$

其中:

  • fx(x₀, y₀)、fy(x₀, y₀) 表示点(x₀, y₀)处f对x、y的偏导数
  • cos α、cos β 是方向l的方向余弦
  • l方向的单位方向向量可以表示为 l₀ = (cos α, cos β)
  • θ是向量∇f和l的夹角

解释θ含义:

θ 的定义:

  • θ 是梯度向量 ∇f 与方向向量 l 之间的夹角
  • 根据向量点乘的几何意义:∇f · l = |∇f| |l| cos θ
  • 由于 l 是单位向量(|l| = 1),所以:∇f · l = |∇f| cos θ

θ 的含义:

  • 当 θ = 0° 时,cos θ = 1,方向向量 l 与梯度向量 ∇f 同向
  • 此时方向导数 = |∇f| cos θ = |∇f|,达到最大值
  • 当 θ = 90° 时,cos θ = 0,方向向量与梯度垂直,方向导数为0
  • 当 θ = 180° 时,cos θ = -1,方向向量与梯度反向,方向导数为 -|∇f|,达到最小值

几何直观: θ 描述了我们选择的方向 l 与函数在该点增长最快的方向(梯度方向)之间的偏离程度。偏离得越小(θ 越小),沿该方向的变化率就越大。

这就是为什么说梯度指向函数增长最快的方向——因为只有当方向向量与梯度同向时(θ = 0),方向导数才能达到最大值 |∇f|。

梯度向量 ∇f

物理含义:

  • 梯度向量是函数在某点处增长最快的方向
  • 它的模长表示在该方向上的最大变化率

数学表示:

  • 对于二元函数 f(x,y):∇f = (∂f/∂x, ∂f/∂y)
  • 梯度向量是由各个偏导数组成的向量

几何直观:

  • 想象一个山坡,梯度向量就是该点处最陡峭上坡的方向
  • 梯度向量总是垂直于等高线(等值线)

方向向量 l

物理含义:

  • 方向向量是我们人为选择的一个方向
  • 它表示我们想要沿着哪个方向去观察函数的变化

数学表示:

  • l = (cos α, cos β),其中 α, β 是方向角
  • 必须是单位向量,即 |l| = 1

几何直观:

  • 就像指南针上的一个方向
  • 我们可以任意选择这个方向去”探索”函数的变化

形象比喻:

  • 梯度向量 = 水流的方向(客观存在)
  • 方向向量 = 我们划船选择的方向(主观选择)
  • 方向导数 = 我们选择的方向与水流方向的”配合程度”

当我们选择的方向与梯度方向一致时,就能获得最大的变化率!

推导过程

设梯度向量:∇f = (fx(x), fy(x)),方向向量:l = (cos α, cos β)

通过向量点乘(对应位置相乘再求和): $$\nabla f \cdot l = \frac{\partial f}{\partial l}(x_0, y_0) = f_x(x_0, y_0)\cos α + f_y(x_0, y_0)\cos β$$

任意选取一个方向l,那么在某个点处的变化率就是l方向的方向导数。由于l是单位方向向量,其模长为1,因此:

当cos θ = 1时(即方向一致时),方向导数取得最大值,这个最大值就是梯度的模长

 


梯度

定义

对于多元函数 f(x₁, …, xₙ),在点 a = (a₁, a₂, …, aₙ) 处的梯度定义为:

$$\nabla f(a) = \left[\frac{\partial f}{\partial x_1}(a), \frac{\partial f}{\partial x_2}(a), …, \frac{\partial f}{\partial x_n}(a)\right]$$

这个向量称为f在点a处的梯度,记作∇f(a)或grad f(a)。

形象理解:梯度是一个向量场,它描述了多元函数在空间中每一点的变化率和变化方向

梯度的计算示例

对于二元函数 f(x,y) = x² + xy + y²:

  • ∂f/∂x = 2x + y,在(1,1)处值为3
  • ∂f/∂y = x + 2y,在(1,1)处值为3
  • 所以梯度为 ∇f(1,1) = [3, 3]

梯度的重要性质

  1. 梯度向量指向函数增长最快的方向
  2. 梯度的模长表示函数在该方向上的最大变化率
  3. 梯度与方向导数的关系:∂f/∂l = ∇f · e

因此,梯度是多元函数各个偏导数的向量组合。对于二维函数f(x,y),梯度就是x和y在某点处偏导数组成的向量。

 


工具使用:Python可视化

我们可以利用numpy和matplotlib结合jupyter notebook来画出函数及其导数的图像。

绘制sin(x)函数

import numpy as np
import matplotlib.pyplot as plt

# 定义函数 y = sin(x)
x = np.arange(0, 6, 0.1)
y = np.sin(x)

# 画出函数图像
plt.plot(x, y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('y = sin(x)')
plt.show()

绘制导函数cos(x)

# 导函数 y' = cos(x)
y1 = np.cos(x)

# 画出函数图像
plt.plot(x, y1)
plt.xlabel('x')
plt.ylabel("y'")
plt.title("y' = cos(x)")
plt.show()

函数与导函数的对比

# 将函数和导函数放到一张图中
plt.plot(x, y, label='y = sin(x)')
plt.plot(x, y1, label="y' = cos(x)", linestyle='--')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.title('sin(x) and its derivative')
plt.show()


总结

导数是微积分的核心概念,从一阶导数到二阶导数,从单变量到多变量,从偏导数到方向导数再到梯度,这些概念构成了现代数学分析和机器学习的理论基础。通过Python等工具的可视化,我们可以更直观地理解这些抽象的数学概念。

导数在现代AI中的应用

机器学习中的核心作用

在机器学习中,导数的概念无处不在:

损失函数优化:机器学习的核心是最小化损失函数,而梯度下降算法正是利用梯度(多元函数的导数)来寻找函数的最小值点。算法通过计算损失函数相对于模型参数的梯度,然后沿着梯度的反方向更新参数。

反向传播算法:这是深度学习的基石,本质上是链式法则的应用。通过计算损失函数对每个权重的偏导数,网络可以有效地更新所有参数。

深度学习中的梯度计算

自动微分:现代深度学习框架(如PyTorch、TensorFlow)都内置了自动微分系统,能够自动计算复杂计算图中任意函数的梯度。这使得构建和训练复杂的神经网络成为可能。

梯度消失和梯度爆炸:在深度神经网络中,梯度在反向传播过程中可能会逐渐消失或爆炸,这直接影响了网络的训练效果。理解导数的链式法则有助于设计更好的网络架构和激活函数。

大模型时代的导数应用

Transformer架构:现代大语言模型的核心Transformer架构中,注意力机制的计算涉及大量的矩阵微分和梯度计算。

优化器进化:从简单的SGD到Adam、AdamW等现代优化器,都是基于梯度的一阶和二阶信息来设计的。这些优化器的核心都是如何更好地利用梯度信息来更新参数。

大规模并行训练:在训练GPT、BERT等大模型时,梯度的计算和同步成为了关键的技术挑战。理解梯度的性质有助于设计更高效的分布式训练策略。

实际应用示例

计算机视觉:在CNN中,卷积层的权重更新依赖于误差相对于卷积核的偏导数计算。

自然语言处理:在RNN/LSTM中,序列数据的处理需要计算损失函数相对于各个时间步参数的梯度。

推荐系统:协同过滤和深度推荐模型都需要通过梯度下降来优化用户和物品的嵌入向量。

未来展望

随着AI技术的发展,导数的概念在以下方面将发挥更重要的作用:

  • 神经架构搜索(NAS):通过可微分的架构搜索来自动设计神经网络
  • 元学习:利用二阶导数信息来快速适应新任务
  • 物理信息神经网络:将物理定律的微分方程嵌入到神经网络中

导数不仅是数学分析的基础工具,更是现代人工智能革命的数学基石。掌握导数的概念和计算方法,对于深入理解和应用机器学习、深度学习技术具有重要意义,后续我会持续的更新人工智能学习过程中的高等数学相关的知识补充进来。

AI相关技术感想

2025-7-14 评论(0) 分类:生活 Tags:

前天Grok4又发布了,号称是媲美博士生的水平,实际水平还得拿到实测以后才能评论,大模型技术每天层出不穷,很多同学也想学习这行相关的技术,后续我会整理大模型从入门到高阶的成长学习笔记,帮助后面的同学可以尽快进入。

Linux和Vim常见指令集合

2025-7-10 评论(0) 分类:人工智能 Tags:

Linux常用命令完整指南

📖 背景

每次使用vim和Linux一些不常见的指令都需要到处寻找,特地整理了下常见的指令,希望能够帮到自己也能帮到大家。

无论是刚接触Linux的新手,还是偶尔需要查找命令的开发者,这份指南都能为您提供快速参考。所有命令都经过实际验证,并包含了实用的使用场景和注意事项。

📁 文件增删改查

创建文件和文件夹

创建文件

touch test.txt

创建文件夹

# 创建单层目录
mkdir demo

# 创建多层目录
mkdir -p demo/a/b/c

复制文件

cp test.txt test2.txt

删除文件和文件夹

删除文件

rm -f test3.txt

删除文件夹

rm -rf 文件夹名称

⚠️ 注意: rm -rf 命令非常危险,使用时请谨慎确认路径!

修改文件

使用编辑器

  • vi/vim编辑器:功能强大的文本编辑器
  • 三种模式
    • 命令模式(默认模式)
    • 输入模式(编辑文本)
    • 底线模式(保存退出)

移动和重命名

# 移动文件到指定路径
mv 原始路径 目标路径

# 重命名文件
mv 旧文件名 新文件名

查看文件内容

查看小文件

cat filename

分页查看大文件

more filename

实时查看文件尾部

# 查看文件尾部(默认10行)
tail filename

# 实时监控文件变化
tail -f xxx.log

# 查看指定行数
tail -f -n 20 xxx.log
# 或者
tail -fn 20 xxx.log

查看文件头部

# 查看文件头部(默认10行)
head xxx.log

# 查看指定行数
head -20 xxx.log

🔐 文件权限管理

权限类型

  • 用户权限(U):文件所有者的权限
  • 用户组权限(G):文件所属组的权限
  • 其他权限(O):其他用户的权限

权限修改命令

字符表示法

# 设置用户读写权限
chmod u=rw demo.txt

# 添加权限
chmod u+r demo.txt  # 添加读权限
chmod u+w demo.txt  # 添加写权限
chmod u+x demo.txt  # 添加执行权限

# 移除权限
chmod u-r demo.txt  # 移除读权限
chmod u-w demo.txt  # 移除写权限
chmod u-x demo.txt  # 移除执行权限

数字表示法

权限对应数字:

  • r(读):4
  • w(写):2
  • x(执行):1
# 示例:755 = rwxr-xr-x
chmod 755 demo.txt

👥 用户权限管理

用户管理

添加用户

# 创建用户(会在/home下创建用户目录)
useradd u1

# 为用户设置密码
passwd u1

删除用户

# 删除用户(保留home目录)
userdel u1

# 删除用户和home目录
userdel -r u1

切换用户

# 切换到指定用户
su - 账号

# 退出当前用户
# 使用 Ctrl + D

用户组管理

添加用户组

# 创建用户组
groupadd g1

# 将已存在用户添加到用户组
usermod -aG g1 u1

# 创建用户时直接指定用户组
useradd u1 -g g1

删除用户组

groupdel g1

更改文件所有者和用户组

# 递归更改文件所有者和用户组
chown u1:g1 -R 文件路径

查看用户信息

# 查看用户组关系
getent group

# 查看用户信息
getent passwd

📦 文件打包和压缩

tar打包

打包文件

tar -cvf demo.tar demo

解包文件

# 解包到当前目录
tar -xvf demo.tar

# 解包到指定目录
tar -xvf demo.tar -C test/

gz压缩

压缩文件

tar -zcvf demo.tar.gz /tmp/test

解压文件

# 解压到当前目录
tar -zxvf demo.tar.gz

# 解压到指定目录
tar -zxvf demo.tar.gz -C /tmp/test

🔗 文件链接

软链接(符号链接)

  • 类似于快捷方式,安全性较高
  • 原文件删除后,软链接失效
ln -s test.txt test2.txt

硬链接

  • 直接指向文件内容,与原文件共享数据
  • 原文件删除后,硬链接仍然有效
ln test.txt test3.txt

💻 进程和网络管理

查看进程

ps -ef

网络请求

# 发送HTTP请求
curl http://example.com

# 下载文件
wget http://example.com/file.txt

📥 软件安装

YUM包管理器

  • RPM的升级版本
  • 自动处理依赖关系
# 安装软件包
yum install package_name

# 示例:安装上传下载工具
yum install lrzsz

RPM包管理器

  • 红帽系列包管理工具
  • 需要手动处理依赖关系
# 安装RPM包
rpm -ivh package.rpm

🛠 其他常用命令

网络测试

ping google.com

查找命令位置

which java

文件查找

# 按文件名查找
find / -name 'test.txt'

# 按文件大小查找
find . -size +1k

清空屏幕

clear

内容过滤

# 管道过滤
cat 文件 | grep '关键词'

# 直接过滤
grep -n '关键词' 文件

系统服务管理

# CentOS 7及以后版本使用systemctl
systemctl start/stop/restart/status 服务名

# CentOS 7之前版本使用service
service 服务名 start/stop/restart/status

✏️ VIM编辑器详解

模式切换

进入输入模式

  • i:在当前光标位置进入输入模式
  • a:在当前光标之后进入输入模式
  • I:在当前行开头进入输入模式
  • A:在当前行结尾进入输入模式
  • o:在当前光标下一行进入输入模式
  • O:在当前光标上一行进入输入模式

返回命令模式

  • ESC:从任何模式返回命令模式

常用操作命令

光标移动

  • 0:移动到当前行开头
  • $:移动到当前行结尾
  • gg:跳到文件首行
  • G:跳到文件末行

删除操作

  • dd:删除当前行
  • ndd:删除当前行向下n行
  • dG:从当前行删除到文件末尾
  • dgg:从当前行删除到文件开头
  • d$:从光标删除到行尾
  • d0:从光标删除到行首

复制粘贴

  • yy:复制当前行
  • nyy:复制当前行和下面n行
  • p:粘贴复制的内容

撤销操作

  • u:撤销上一步修改
  • Ctrl + r:反向撤销(重做)

保存和退出

  • :wq:保存并退出
  • :q!:强制退出不保存
  • :w:保存文件
  • :q:退出编辑器

💡 使用技巧

  1. 使用Tab键自动补全命令和文件名
  2. 使用history命令查看命令历史
  3. 使用man命令查看详细帮助文档
  4. 备份重要文件before进行危险操作
  5. 定期清理临时文件释放磁盘空间

希望这个指南能帮助您更好地使用Linux系统!

TCP三次握手和四次挥手详解

2025-7-7 评论(0) 分类:人工智能 Tags:

前言

今天使用WireShark抓包工具来深入分析TCP网络通信的详细过程。通过实际的数据包捕获,我们将完整展示TCP三次握手和四次挥手的全流程。

WireShark过滤器配置

使用以下过滤关键词来捕获TCP连接相关的数据包:

(tcp.flags.syn == 1 || tcp.flags.fin == 1 || tcp.stream eq 0 || tcp.flags.syn == 1 or tcp.flags.fin == 1 ||tcp.flags.reset == 1 ||tcp.flags.ack == 1 || tcp.len > 0 ) and tcp.port ==8090

过滤器解析

  • tcp.flags.syn == 1: 捕获SYN包
  • tcp.flags.fin == 1: 捕获FIN包
  • tcp.flags.reset == 1: 捕获RST包
  • tcp.flags.ack == 1: 捕获ACK包
  • tcp.len > 0: 捕获包含数据的包
  • tcp.port == 8090: 限定端口8090

完整数据包日志

以下是捕获到的完整TCP通信过程:

104  16.836236  127.0.0.1  →  127.0.0.1  TCP  68  65297 → 8090 [SYN] Seq=0 Win=65535 Len=0
105  16.836315  127.0.0.1  →  127.0.0.1  TCP  68  8090 → 65297 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0
106  16.836324  127.0.0.1  →  127.0.0.1  TCP  56  65297 → 8090 [ACK] Seq=1 Ack=1 Win=408256 Len=0
107  16.836330  127.0.0.1  →  127.0.0.1  TCP  56  8090 → 65297 [ACK] Seq=1 Ack=1 Win=408256 Len=0
108  16.836772  127.0.0.1  →  127.0.0.1  TCP  61  65297 → 8090 [PSH, ACK] Seq=1 Ack=1 Win=408256 Len=5
109  16.836892  127.0.0.1  →  127.0.0.1  TCP  56  8090 → 65297 [ACK] Seq=1 Ack=6 Win=408256 Len=0
110  16.836992  127.0.0.1  →  127.0.0.1  TCP  62  8090 → 65297 [PSH, ACK] Seq=1 Ack=6 Win=408256 Len=6
111  16.837015  127.0.0.1  →  127.0.0.1  TCP  56  65297 → 8090 [ACK] Seq=6 Ack=7 Win=408256 Len=0
112  16.837073  127.0.0.1  →  127.0.0.1  TCP  56  65297 → 8090 [FIN, ACK] Seq=6 Ack=7 Win=408256 Len=0
113  16.837093  127.0.0.1  →  127.0.0.1  TCP  56  8090 → 65297 [ACK] Seq=7 Ack=7 Win=408256 Len=0
114  16.837147  127.0.0.1  →  127.0.0.1  TCP  62  8090 → 65297 [PSH, ACK] Seq=7 Ack=7 Win=408256 Len=6
115  16.837181  127.0.0.1  →  127.0.0.1  TCP  44  65297 → 8090 [RST] Seq=7 Win=0 Len=0

 


1. TCP三次握手详细分析

1.1 第一次握手 (SYN)

104  16.836236  127.0.0.1  →  127.0.0.1  TCP  68  65297 → 8090 [SYN] Seq=0 Win=65535 Len=0

数据包104分析:

  • 方向:127.0.0.1:65297 → 127.0.0.1:8090
  • 标志:[SYN]
  • 作用:客户端发送SYN包,请求建立连接
  • 序列号:Seq=0,表示初始序列号
  • 窗口大小:Win=65535,表示接收窗口大小
  • MSS:16344,表示最大段大小

1.2 第二次握手 (SYN+ACK)

105  16.836315  127.0.0.1  →  127.0.0.1  TCP  68  8090 → 65297 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0

数据包105分析:

  • 方向:127.0.0.1:8090 → 127.0.0.1:65297
  • 标志:[SYN, ACK]
  • 作用:服务器响应SYN+ACK包
  • 序列号:Seq=0 Ack=1,确认收到客户端的SYN包
  • 窗口大小:Win=65535,服务器的接收窗口大小

1.3 第三次握手 (ACK)

106  16.836324  127.0.0.1  →  127.0.0.1  TCP  56  65297 → 8090 [ACK] Seq=1 Ack=1 Win=408256 Len=0

数据包106分析:

  • 方向:127.0.0.1:65297 → 127.0.0.1:8090
  • 标志:[ACK]
  • 作用:客户端发送ACK包,确认连接建立
  • 序列号:Seq=1 Ack=1,确认收到服务器的SYN+ACK包
  • 状态:此时TCP连接正式建立

2. TCP连接维护阶段

2.1 窗口更新 (数据包107)

107  16.836330  127.0.0.1  →  127.0.0.1  TCP  56  8090 → 65297 [ACK] Seq=1 Ack=1 Win=408256 Len=0

数据包107分析:

  • 方向:服务器 → 客户端
  • 标志:[ACK]
  • 作用:TCP窗口更新
  • 窗口变化:从初始的65535字节扩大到408256字节
  • 意义:允许客户端发送更多数据,提高传输效率

2.2 客户端数据传输 (数据包108)

108  16.836772  127.0.0.1  →  127.0.0.1  TCP  61  65297 → 8090 [PSH, ACK] Seq=1 Ack=1 Win=408256 Len=5

数据包108分析:

  • 方向:客户端 → 服务器
  • 标志:[PSH, ACK]
  • 数据长度:Len=5
  • PSH标志:表示”立即推送这些数据给应用程序”
  • 数据内容:通过十六进制分析,发送的是”study”

数据解析过程:

十六进制: 73 74 75 64 79
ASCII转换: 
73 = 's'
74 = 't' 
75 = 'u'
64 = 'd'
79 = 'y'
结果: "study"

2.3 服务器数据确认 (数据包109)

109  16.836892  127.0.0.1  →  127.0.0.1  TCP  56  8090 → 65297 [ACK] Seq=1 Ack=6 Win=408256 Len=0

数据包109分析:

  • 方向:服务器 → 客户端
  • 标志:[ACK]
  • 作用:确认数据接收
  • 序列号变化:Ack=6表示期望下一个数据包的序列号从6开始
  • 窗口维持:Win=408256字节

2.4 服务器响应数据 (数据包110)

110  16.836992  127.0.0.1  →  127.0.0.1  TCP  62  8090 → 65297 [PSH, ACK] Seq=1 Ack=6 Win=408256 Len=6

数据包110分析:

  • 方向:服务器 → 客户端
  • 标志:[PSH, ACK]
  • 数据长度:Len=6
  • 作用:服务器发送响应数据

数据解析过程:

原始十六进制数据:

02 00 00 00 45 00 00 3a 00 00 40 00 40 06 00 00
7f 00 00 01 7f 00 00 01 1f 9a ff 11 bd 77 4d c9
a8 0a cc 93 80 18 18 eb fe 2e 00 00 01 01 08 0a
27 84 1a 14 b2 e5 29 71 e4 bd a0 e5 a5 bd

数据包结构解析

IP头部(前20字节):

  • 02 00 00 00: 可能是以太网帧头部分
  • 45: IP版本4,头部长度5×4=20字节
  • 00 00 3a: 总长度58字节
  • 40 00: 标志位和片偏移
  • 40 06: TTL=64,协议=6(TCP)
  • 7f 00 00 01: 源IP 127.0.0.1
  • 7f 00 00 01: 目标IP 127.0.0.1

TCP头部:

  • 1f 9a: 源端口8090
  • ff 11: 目标端口65297
  • bd 77 4d c9: 序列号
  • a8 0a cc 93: 确认号
  • 80 18: 头部长度和标志位(PSH+ACK)
  • 18 eb: 窗口大小
  • fe 2e: 校验和

TCP选项:

  • 01 01 08 0a 27 84 1a 14 b2 e5 29 71: TCP时间戳选项

应用数据内容: 关键部分 – 实际数据载荷:

e4 bd a0 e5 a5 bd

转换为UTF-8字符:

  • e4 bd a0 = 你
  • e5 a5 bd = 好

结论: 这个数据包发送的中文内容是:”你好”

2.5 客户端确认响应 (数据包111)

111  16.837015  127.0.0.1  →  127.0.0.1  TCP  56  65297 → 8090 [ACK] Seq=6 Ack=7 Win=408256 Len=0

数据包111分析:

  • 方向:客户端 → 服务器
  • 标志:[ACK]
  • 作用:确认服务器响应
  • 序列号:Ack=7表示期望下一个数据包从序列号7开始
  • 完成:一次完整的数据交换确认

3. TCP四次挥手分析

3.1 第一次挥手 (FIN)

112  16.837073  127.0.0.1  →  127.0.0.1  TCP  56  65297 → 8090 [FIN, ACK] Seq=6 Ack=7 Win=408256 Len=0

数据包112分析:

  • 方向:客户端 → 服务器
  • 标志:[FIN, ACK]
  • 作用:客户端发送FIN包,请求关闭连接
  • 序列号:Seq=6 Ack=7

3.2 第二次挥手 (ACK)

113  16.837093  127.0.0.1  →  127.0.0.1  TCP  56  8090 → 65297 [ACK] Seq=7 Ack=7 Win=408256 Len=0

数据包113分析:

  • 方向:服务器 → 客户端
  • 标志:[ACK]
  • 作用:服务器发送ACK包,确认收到FIN请求
  • 序列号:Seq=7 Ack=7

3.3 第三次挥手 (FIN)

114  16.837147  127.0.0.1  →  127.0.0.1  TCP  62  8090 → 65297 [PSH, ACK] Seq=7 Ack=7 Win=408256 Len=6

数据包114分析:

  • 方向:服务器 → 客户端
  • 标志:[PSH, ACK]
  • 作用:服务器发送自己的FIN包,请求关闭连接
  • 序列号:Seq=7 Ack=7

3.4 异常关闭 (RST)

115  16.837181  127.0.0.1  →  127.0.0.1  TCP  44  65297 → 8090 [RST] Seq=7 Win=0 Len=0

数据包115分析:

  • 方向:客户端 → 服务器
  • 标志:[RST]
  • 异常情况:客户端发送RST包而不是正常的ACK包
  • 原因:可能是因为客户端强制关闭连接

4. 测试代码

4.1 服务端代码

python
import socket

socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(f'socket对象:{socket}')
socket_server.bind(('127.0.0.1', 8090))
socket_server.listen(5)
socket_client, client_info = socket_server.accept()

while True:
    recv = socket_client.recv(1024)
    print(f'服务端接收到资源:{recv}')
    socket_client.send('你好'.encode())
    
socket_server.close()

4.2 客户端代码

python
import socket

socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(f'socket对象:{socket}')
socket_client.connect(('127.0.0.1', 8090))
socket_client.send('study'.encode('utf-8'))
recv = socket_client.recv(1024)
print(f'接受数据:{recv.decode()}')
socket_client.close()

5. 深入理解:为什么需要序列号(Seq)?

5.1 保证数据顺序

没有序列号的问题:

发送方: [数据1] [数据2] [数据3]
网络传输: 可能乱序到达
接收方: [数据3] [数据1] [数据2] ❌ 错误顺序

有序列号的解决方案:

发送方: [数据1-Seq=100] [数据2-Seq=150] [数据3-Seq=200]
网络传输: 可能乱序到达
接收方: [数据3-Seq=200] [数据1-Seq=100] [数据2-Seq=150]
重组后: [数据1] [数据2] [数据3] ✓ 正确顺序

5.2 检测数据丢失

发送方发送: Seq=100(50字节) → Seq=150(50字节) → Seq=200(50字节)
接收方收到: Seq=100 → Seq=200 (发现缺少Seq=150!)
接收方响应: "我收到了100和200,但是150丢了,请重发"

5.3 防止重复数据

网络延迟导致重复:
发送方: 发送Seq=100 → 超时重发Seq=100
接收方: 收到两个Seq=100 → 丢弃重复的包

5.4 实现可靠传输

发送方: 发送Seq=100-149 (50字节)
接收方: 发送Ack=150 (确认收到100-149,期望150开始)
发送方: 知道数据已安全到达,可以发送下一批数据

6. 关键观察点总结

  • 本地回环连接:所有通信都在127.0.0.1上进行
  • 端口使用:客户端使用临时端口65297,服务器监听8090端口
  • 异常关闭:最后一个包是RST而不是正常的ACK
  • 窗口管理:可以看到TCP窗口大小的动态调整过程
  • 序列号追踪:序列号和确认号的递增显示了数据传输的顺序

这个抓包展示了一个完整的TCP连接生命周期,但最后以RST异常终止而不是正常的四次挥手完成。通过这个实际案例,我们可以深入理解TCP协议的工作原理和数据传输机制。


TCP和QUIC面试题大全

第六部分:常见面试题深度解析

TCP的三次握手和四次挥手是网络编程面试中的高频考点。以下结合我们的实际抓包数据,深入分析常见面试题:

三次握手相关面试题

1. 基础概念题

Q1:什么是TCP三次握手?请详细描述三次握手的过程

基于抓包数据的标准答案:

从我们的抓包数据可以看到完整的三次握手过程:

# 第一次握手 (SYN)
104  16.836236  127.0.0.1  →  127.0.0.1  TCP  68  65297 → 8090 [SYN] Seq=0 Win=65535 Len=0

# 第二次握手 (SYN+ACK)  
105  16.836315  127.0.0.1  →  127.0.0.1  TCP  68  8090 → 65297 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0

# 第三次握手 (ACK)
106  16.836324  127.0.0.1  →  127.0.0.1  TCP  56  65297 → 8090 [ACK] Seq=1 Ack=1 Win=408256 Len=0

详细过程:

  1. 第一次握手:客户端发送SYN包(Seq=0),状态变为SYN_SENT
  2. 第二次握手:服务器回复SYN+ACK包(Seq=0, Ack=1),状态变为SYN_RCVD
  3. 第三次握手:客户端发送ACK包(Seq=1, Ack=1),状态变为ESTABLISHED

Q2:为什么需要三次握手?两次握手不行吗?

实际案例分析: 观察数据包104-106,我们看到:

  • 包104:客户端证明自己能发送
  • 包105:服务器证明自己能接收和发送
  • 包106:客户端证明自己能接收

如果只有两次握手,客户端无法确认服务器是否收到了自己的确认,可能导致:

  • 旧的连接请求在网络中延迟到达
  • 服务器误认为是新的连接请求
  • 造成资源浪费和连接混乱

Q3:三次握手中的SYN、ACK、seq、ack字段分别代表什么?

基于抓包数据解释:

从包104看到:

  • SYN=1:同步序列号标志,表示请求建立连接
  • Seq=0:客户端的初始序列号
  • ACK=0:此时还没有确认信息

从包105看到:

  • SYN=1, ACK=1:同时设置同步和确认标志
  • Seq=0:服务器的初始序列号
  • Ack=1:确认客户端的序列号+1

2. 深入理解题

Q4:第三次握手失败了会怎么样?

基于抓包分析: 如果包106丢失,会出现:

  • 客户端认为连接已建立(发送了ACK)
  • 服务器仍在SYN_RCVD状态(没收到ACK)
  • 客户端发送数据时,服务器会回复RST包
  • 类似我们抓包中包115的RST情况

Q5:什么是SYN洪水攻击?如何防范?

攻击原理: 恶意客户端大量发送SYN包(类似包104),但不完成第三次握手,导致服务器:

  • 半连接队列被占满
  • 无法处理正常连接请求

防范措施:

bash
# 调整半连接队列大小
net.ipv4.tcp_max_syn_backlog = 2048

# 启用SYN Cookie
net.ipv4.tcp_syncookies = 1

# 减少SYN+ACK重传次数
net.ipv4.tcp_synack_retries = 2

3. 状态变迁题

Q6:描述TCP连接建立过程中客户端和服务端的状态变化

基于抓包时间线分析:

时间轴:16.836236 → 16.836315 → 16.836324

客户端状态变化:
CLOSED → SYN_SENT(发送包104) → ESTABLISHED(发送包106)

服务器状态变化:  
LISTEN → SYN_RCVD(发送包105) → ESTABLISHED(收到包106)

四次挥手相关面试题

1. 基础概念题

Q7:什么是TCP四次挥手?请详细描述四次挥手的过程

基于抓包数据分析:

# 第一次挥手 (FIN)
112  16.837073  127.0.0.1  →  127.0.0.1  TCP  56  65297 → 8090 [FIN, ACK] Seq=6 Ack=7 Win=408256 Len=0

# 第二次挥手 (ACK)
113  16.837093  127.0.0.1  →  127.0.0.1  TCP  56  8090 → 65297 [ACK] Seq=7 Ack=7 Win=408256 Len=0

# 第三次挥手 (FIN) - 包114实际发送了数据
114  16.837147  127.0.0.1  →  127.0.0.1  TCP  62  8090 → 65297 [PSH, ACK] Seq=7 Ack=7 Win=408256 Len=6

# 第四次挥手异常 (RST)
115  16.837181  127.0.0.1  →  127.0.0.1  TCP  44  65297 → 8090 [RST] Seq=7 Win=0 Len=0

注意:我们的抓包显示了一个异常情况,最后是RST而不是正常的ACK。

Q8:为什么连接的建立是三次握手,而关闭却是四次挥手?

关键区别分析:

  • 建立连接:SYN和ACK可以合并在一个包中发送(包105)
  • 关闭连接:FIN和ACK通常需要分开发送,因为:
    • 收到FIN后需要立即ACK确认(包113)
    • 但可能还有数据要发送(包114发送了6字节数据)
    • 发送完所有数据后才能发送自己的FIN

2. TIME_WAIT相关题

Q9:什么是TIME_WAIT状态?为什么需要TIME_WAIT?

我们抓包中的异常情况: 包115显示连接被RST强制关闭,如果是正常关闭:

  • 客户端发送最后的ACK后进入TIME_WAIT
  • 持续2MSL(Maximum Segment Lifetime)时间
  • 确保对方收到最后的ACK

TIME_WAIT的作用:

  1. 确保最后的ACK能够到达:如果ACK丢失,对方会重发FIN
  2. 防止旧连接的数据包干扰新连接:等待网络中旧数据包消失

Q10:TIME_WAIT状态过多会有什么问题?如何解决?

问题分析:

  • 每个TIME_WAIT占用一个端口(如我们例子中的65297)
  • 大量TIME_WAIT可能导致端口耗尽
  • 内存资源占用

解决方案:

bash
# 启用TIME_WAIT重用
net.ipv4.tcp_tw_reuse = 1

# 调整TIME_WAIT超时时间
net.ipv4.tcp_fin_timeout = 30

# 使用SO_REUSEADDR选项
socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

3. 异常情况题

Q11:什么是RST包?什么时候会发送RST包?

基于包115的RST分析:

115  16.837181  127.0.0.1  →  127.0.0.1  TCP  44  65297 → 8090 [RST] Seq=7 Win=0 Len=0

RST包的特点:

  • Win=0:窗口大小为0
  • 立即关闭连接,不进入TIME_WAIT状态
  • 表示连接异常终止

发送RST的常见情况:

  1. 端口未监听:连接到未开放的端口
  2. 连接异常:应用程序崩溃
  3. 协议错误:收到意外的数据包
  4. 强制关闭:应用程序强制关闭连接

四、TCP vs QUIC对比题

基础对比题

Q11: TCP的三次握手和QUIC的握手有什么区别?

TCP三次握手:

  • 需要3个RTT(包括TLS握手)
  • 顺序:TCP握手 → TLS握手 → 数据传输

QUIC握手:

  • 首次连接:1-RTT
  • 重复连接:0-RTT
  • 集成:传输层和安全层集成

Q12: 为什么QUIC可以实现0-RTT连接建立?

QUIC 0-RTT的实现:

  • 会话恢复:复用之前的加密参数
  • 预共享密钥:使用PSK(Pre-Shared Key)
  • 风险控制:限制0-RTT数据的类型,防止重放攻击

性能对比题

Q13: 在连接建立速度上,QUIC相比TCP+TLS有什么优势?

TCP + TLS握手时间:

TCP三次握手: 1 RTT
TLS握手: 1-2 RTT
总计: 2-3 RTT

QUIC握手时间:

首次连接: 1 RTT
重复连接: 0 RTT
减少: 1-3 RTT

实际性能提升:

  • 延迟减少:特别是在高延迟网络中优势明显
  • 移动网络:网络切换时的连接恢复更快

连接管理对比题

Q14: QUIC的连接关闭机制和TCP的四次挥手有什么不同?

TCP四次挥手:

  • 需要4个数据包
  • 存在TIME_WAIT状态
  • 可能出现RST异常关闭(如我们的例子)

QUIC连接关闭:

  • CONNECTION_CLOSE帧:优雅关闭
  • 无TIME_WAIT:立即释放资源
  • 连接迁移:支持连接ID变更

多路复用对比题

Q15: TCP的单流阻塞问题(Head-of-Line Blocking)是什么?

TCP队头阻塞问题:

发送顺序: [包1] [包2] [包3]
网络情况: 包1丢失,包2、包3到达
TCP行为: 必须等待包1重传,包2、包3被阻塞

QUIC多路复用解决方案:

Stream 1: [包1丢失] - 只影响Stream 1
Stream 2: [包2到达] - 立即处理
Stream 3: [包3到达] - 立即处理

备注:TCP确实也有多路复用(通过多个TCP连接),但QUIC解决队头阻塞的根本原因在于流级别的独立性,而不仅仅是多路复用本身。

TCP的队头阻塞发生在传输层

  • TCP保证字节流的有序传递
  • 即使应用层有多个逻辑流,它们都共享同一个TCP连接的字节流
  • 当某个包丢失时,TCP必须等待重传并按序重组,阻塞整个连接
TCP连接中的数据流:
[Stream1数据][Stream2数据][丢失包][Stream3数据]
         ↓
由于丢失包,Stream3数据无法被应用层读取

QUIC的解决方案:

QUIC在应用层实现了真正的流独立性:

  1. 独立的流重组:每个QUIC流有自己的接收缓冲区和重组逻辑
  2. 流级别的流量控制:每个流可以独立进行流量控制
  3. 选择性确认:可以针对特定流的包进行确认,而不影响其他流
QUIC多流传输:
Stream 1: [包1丢失] - 只影响Stream 1,等待重传
Stream 2: [包2到达] - 立即处理并交付给应用层
Stream 3: [包3到达] - 立即处理并交付给应用层

为什么TCP多路复用不能解决:

HTTP/1.1的多个TCP连接确实能缓解问题,但:

  • 资源开销大:每个连接都有独立的拥塞控制、慢启动
  • 连接管理复杂:需要维护多个TCP状态机
  • 仍有局部阻塞:单个连接内的多个请求仍会相互阻塞

HTTP/2虽然在单个TCP连接上实现了多路复用,但底层TCP层面的队头阻塞问题依然存在。

所以QUIC的核心优势是在单个连接内实现了真正的流级别独立性,这是TCP层面无法解决的架构问题。

 

五、实际应用场景题

部署考虑题

Q16: 在什么场景下选择QUIC比TCP更有优势?

 QUIC适用场景:

  • 移动网络:频繁的网络切换
  • 高延迟网络:卫星通信、跨国连接
  • 实时应用:在线游戏、视频直播
  • Web应用:HTTP/3,特别是多资源加载

TCP适用场景:

  • 企业内网:稳定的网络环境
  • 批量传输:大文件传输
  • 传统应用:现有的TCP应用生态

协议演进题

Q17: 为什么HTTP/3选择了QUIC而不是改进TCP?

 TCP改进的限制:

  • 内核实现:TCP在内核中实现,修改困难
  • 中间设备:防火墙、NAT设备的兼容性问题
  • 向后兼容:不能破坏现有的TCP应用

QUIC的优势:

  • 用户空间实现:易于更新和优化
  • UDP基础:绕过中间设备的TCP限制
  • 快速迭代:可以快速部署新特性

总结与感想

通过本次Wireshark抓包分析,我们深入理解了TCP协议的核心机制:

关键发现

  1. 三次握手:确保双方都能收发数据
  2. 数据传输:通过序列号保证可靠性
  3. 四次挥手:优雅地关闭连接(本例中出现异常RST)
  4. 序列号机制:TCP可靠传输的基础

Python内存结构模型源码详细解析

2025-6-28 评论(0) 分类:人工智能 Tags:

1. 核心对象模型 (Include/object.h)

1.1 基础对象结构 PyObject

// Include/object.h
typedef struct _object {
    _PyObject_HEAD_EXTRA    // 调试模式下的额外字段
    Py_ssize_t ob_refcnt;   // 引用计数
    PyTypeObject *ob_type;  // 类型对象指针
} PyObject;

// 变长对象 PyVarObject是Python中用于表示可变长度对象(如列表、元组、字符串等)的基础结构。它扩展了基本的PyObject,增加了一个表示对象中元素数量的字段(ob_size)
typedef struct {
    PyObject ob_base;       // 基础对象
    Py_ssize_t ob_size;     // 对象大小
} PyVarObject;

源码位置: Include/object.h:106-120

内存布局:

PyObject:
┌─────────────┬──────────────┬─────────────┐
│ ob_refcnt   │   ob_type    │   data...   │
│   8 bytes   │   8 bytes    │  variable   │
└─────────────┴──────────────┴─────────────┘

1.2 类型对象 PyTypeObject

// Include/cpython/object.h
typedef struct _typeobject {
    PyVarObject ob_base;
    const char *tp_name;             // 类型名称
    Py_ssize_t tp_basicsize;         // 基本大小
    Py_ssize_t tp_itemsize;          // 元素大小

    // 析构和打印
    destructor tp_dealloc;
    Py_ssize_t tp_vectorcall_offset;

    // 标准方法
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async;
    reprfunc tp_repr;

    // 数值方法、序列方法、映射方法
    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    // 更多字段...
} PyTypeObject;

2. 整数对象内存模型 (Objects/longobject.c)

2.1 长整数结构

// Include/cpython/longintrepr.h
struct _longobject {
    PyVarObject ob_base;
    digit ob_digit[1];      // 数字位数组
};

typedef struct _longobject PyLongObject;

// digit 是 30 位或 15 位的无符号整数
#if PYLONG_BITS_IN_DIGIT == 30
typedef uint32_t digit;
typedef int32_t sdigit;
typedef uint64_t twodigits;
#elif PYLONG_BITS_IN_DIGIT == 15
typedef unsigned short digit;
typedef short sdigit;
typedef unsigned long twodigits;
#endif

小整数缓存机制:

// Objects/longobject.c
#define IS_SMALL_INT(ival) (-_PY_NSMALLNEGINTS <= (ival) && (ival) < _PY_NSMALLPOSINTS)
#define IS_SMALL_UINT(ival) ((ival) < _PY_NSMALLPOSINTS)
// Objects/longobject.c
#define NSMALLPOSINTS           257
#define NSMALLNEGINTS           5

// 小整数对象池 [-5, 256]
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

PyObject *
PyLong_FromLong(long ival)
{
    // 使用小整数缓存
    if (IS_SMALL_INT(ival)) {
        return get_small_int((sdigit)ival);
    }
    // 创建新的长整数对象
    return PyLong_FromLongLong(ival);
}

2.2 内存布局示例

小整数 (42):
┌───────────┬──────────┬────────┬──────────┐
│ ob_refcnt │ ob_type  │ob_size │ ob_digit │
│     ?     │  &Long   │   1    │    42    │
└───────────┴──────────┴────────┴──────────┘

大整数 (2^100):
┌───────────┬──────────┬────────┬─────────────────────┐
│ ob_refcnt │ ob_type  │ob_size │    ob_digit[]       │
│     1     │  &Long   │   4    │ [低位...高位]       │
└───────────┴──────────┴────────┴─────────────────────┘

3. 字符串对象内存模型 (Objects/unicodeobject.c)

3.1 Unicode 对象结构

// Include/cpython/unicodeobject.h
typedef struct {
    PyObject_HEAD
    Py_ssize_t length;          // 字符串长度
    Py_hash_t hash;            // 哈希值缓存
    struct {
        unsigned int interned:2;    // 字符串驻留状态
        unsigned int kind:3;        // 字符串类型
        unsigned int compact:1;     // 是否紧凑存储
        unsigned int ascii:1;       // 是否纯ASCII
        unsigned int ready:1;       // 是否就绪
        unsigned int :24;
    } state;
    wchar_t *wstr;             // 宽字符表示(可选)
} PyASCIIObject;

// 紧凑 Unicode 对象
typedef struct {
    PyASCIIObject _base;
    Py_ssize_t utf8_length;    // UTF-8 长度
    char *utf8;                // UTF-8 缓存
    Py_ssize_t wstr_length;    // 宽字符长度
} PyCompactUnicodeObject;

3.2 字符串驻留机制

// Objects/unicodeobject.c
static PyObject *interned = NULL;  // 驻留字符串字典

void
PyUnicode_InternInPlace(PyObject **p)
{
    PyObject *s = *p;
    if (PyUnicode_READY(s) == -1) {
        return;
    }

    // 检查是否已驻留
    if (PyUnicode_CHECK_INTERNED(s)) {
        return;
    }

    // 添加到驻留字典
    PyObject *t = PyDict_SetDefault(interned, s, s);
    if (t != s) {
        Py_SETREF(*p, Py_NewRef(t));
    }
    PyUnicode_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL;
}

4. 列表对象内存模型 (Objects/listobject.c)

4.1 列表结构

// Include/cpython/listobject.h
typedef struct {
    PyVarObject ob_base;
    PyObject **ob_item;        // 指向元素数组的指针
    Py_ssize_t allocated;      // 已分配的空间大小
} PyListObject;

4.2 动态扩容机制

// Objects/listobject.c
static int
list_resize(PyListObject *self, Py_ssize_t newsize)
{
    PyObject **items;
    size_t new_allocated, num_allocated_bytes;
    Py_ssize_t allocated = self->allocated;

    // 扩容策略: newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6)
    new_allocated = ((size_t)newsize + (newsize >> 3) + 6) & ~(size_t)3;

    if (newsize == 0) {
        PyMem_FREE(self->ob_item);
        self->ob_item = NULL;
        self->allocated = 0;
        return 0;
    }

    // 重新分配内存
    items = (PyObject **)PyMem_Realloc(self->ob_item,
                                       new_allocated * sizeof(PyObject *));
    if (items == NULL) {
        PyErr_NoMemory();
        return -1;
    }

    self->ob_item = items;
    self->allocated = new_allocated;
    return 0;
}

4.3 内存布局

PyObject:
┌─────────────┬──────────────┬─────────────┐
│ ob_refcnt   │   ob_type    │   data...   │
│   8 bytes   │   8 bytes    │  variable   │
└─────────────┴──────────────┴─────────────┘

1.2 类型对象 PyTypeObject

// Include/cpython/object.h
typedef struct _typeobject {
    PyVarObject ob_base;
    const char *tp_name;             // 类型名称
    Py_ssize_t tp_basicsize;         // 基本大小
    Py_ssize_t tp_itemsize;          // 元素大小

    // 析构和打印
    destructor tp_dealloc;
    Py_ssize_t tp_vectorcall_offset;

    // 标准方法
    getattrfunc tp_getattr;
    setattrfunc tp_setattr;
    PyAsyncMethods *tp_as_async;
    reprfunc tp_repr;

    // 数值方法、序列方法、映射方法
    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    // 更多字段...
} PyTypeObject;

2. 整数对象内存模型 (Objects/longobject.c)

2.1 长整数结构

// Include/cpython/longintrepr.h
struct _longobject {
    PyVarObject ob_base;
    digit ob_digit[1];      // 数字位数组
};

typedef struct _longobject PyLongObject;

// digit 是 30 位或 15 位的无符号整数
#if PYLONG_BITS_IN_DIGIT == 30
typedef uint32_t digit;
typedef int32_t sdigit;
typedef uint64_t twodigits;
#elif PYLONG_BITS_IN_DIGIT == 15
typedef unsigned short digit;
typedef short sdigit;
typedef unsigned long twodigits;
#endif

小整数缓存机制:

// Objects/longobject.c
#define IS_SMALL_INT(ival) (-_PY_NSMALLNEGINTS <= (ival) && (ival) < _PY_NSMALLPOSINTS)
#define IS_SMALL_UINT(ival) ((ival) < _PY_NSMALLPOSINTS)
// Objects/longobject.c
#define NSMALLPOSINTS           257
#define NSMALLNEGINTS           5

// 小整数对象池 [-5, 256]
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

PyObject *
PyLong_FromLong(long ival)
{
    // 使用小整数缓存
    if (IS_SMALL_INT(ival)) {
        return get_small_int((sdigit)ival);
    }
    // 创建新的长整数对象
    return PyLong_FromLongLong(ival);
}

2.2 内存布局示例

小整数 (42):
┌───────────┬──────────┬────────┬──────────┐
│ ob_refcnt │ ob_type  │ob_size │ ob_digit │
│     ?     │  &Long   │   1    │    42    │
└───────────┴──────────┴────────┴──────────┘

大整数 (2^100):
┌───────────┬──────────┬────────┬─────────────────────┐
│ ob_refcnt │ ob_type  │ob_size │    ob_digit[]       │
│     1     │  &Long   │   4    │ [低位...高位]       │
└───────────┴──────────┴────────┴─────────────────────┘

3. 字符串对象内存模型 (Objects/unicodeobject.c)

3.1 Unicode 对象结构

// Include/cpython/unicodeobject.h
typedef struct {
    PyObject_HEAD
    Py_ssize_t length;          // 字符串长度
    Py_hash_t hash;            // 哈希值缓存
    struct {
        unsigned int interned:2;    // 字符串驻留状态
        unsigned int kind:3;        // 字符串类型
        unsigned int compact:1;     // 是否紧凑存储
        unsigned int ascii:1;       // 是否纯ASCII
        unsigned int ready:1;       // 是否就绪
        unsigned int :24;
    } state;
    wchar_t *wstr;             // 宽字符表示(可选)
} PyASCIIObject;

// 紧凑 Unicode 对象
typedef struct {
    PyASCIIObject _base;
    Py_ssize_t utf8_length;    // UTF-8 长度
    char *utf8;                // UTF-8 缓存
    Py_ssize_t wstr_length;    // 宽字符长度
} PyCompactUnicodeObject;

3.2 字符串驻留机制

// Objects/unicodeobject.c
static PyObject *interned = NULL;  // 驻留字符串字典

void
PyUnicode_InternInPlace(PyObject **p)
{
    PyObject *s = *p;
    if (PyUnicode_READY(s) == -1) {
        return;
    }

    // 检查是否已驻留
    if (PyUnicode_CHECK_INTERNED(s)) {
        return;
    }

    // 添加到驻留字典
    PyObject *t = PyDict_SetDefault(interned, s, s);
    if (t != s) {
        Py_SETREF(*p, Py_NewRef(t));
    }
    PyUnicode_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL;
}

4. 列表对象内存模型 (Objects/listobject.c)

4.1 列表结构

// Include/cpython/listobject.h
typedef struct {
    PyVarObject ob_base;
    PyObject **ob_item;        // 指向元素数组的指针
    Py_ssize_t allocated;      // 已分配的空间大小
} PyListObject;

4.2 动态扩容机制

// Objects/listobject.c
static int
list_resize(PyListObject *self, Py_ssize_t newsize)
{
    PyObject **items;
    size_t new_allocated, num_allocated_bytes;
    Py_ssize_t allocated = self->allocated;

    // 扩容策略: newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6)
    new_allocated = ((size_t)newsize + (newsize >> 3) + 6) & ~(size_t)3;

    if (newsize == 0) {
        PyMem_FREE(self->ob_item);
        self->ob_item = NULL;
        self->allocated = 0;
        return 0;
    }

    // 重新分配内存
    items = (PyObject **)PyMem_Realloc(self->ob_item,
                                       new_allocated * sizeof(PyObject *));
    if (items == NULL) {
        PyErr_NoMemory();
        return -1;
    }

    self->ob_item = items;
    self->allocated = new_allocated;
    return 0;
}

4.3 内存布局

空列表:
┌───────────┬──────────┬────────┬──────────┬───────────┐
│ ob_refcnt │ ob_type  │ob_size │ ob_item  │ allocated │
│     1     │  &List   │   0    │   NULL   │     0     │
└───────────┴──────────┴────────┴──────────┴───────────┘

包含元素的列表 [1, 2, 3]:
┌───────────┬──────────┬────────┬──────────┬───────────┐
│ ob_refcnt │ ob_type  │ob_size │ ob_item  │ allocated │
│     1     │  &List   │   3    │   ptr    │     4     │
└───────────┴──────────┴────────┴──────────┴───────────┘
                                      │
                                      ▼
                               ┌──────────────────┐
                               │ PyObject *[4]    │
                               │ [0]: &int(1)     │
                               │ [1]: &int(2)     │
                               │ [2]: &int(3)     │
                               │ [3]: NULL        │
                               └──────────────────┘

5. 字典对象内存模型 (Objects/dictobject.c)

5.1 字典结构 (紧凑表示)

// Objects/dictobject.c
struct _dictkeysobject {
    Py_ssize_t dk_refcnt;      // 键对象引用计数
    Py_ssize_t dk_size;        // 哈希表大小
    dict_lookup_func dk_lookup; // 查找函数
    Py_ssize_t dk_usable;      // 可用槽位数
    Py_ssize_t dk_nentries;    // 已使用条目数
    char dk_indices[];         // 索引数组 + 条目数组
};

typedef struct {
    PyObject_HEAD
    Py_ssize_t ma_used;        // 已使用条目数
    uint64_t ma_version_tag;   // 版本标签
    PyDictKeysObject *ma_keys; // 键对象
    PyObject **ma_values;      // 值数组(分离表示)
} PyDictObject;

5.2 哈希冲突解决

// Objects/dictobject.c
static Py_ssize_t
lookdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr)
{
    PyDictKeysObject *dk = mp->ma_keys;
    size_t mask = DK_MASK(dk);
    size_t perturb = hash;
    size_t i = (size_t)hash & mask;

    // 开放寻址法解决冲突
    for (;;) {
        Py_ssize_t ix = dk_get_index(dk, i);
        if (ix == DKIX_EMPTY) {
            *value_addr = NULL;
            return ix;
        }

        PyDictKeyEntry *ep = &DK_ENTRIES(dk)[ix];
        assert(ep->me_key != NULL);

        if (ep->me_key == key) {
            *value_addr = ep->me_value;
            return ix;
        }

        // 扰动探测
        perturb >>= PERTURB_SHIFT;
        i = (i*5 + 1 + perturb) & mask;
    }
}

6. 内存管理机制

6.1 对象分配器 (Objects/obmalloc.c)

// Objects/obmalloc.c

// 内存池结构
struct pool_header {
    union { block *_padding;
            uint count; } ref;          // 引用计数
    block *freeblock;                  // 空闲块链表
    struct pool_header *nextpool;      // 下一个池
    struct pool_header *prevpool;      // 前一个池
    uint arenaindex;                   // 竞技场索引
    uint szidx;                        // 大小索引
    uint nextoffset;                   // 下一个偏移
    uint maxnextoffset;                // 最大偏移
};

// PyObject_Malloc 实现
void *
PyObject_Malloc(size_t size)
{
    if (size > SMALL_REQUEST_THRESHOLD) {
        return PyMem_RawMalloc(size);
    }

    // 使用对象分配器
    uint pool_idx = size_to_pool_idx(size);
    pool_header *pool = usedpools[pool_idx];

    if (pool != NULL) {
        // 从现有池分配
        block *bp = pool->freeblock;
        if (bp != NULL) {
            pool->freeblock = *(block **)bp;
            return (void *)bp;
        }
    }

    // 创建新池或使用系统分配器
    return allocate_from_new_pool(size);
}

6.2 垃圾回收机制 (Modules/gcmodule.c)

// Modules/gcmodule.c

// GC 头结构
typedef union _gc_head {
    struct {
        union _gc_head *gc_next;
        union _gc_head *gc_prev;
        Py_ssize_t gc_refs;
    } gc;
    double dummy;  // 对齐
} PyGC_Head;

// 垃圾回收主函数
static Py_ssize_t
gc_collect_main(PyThreadState *tstate, int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, int nofail)
{
    Py_ssize_t m = 0; // 收集的对象数
    Py_ssize_t n = 0; // 无法收集的对象数
    PyGC_Head *young; // 年轻代
    PyGC_Head *old; // 老年代
    PyGC_Head unreachable; // 不可达对象
    PyGC_Head finalizers; // 终结器对象

    // 标记-清扫算法
    // 1. 将引用计数复制到gc_refs
    update_refs(young);

    // 2. 减少内部引用
    subtract_refs(young);

    // 3. 标记可达对象
    move_unreachable(young, &unreachable);

    // 4. 处理终结器
    move_legacy_finalizers(&unreachable, &finalizers);

    // 5. 删除不可达对象
    delete_garbage(tstate, &unreachable, generation);

    return n+m;
}
// Modules/gcmodule.c 详细展开
static Py_ssize_t
gc_collect_main(PyThreadState *tstate, int generation,
                Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
                int nofail)
{
    int i;
    Py_ssize_t m = 0; /* # objects collected */
    Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */
    PyGC_Head *young; /* the generation we are examining */
    PyGC_Head *old; /* next older generation */
    PyGC_Head unreachable; /* non-problematic unreachable trash */
    PyGC_Head finalizers;  /* objects with, & reachable from, __del__ */
    PyGC_Head *gc;
    _PyTime_t t1 = 0;   /* initialize to prevent a compiler warning */
    GCState *gcstate = &tstate->interp->gc;

    // gc_collect_main() must not be called before _PyGC_Init
    // or after _PyGC_Fini()
    assert(gcstate->garbage != NULL);
    assert(!_PyErr_Occurred(tstate));

    if (gcstate->debug & DEBUG_STATS) {
        PySys_WriteStderr("gc: collecting generation %d...\n", generation);
        show_stats_each_generations(gcstate);
        t1 = _PyTime_GetPerfCounter();
    }

    if (PyDTrace_GC_START_ENABLED())
        PyDTrace_GC_START(generation);

        **//重置当前代及更年轻代的计数器,增加下一代的计数器,之所以重制当前代的计数器,是为了完成垃圾回收后,当前的计数器还处于较高的峰值,再次触发回收,给下一代增加1 ,这个很好理解,为了出发老年代的对象**
        
    /* update collection and allocation counters */
    if (generation+1 < NUM_GENERATIONS)
        gcstate->generations[generation+1].count += 1;
    for (i = 0; i <= generation; i++)
        gcstate->generations[i].count = 0;

        **//这是分代收集的关键:收集第N代时,会同时收集所有更年轻的代(0到N-1代)。**
    /* merge younger generations with one we are currently collecting */
    for (i = 0; i < generation; i++) {
        gc_list_merge(GEN_HEAD(gcstate, i), GEN_HEAD(gcstate, generation));
    }

        **//young: 指向当前正在收集的代
        //old: 指向下一个更老的代(或在特殊情况下指向当前代)**
    /* handy references */
    young = GEN_HEAD(gcstate, generation);
    if (generation < NUM_GENERATIONS-1)
        old = GEN_HEAD(gcstate, generation+1);
    else
        old = young;
    validate_list(old, collecting_clear_unreachable_clear);

        **//将不可达的对象封装到unreachable链表里,可达对象保留在young链表中,分离不可达对象(move_unreachable): 将`gc_refs`为0的对象移到不可达链表,大于0的对象留在可达链表。**
    deduce_unreachable(young, &unreachable);

    untrack_tuples(young);
    /* Move reachable objects to next generation. */
    if (young != old) {
        if (generation == NUM_GENERATIONS - 2) {
            gcstate->long_lived_pending += gc_list_size(young);
        }
        gc_list_merge(young, old);
    }
    else {
        /* We only un-track dicts in full collections, to avoid quadratic
           dict build-up. See issue #14775. */
        untrack_dicts(young);
        gcstate->long_lived_pending = 0;
        gcstate->long_lived_total = gc_list_size(young);
    }

        **//创建一个空的链表用于存放有终结器的对象
        //这个列表将包含所有不能立即删除的"问题"对象**
    /* All objects in unreachable are trash, but objects reachable from
     * legacy finalizers (e.g. tp_del) can't safely be deleted.
     */
    gc_list_init(&finalizers);
    // NEXT_MASK_UNREACHABLE is cleared here.
    // After move_legacy_finalizers(), unreachable is normal list.
    
        //   从unreachable列表中找出所有有遗留终结器的对象,移动到finalizers列表
        //具体过程:
        //	遍历unreachable列表中的每个对象
        //	检查对象是否有__del__方法或tp_del函数
        //	如果有,将该对象从unreachable移动到finalizers
        //	同时清除对象的NEXT_MASK_UNREACHABLE标记

    move_legacy_finalizers(&unreachable, &finalizers);
    
    **//找出从终结器对象可达的其他对象,也移动到finalizers列表**
    /* finalizers contains the unreachable objects with a legacy finalizer;
     * unreachable objects reachable *from* those are also uncollectable,
     * and we move those into the finalizers list too.
     */
    move_legacy_finalizer_reachable(&finalizers);

    validate_list(&finalizers, collecting_clear_unreachable_clear);
    validate_list(&unreachable, collecting_set_unreachable_clear);

    /* Print debugging information. */
    if (gcstate->debug & DEBUG_COLLECTABLE) {
        for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) {
            debug_cycle("collectable", FROM_GC(gc));
        }
    }

        **//清理弱引用并调用相关回调函数。**
    /* Clear weakrefs and invoke callbacks as necessary. */
    m += handle_weakrefs(&unreachable, old);

    validate_list(old, collecting_clear_unreachable_clear);
    validate_list(&unreachable, collecting_set_unreachable_clear);

        **//调用对象的tp_finalize方法**
    /* Call tp_finalize on objects which have one. */
    finalize_garbage(tstate, &unreachable);

    /* Handle any objects that may have resurrected after the call
     * to 'finalize_garbage' and continue the collection with the
     * objects that are still unreachable */
    PyGC_Head final_unreachable;
    handle_resurrected_objects(&unreachable, &final_unreachable, old);

    /* Call tp_clear on objects in the final_unreachable set.  This will cause
    * the reference cycles to be broken.  It may also cause some objects
    * in finalizers to be freed.
    */
    m += gc_list_size(&final_unreachable);
    delete_garbage(tstate, gcstate, &final_unreachable, old);

    /* Collect statistics on uncollectable objects found and print
     * debugging information. */
    for (gc = GC_NEXT(&finalizers); gc != &finalizers; gc = GC_NEXT(gc)) {
        n++;
        if (gcstate->debug & DEBUG_UNCOLLECTABLE)
            debug_cycle("uncollectable", FROM_GC(gc));
    }
    if (gcstate->debug & DEBUG_STATS) {
        double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1);
        PySys_WriteStderr(
            "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n",
            n+m, n, d);
    }

    /* Append instances in the uncollectable set to a Python
     * reachable list of garbage.  The programmer has to deal with
     * this if they insist on creating this type of structure.
     */
    handle_legacy_finalizers(tstate, gcstate, &finalizers, old);
    validate_list(old, collecting_clear_unreachable_clear);

    /* Clear free list only during the collection of the highest
     * generation */
    if (generation == NUM_GENERATIONS-1) {
        clear_freelists(tstate->interp);
    }

    if (_PyErr_Occurred(tstate)) {
        if (nofail) {
            _PyErr_Clear(tstate);
        }
        else {
            _PyErr_WriteUnraisableMsg("in garbage collection", NULL);
        }
    }

    /* Update stats */
    if (n_collected) {
        *n_collected = m;
    }
    if (n_uncollectable) {
        *n_uncollectable = n;
    }

    struct gc_generation_stats *stats = &gcstate->generation_stats[generation];
    stats->collections++;
    stats->collected += m;
    stats->uncollectable += n;

    if (PyDTrace_GC_DONE_ENABLED()) {
        PyDTrace_GC_DONE(n + m);
    }

    assert(!_PyErr_Occurred(tstate));
    return n + m;
}
move_legacy_finalizers(&unreachable, &finalizers)作用

功能:找出从终结器对象可达的其他对象,也移动到finalizers列表
为什么需要这步?
Container:
    def __del__(self):
        # 可能访问self.data
        print(f"删除容器: {self.data}")

class Data:
    pass

# 创建循环引用
container = Container()
data = Data()
container.data = data
data.container = container
在这个例子中:

Container有__del__方法,会被放入finalizers
Data对象虽然没有终结器,但从Container可达
如果先删除Data,Container.__del__执行时会出错
因此Data也必须被标记为不可收集

执行过程:

从finalizers中的每个对象开始
通过BFS/DFS遍历所有可达对象
将这些可达对象也移动到finalizers列表
确保终结器执行时不会访问到已删除的对象

6.2.2. finalize_garbage方法

finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable)
{
    destructor finalize;
    PyGC_Head seen;

    /* While we're going through the loop, `finalize(op)` may cause op, or
     * other objects, to be reclaimed via refcounts falling to zero.  So
     * there's little we can rely on about the structure of the input
     * `collectable` list across iterations.  For safety, we always take the
     * first object in that list and move it to a temporary `seen` list.
     * If objects vanish from the `collectable` and `seen` lists we don't
     * care.
     */
    gc_list_init(&seen);

    while (!gc_list_is_empty(collectable)) {
        PyGC_Head *gc = GC_NEXT(collectable);
        PyObject *op = FROM_GC(gc);
        gc_list_move(gc, &seen);
        if (!_PyGCHead_FINALIZED(gc) &&
                (finalize = Py_TYPE(op)->tp_finalize) != NULL) {
            _PyGCHead_SET_FINALIZED(gc);
            Py_INCREF(op);
            finalize(op);
            assert(!_PyErr_Occurred(tstate));
            Py_DECREF(op);
        }
    }
    gc_list_merge(&seen, collectable);
}
(Objects/typeobject.c)

FLSLOT(__init__, tp_init, slot_tp_init, (wrapperfunc)(void(*)(void))wrap_init,
           "__init__($self, /, *args, **kwargs)\n--\n\n"
           "Initialize self.  See help(type(self)) for accurate signature.",
           PyWrapperFlag_KEYWORDS),
    TPSLOT(__new__, tp_new, slot_tp_new, NULL,
           "__new__(type, /, *args, **kwargs)\n--\n\n"
           "Create and return new object.  See help(type) for accurate signature."),
    **TPSLOT(__del__, tp_finalize, slot_tp_finalize, (wrapperfunc)wrap_del, ""),**

    BUFSLOT(__buffer__, bf_getbuffer, slot_bf_getbuffer, wrap_buffer,
            "__buffer__($self, flags, /)\n--\n\n"
            "Return a buffer object that exposes the underlying memory of the object."),
    BUFSLOT(__release_buffer__, bf_releasebuffer, slot_bf_releasebuffer, wrap_releasebuffer,
            "__release_buffer__($self, buffer, /)\n--\n\n"
            "Release the buffer object that exposes the underlying memory of the object."),
                     
......

static void
slot_tp_finalize(PyObject *self)
{
    int unbound;
    PyObject *del, *res;

    /* Save the current exception, if any. */
    PyObject *exc = PyErr_GetRaisedException();

    /* Execute __del__ method, if any. */
    del = lookup_maybe_method(self, &_Py_ID(__del__), &unbound);
    if (del != NULL) {
        res = call_unbound_noarg(unbound, del, self);
        if (res == NULL)
            PyErr_WriteUnraisable(del);
        else
            Py_DECREF(res);
        Py_DECREF(del);
    }

    /* Restore the saved exception. */
    PyErr_SetRaisedException(exc);
}

// 调用del方法

lookup_maybe_method(PyObject *self, PyObject *attr, int *unbound)
{
    PyObject *res = _PyType_Lookup(Py_TYPE(self), attr);
    if (res == NULL) {
        return NULL;
    }

    if (_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
        /* Avoid temporary PyMethodObject */
        *unbound = 1;
        Py_INCREF(res);
    }
    else {
        *unbound = 0;
        descrgetfunc f = Py_TYPE(res)->tp_descr_get;
        if (f == NULL) {
            Py_INCREF(res);
        }
        else {
            res = f(res, self, (PyObject *)(Py_TYPE(self)));
        }
    }
    return res;
}
(Objects/typeobject.c)

FLSLOT(__init__, tp_init, slot_tp_init, (wrapperfunc)(void(*)(void))wrap_init,
           "__init__($self, /, *args, **kwargs)\n--\n\n"
           "Initialize self.  See help(type(self)) for accurate signature.",
           PyWrapperFlag_KEYWORDS),
    TPSLOT(__new__, tp_new, slot_tp_new, NULL,
           "__new__(type, /, *args, **kwargs)\n--\n\n"
           "Create and return new object.  See help(type) for accurate signature."),
    **TPSLOT(__del__, tp_finalize, slot_tp_finalize, (wrapperfunc)wrap_del, ""),**

    BUFSLOT(__buffer__, bf_getbuffer, slot_bf_getbuffer, wrap_buffer,
            "__buffer__($self, flags, /)\n--\n\n"
            "Return a buffer object that exposes the underlying memory of the object."),
    BUFSLOT(__release_buffer__, bf_releasebuffer, slot_bf_releasebuffer, wrap_releasebuffer,
            "__release_buffer__($self, buffer, /)\n--\n\n"
            "Release the buffer object that exposes the underlying memory of the object."),
                     
......

static void
slot_tp_finalize(PyObject *self)
{
    int unbound;
    PyObject *del, *res;

    /* Save the current exception, if any. */
    PyObject *exc = PyErr_GetRaisedException();

    /* Execute __del__ method, if any. */
    del = lookup_maybe_method(self, &_Py_ID(__del__), &unbound);
    if (del != NULL) {
        res = call_unbound_noarg(unbound, del, self);
        if (res == NULL)
            PyErr_WriteUnraisable(del);
        else
            Py_DECREF(res);
        Py_DECREF(del);
    }

    /* Restore the saved exception. */
    PyErr_SetRaisedException(exc);
}

// 调用del方法

lookup_maybe_method(PyObject *self, PyObject *attr, int *unbound)
{
    PyObject *res = _PyType_Lookup(Py_TYPE(self), attr);
    if (res == NULL) {
        return NULL;
    }

    if (_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
        /* Avoid temporary PyMethodObject */
        *unbound = 1;
        Py_INCREF(res);
    }
    else {
        *unbound = 0;
        descrgetfunc f = Py_TYPE(res)->tp_descr_get;
        if (f == NULL) {
            Py_INCREF(res);
        }
        else {
            res = f(res, self, (PyObject *)(Py_TYPE(self)));
        }
    }
    return res;
}

在 CPython 中,slotdefs 数组用于将 Python 的特殊方法(如 __getitem____add__ 等)映射到类型对象(PyTypeObject)的相应槽位(slots)。注册过程发生在类型创建时,具体步骤如下:

/* 伪代码:简化版类型创建流程 */
PyObject* type_new(...) {
    // 1. 创建类型对象
    PyTypeObject *type = ...;
    
    // 2. 遍历 slotdefs 数组
    for (PySlotDef *slot = slotdefs; slot->name; slot++) {
        // 3. 在类字典中查找特殊方法
        PyObject *method = _PyDict_GetItemId(class_dict, slot->name);
        if (method) {
            // 4. 将方法绑定到槽位
            int res = _PyType_SetSlotFromSpec(type, slot, method);
            // 处理错误...
        }
    }
}
/* Cpython里实际代码 */
/* Update the slots after assignment to a class (type) attribute. */
static int
update_slot(PyTypeObject *type, PyObject *name)
{
    pytype_slotdef *ptrs[MAX_EQUIV];
    pytype_slotdef *p;
    pytype_slotdef **pp;
    int offset;

    assert(PyUnicode_CheckExact(name));
    assert(PyUnicode_CHECK_INTERNED(name));

    pp = ptrs;
    for (p = slotdefs; p->name; p++) {
        assert(PyUnicode_CheckExact(p->name_strobj));
        assert(PyUnicode_CHECK_INTERNED(p->name_strobj));
        assert(PyUnicode_CheckExact(name));
        /* bpo-40521: Using interned strings. */
        if (p->name_strobj == name) {
            *pp++ = p;
        }
    }
    *pp = NULL;
    for (pp = ptrs; *pp; pp++) {
        p = *pp;
        offset = p->offset;
        while (p > slotdefs && (p-1)->offset == offset)
            --p;
        *pp = p;
    }
    if (ptrs[0] == NULL)
        return 0; /* Not an attribute that affects any slots */
    return update_subclasses(type, name,
                             update_slots_callback, (void *)ptrs);
}
  • 核心函数,负责:

    1. 验证方法签名

    2. 使用包装函数 将 Python 方法转为 C 可调用形式

    3. 将函数指针赋给类型槽位(如 type->tp_as_sequence->sq_item

  • 包装函数的作用(如 wrap_binaryfunc

    将 Python 层面的函数调用适配到底层 C 接口:

static PyObject* wrap_binaryfunc(PyObject *self, PyObject *args) {
    // 解包参数,调用用户定义的 __getitem__ 等
}

CPython 通过 slotdefs 数组在类型创建阶段自动完成特殊方法到槽位的注册:

  1. 遍历 slotdefs 匹配类字典中的方法

  2. 使用包装函数桥接 Python/C 接口

  3. 填充类型对象的槽位函数指针

  4. 为未定义的方法设置默认实现

这一机制使得 Python 层面的特殊方法能直接影响对象在解释器中的底层行为,是 CPython 实现面向对象特性的核心机制。

6.2.3 可达对象如何决策的

update_refs(PyGC_Head *containers)
{
    PyGC_Head *next;
    PyGC_Head *gc = GC_NEXT(containers);

    while (gc != containers) {
        next = GC_NEXT(gc);
        /* Move any object that might have become immortal to the
         * permanent generation as the reference count is not accurately
         * reflecting the actual number of live references to this object
         */
        if (_Py_IsImmortal(FROM_GC(gc))) {
           gc_list_move(gc, &get_gc_state()->permanent_generation.head);
           gc = next;
           continue;
        }
        gc_reset_refs(gc, Py_REFCNT(FROM_GC(gc)));
        /* Python's cyclic gc should never see an incoming refcount
         * of 0:  if something decref'ed to 0, it should have been
         * deallocated immediately at that time.
         * Possible cause (if the assert triggers):  a tp_dealloc
         * routine left a gc-aware object tracked during its teardown
         * phase, and did something-- or allowed something to happen --
         * that called back into Python.  gc can trigger then, and may
         * see the still-tracked dying object.  Before this assert
         * was added, such mistakes went on to allow gc to try to
         * delete the object again.  In a debug build, that caused
         * a mysterious segfault, when _Py_ForgetReference tried
         * to remove the object from the doubly-linked list of all
         * objects a second time.  In a release build, an actual
         * double deallocation occurred, which leads to corruption
         * of the allocator's internal bookkeeping pointers.  That's
         * so serious that maybe this should be a release-build
         * check instead of an assert?
         */
        _PyObject_ASSERT(FROM_GC(gc), gc_get_refs(gc) != 0);
        gc = next;
    }
}

将每个对象的 gc_refs 初始化为其 ob_refcnt(原始引用计数)。ob_refcnt 包含了来自 所有来源 的引用,包括:

栈上的变量(函数局部变量)
全局/静态变量
其他代(非当前回收代)的对象
非容器对象(如整数、字符串等)
这些来源共同构成了 GC 的根。
subtract_refs(base):
遍历当前代(base 链表)的对象,对每个对象引用的 其他同代对象,减少其 gc_refs。这相当于减去了 同代对象间的内部引用。
结果:
若对象最终 gc_refs > 0:表示存在 来自根(外部)的引用。

若 gc_refs = 0:对象仅被同代对象引用(形成孤岛),判定为不可达。
  • GC 的根roots 包括:

    • 栈上的变量(局部变量)

    • 全局/静态变量

    • 其他代的对象

    • 非容器对象

  • 这些根通过 ob_refcnt 的初始值 在 update_refs 中间接捕获,并在 subtract_refs 后通过 gc_refs > 0 体现。显式的根扫描(如遍历栈、全局区)发生在 GC 的其他阶段(如分代回收触发前)。

7. 引用计数机制

7.1 引用计数宏定义

// Include/object.h

// 增加引用计数
static inline void _Py_INCREF(PyObject *op)
{
#ifdef Py_REF_DEBUG
    _Py_RefTotal++;
#endif
    op->ob_refcnt++;
}

// 减少引用计数
static inline void _Py_DECREF(PyObject *op)
{
#ifdef Py_REF_DEBUG
    _Py_RefTotal--;
#endif
    if (--op->ob_refcnt != 0) {
        return;
    }
    _Py_Dealloc(op);  // 引用计数为0时销毁对象
}

#define Py_INCREF(op) _Py_INCREF(_PyObject_CAST(op))
#define Py_DECREF(op) _Py_DECREF(_PyObject_CAST(op))

7.2 对象销毁机制

// Objects/object.c
void
_Py_Dealloc(PyObject *op)
{
    destructor dealloc = Py_TYPE(op)->tp_dealloc;

#ifdef Py_TRACE_REFS
    _Py_ForgetReference(op);
#endif

    // 调用类型特定的析构函数
    (*dealloc)(op);
}

8. 内存布局总结

8.1 对象内存开销

对象类型 固定开销 可变部分 总大小示例
int (小) 28 bytes 0 28 bytes
int (大) 28 bytes 4*n bytes 28+4n bytes
str (ASCII) 49 bytes 1*n bytes 49+n bytes
str (Unicode) 80 bytes 4*n bytes 80+4n bytes
list 56 bytes 8*cap bytes 56+8*cap bytes
dict 240 bytes 24*n bytes 240+24n bytes

 

8.2 内存对齐和填充

// 内存对齐宏
#define _PyObject_SIZE(typeobj) ( (typeobj)->tp_basicsize )
#define _PyObject_VAR_SIZE(typeobj, nitems) \
    (size_t) \
    ( ( (typeobj)->tp_basicsize + \
        (nitems)*(typeobj)->tp_itemsize + \
        (SIZEOF_VOID_P-1) \
      ) & ~(SIZEOF_VOID_P-1) \
    )

9 执行流程

graph TD
    A[开始回收] --> B[合并年轻代]
    B --> C[标记可达对象]
    C --> D[分离不可达对象]
    D --> E[处理终结器对象]
    E --> F[处理弱引用]
    F --> G[执行__del__方法]
    G --> H[检查复活对象]
    H --> I[回收最终不可达对象]
    I --> J[处理不可回收对象]
    J --> K[更新统计信息]
    K --> L[结束]

基于镜像反射组件动态化实践

2025-5-5 评论(0) 分类:作品 Tags:

引言:反射困境

在Runtime动态化开发中,我们会遇到比如最简单一个log打印

fun Main() {    Log.i("test","打印")}

对应的DSL

[应用层]
   └── Test类

[编译层]
   └── 注解处理器
       ├── 导入指令解析
       │   ├── android.util.Log
       ├── 方法声明解析
       │   └── Main方法 (包含日志打印)
       └── 元数据处理
[运行时层]
   └── ComposeComponentRegistry
       ├── 组件注册表
       ├── 类型安全构建器
       └── 参数转换器

这种最简单的做法就是反射直接调用。这种第一个是运行时加载,对性能有影响,其次经常面临这样的场景:需要根据服务端下发的配置动态渲染UI组件。以布局属性Alignment.CenterHorizontally为例,传统反射方案会尝试:

然而这种常规反射手段在Compose框架中频繁遭遇ExceptionInInitializerError,其根本原因在于:

  1. 接口常量陷阱:Alignment本质是包含静态常量的接口(interface),其初始化机制与普通类不同
  2. 类加载死锁:Compose内部类之间存在复杂的初始化依赖链
  3. 混淆干扰:ProGuard规则可能导致反射路径断裂
  4. 性能损耗:传统反射API的调用效率难以满足我们对高频渲染需求

本文提出一种基于编译时反射代码生成的镜像反射方案,借鉴Dart的reflectable库思想,构建编译期静态反射体系:,通过脚本精简实现静态化的类型安全反射,在保持Compose组件特性的同时,实现高性能的运行时动态化能力。

镜像API允许程序反思自己。从历史上看,它起源于SELF,就像许多其他伟大的虚拟机技术一样,而我们Runtime机制本质上其实就是在做一个虚拟机的事情。如果你想了解更多关于镜像及其在其他系统中的作用,可以看Gilad Bracha的文章并跟随链接。

镜像的存在是为了回答反射性的问题,比如。”给定对象的类型是什么?”、”它有哪些字段/方法?”、”字段的类型是什么?”、”给定方法的参数的类型是什么?”以及执行反射性动作,如 “从该对象获取这个字段的值!”和 “在该对象上调用这个方法!”

一、技术方案全景图

1.1 架构分层

[应用层]
   └── @Reflectable注解标记

[编译层]
   └── KSP注解处理器
       ├── 元数据解析
       ├── 反射代码生成
       └── 混淆规则生成

[运行时层]
   └── ComposeComponentRegistry
       ├── 组件注册表
       ├── 类型安全构建器
       └── 参数转换器
@Reflectable
class CustomCard(val title: String)
public class CustomCard_ReflectProxy : ComponentProxy {
    override fun createInstance(args: Map<String, Any?>): Any {
        return CustomCard(
            title = args["title"] as String
        )
    }

    override val descriptor = ComponentDescriptor(
        name = "CustomCard",
        params = listOf(
            ParameterDescriptor("title", String::class)
        )
    )
}
组件注册表
object ComposeComponentRegistry {
    private val registry = ConcurrentHashMap<String, ComponentProxy>()

    fun register(proxy: ComponentProxy) {
        registry[proxy.descriptor.name] = proxy
    }

    fun createComponent(name: String, args: Map<String, Any?>): Any {
        return registry[name]?.createInstance(args)
            ?: throw ComponentNotFoundException(name)
    }
}
fun process(reflectableElements: List<KSClassDeclaration>) {
    reflectableElements.forEach { cls ->
        // 1. 元数据提取
        val properties = cls.getAllProperties()
        val functions = cls.getDeclaredFunctions()

        // 2. 校验约束
        validateReflectableConstraints(cls)

        // 3. 生成桥接类
        generateBridgeClass(cls, properties, functions)

        // 4. 生成映射规则
        generateProguardRules(cls)
    }
}

1.2 核心优势

  • 编译时确定性:所有反射逻辑在编译期展开为直接调用
  • 零反射开销:生成的代码与手写调用等效
  • 混淆免疫:自动生成匹配的keep规则

二、未来演进方向

  1. 跨模块热更新:支持动态加载组件镜像
  2. 动态生成:目前还是依赖于手动添加,一旦有API更细很容易遗漏,可以通过import的依赖信息自动导入,生成reflect中间类。
  3. IDE动态支持自测case:庞大的反射类需要更为细致的自测case来支持,这里可以使用通过大模型帮忙生成自测case时一个不错的选择。

最后

通过镜像反射技术的深度应用,我们成功将Compose组件的动态加载性能提升了一个数量级,同时解决了长期困扰Aether的加载系统类的导致的性能损耗的问题和健壮性问题。该方案在基于Runtime虚拟机场景落地,也为Aether动态化开辟了新的可能性边界。

最新的开源项目 – 跨端技术Aether

2025-4-29 评论(1) 分类:作品 Tags:

一、前沿

随着2023.11月份KMP的第一个stable版本发布,KMP相当于迈入了一个新的台阶,做为从Native到Flutter以及KMP的见证者,在多端渲染和一致性上,这么多年业内工程师做了很多努力,早期的weex,DynamicX,后来的flutter,蚂蚁的cube/鸟巢,字节自己的lynx,无论哪一种哪家都希望能从一套技术栈统一掉平台的差异,同时兼具动态化的能力,但是都没有根本的解决渲染损耗的问题。随着KMP的发布,至少对于Android侧是几乎无损的,给我带来非常大的欣喜,我决定去探索这门新的方向会给客户端带来什么?以及记录下我对KMP对行业的影响和思考。

笔者从2024年开始研究KMP的引入会对原有终端生态带来哪些影响,并且长期关注该技术如何能跟现有业务做一个联动,探索出一条不一样的技术路径,并坚信它可以对现有跨端能力进行一轮重构,目前基本链路已经跑通,起名“Aether”,物理学里认为它类似于光波声波这种介质传播,寓意能够兼具开放和云端传递动态的能力。

二、Aether的优势和劣势

Aether的初心是通过建立一个Android程序员不要再使用不优雅的js来实现动态化能力,我们希望能够提供完整的一套基于Kotlin的动态化的解决方案。我的思考是基于当前的KMP天然的跨端基础上,寻找到一种实现动态化的能力方案,对于Android工程师抛弃不优雅的js语法,真正意义上写kotlin就能完成动态化的能力。基于这个背景我开始了调研,过程中不断的尝试,攻克了一个又一个难点,完成初步目标。

2.1 核心目标

  • 保证通用性,能够保证KMP的跨能能力
  • 门槛低:足够的低成本上手:不希望对新手有很大的上手成本
  • 丰富的调试工具:希望能够在开发Aether卡片的时候有很多工具可以使用
  • 适用性:不能有很大的包大小的引入

2.2 技术选型

1. 利用JS引擎

JS局限性:Native测本身是具备解析js的环境,但是经过技术方案审慎评估,我们发现Native端基于JS动态执行环境构建DSL生成机制的技术路径,虽然可实现动态化但是存在显著的局限性,具体而言,该方案通过运行时动态构建包含页面逻辑和事件绑定的DSL,技术KMP进行组件化渲染,其核心实现原理虽然与RN技术存在相似性,但是本质上深度整合了KMP的框架体系。

从实现效果来看,该架构在动态化支持方面具备可行性,但是存在关键性的制约因素:首先,技术实现层面需要严格对其KMP的框架规范,导致js承担大量的适配工作,造成研发成本较高且开发体验折损,其次,技术栈依赖js的执行机制,在运行时频繁的触发KOTLIN与JS的语言通信(IPC),产生不可忽略的性能折损,这也是Weex,Lynx局限性,难以满足我们对高性能的要求,更为关键的是该方案强制开发者将现有的Kotlin的开发体系迁移至JS体系,导致Kotlin工具链的完整兼容性失效,这与我们的低成本上手背道而驰。

综上,从技术路径可行性、性能、以及对新手友好程度等三个维度存在系统性的挑战,我认为这不是最优解,我们尝试探索更多范式。

2. 生成Kotlin dsl

我们以平台的官方语音kotlin语法作为输入,通过转换层完整提取语义信息生成DSL,集合Kolin与KMP映射体系和基础逻辑运行时,实现纯Kolin环境下的组建渲染和交互闭环。

技术实现层面,该方案采用了Kotlin官方的KotlinCompiler库(已广泛应用在代码的格式化、java转Kotlin等工具链),其中标准化的API对Kotlin源码级别进行文件级别的AST抽象语法树构建,AST的数据结构系统化承载了代码的完整语义,为DSL的生成提供了结构化输入,整套编译阶段可以在容器内部完成也支持离线完成(优化加载速度),最终生成的这套DSL驱动KMP的渲染链路。

此架构设计具备三种技术价值:完成的保留了Kotlin的工具链和语法糖,消除了跨平台的适配成本、通过静态映射实现逻辑动态化、避免依赖JS引擎带来的运行时开销、最终在开发体验、性能指标和平台兼容性上都达到了工程化的最优解。

2.2 项目架构

  • 最下面的部分是编译依赖
  • 中间是转换阶段
  • 最上面是核心的runtime,runtime核心的是做一个类似于虚拟机的栈,保证所有的转换后的DSL映射后的对应数据,模拟成系统的栈结构。

三、困难

1. AST转换难点以及运行原理

AST意思是就是一个抽象的语法树,Kotlin的语法树与其他语言类似,但是还是存在差异,在Kotlin里,“org.jetbrains.kotlin.lexer.tokens”包含了所有的token。AST通过compiler解析语法树里的各个token值完成语法树的解析。

举个例子

object DemoFunctionExpress {    
  /**     * result =10     * result2 =10     * index = 5     */   
   fun test2(index: Int, item: String): Int { 
    val a = 2        
    val b = 2 + index        
    return a * b    
   }
}

 

如果不加以精简它是一个非常庞大的一个数据结构。

精简后。最后它的机构是类似于这种

KtFile (name: DemoFunctionExpress)
│
└── KtObjectDeclaration (name: DemoFunctionExpress)
    │
    └── KtNamedFunction (name: test2, parameters: [index: Int, item: String], returnType: Int)
        │
        ├── KtParameter (name: index, type: Int)
        ├── KtParameter (name: item, type: String)
        │
        ├── KtBlockExpression
        │   │
        │   ├── KtValVarKeyword (keyword: val)
        │   ├── KtNameIdentifier (name: a)
        │   ├── KtConstantExpression (value: 2)
        │   │
        │   ├── KtValVarKeyword (keyword: val)
        │   ├── KtNameIdentifier (name: b)
        │   ├── KtBinaryExpression (operator: +)
        │       ├── KtConstantExpression (value: 2)
        │       ├── KtNameReferenceExpression (name: index)
        │
        │   └── KtReturnExpression
        │       └── KtBinaryExpression (operator: *)
        │           ├── KtNameReferenceExpression (name: a)
        │           ├── KtNameReferenceExpression (name: b)

 

 

这个结构最外层就是一个progrem代表一个最小的程序单元,内层是嵌套一个KtObjectDeclaration,它表示一个函数的声明,再内部就是一个表达式和一些参数的声明,这个表达式最后其实就是一个将Kotlin的源码进行json化的一个转换。

生成上面的结构我们是依赖于KotlinCoreEnvirment的Visitor来进行解析,但是这个类的访问节点会遍历很多,所以我们我们需要对其进行精简,

举个例子,下面是一个二元运算的一个节点。

override fun visitBinaryExpression(   
 expression: KtBinaryExpression, data: Void?): Map<String, Any?>? {  
   println("Visiting binary expression: ${expression.text}")  
     super.visitBinaryExpression(expression, data)   
      return mapOf(     
         "type" to "BinaryExpression",       
          "name" to expression.name,      
          "operator" to (expression.operationToken as KtSingleValueToken).value,     
          "left" to visitNode(expression.left, data),        
          "right" to visitNode(expression.right, data),        
          "node" to expression,   
           )
          }

 

 

加载流程

当我们想要去加载上述的方法的时候的时候首先我们会将原文件加载到内存里,作为解释器的输入。

val test2 = runtime.invoke("test2", mutableListOf(5))

这一步我们是将AST转成DSL,然后我们会去遍历这个AST对应的所有的节点,然后精简成我们需要的DSL加载到内存里,这一步引擎会给他一个自定义的方法名并且与映射的方法产生一一对应的关系

它的执行流程简化来说可以是一个虚拟机的执行过程

  1. 在堆栈上查找名称为test2的方法
  2. 将参数分别压栈
  3. 遍历方法在是否在本单元内部->相邻类->系统类
  4. 找到后分别执行,退栈
  5. 返回结果

上述案例仅展示了 DSL 体系中的基础场景片段,实际应用需处理更复杂的 多维度逻辑嵌套,典型场景包括但不限于:

  1. 多层循环结构中的集合操作(如 O(n²) 级联 flatMap 与状态化 reduce 的复合逻辑)
  2. 异步回调链中的高阶条件分支
  3. 动态上下文依赖的 AST 节点生成(基于运行时元数据的子树构建)

受限于技术文档篇幅,本文未展开具体业务实现细节,但其核心机制具有一致性:

  • AST 增强型描述协议:在标准抽象语法树构建阶段,注入 上下文感知处理器(Context-Aware Processor)
  • 结构化数据组装:将转换后生成的 异构 Map 节点(Typed Map Node)按原始 AST 拓扑结构进行 递归归并(Recursive Merge),确保数据层级与源结构严格同构
  • 工业级序列化方案:通过 预编译型 JSON 序列化组件实现 AST 中间表示到标准化 JSON 的高性能转换,可以提高加载速度,可以作为优化项目。

当我输入5

回看上面的方法

object DemoFunctionExpress {    
  /**     * result =10     * result2 =10     * index = 5     */   
   fun test2(index: Int, item: String): Int { 
    val a = 2        
    val b = 2 + index        
    return a * b    
   }
}

 

输出结果

val test2 = runtime.invoke("test2", mutableListOf(5,"a"))
System.out.println("动态化引擎输出结果:" + test2)

上述只是一个简单的数据处理,实际企业级的应用不可能是是这么简单的逻辑处理,我们可能还有自定义的加载类或者使用系统类,如何去保证这类能够完全的动态化呢?

apply方法

需要针对静态方法和非静态方法分别处理

fun apply(
            method: AstMethod,
            positionalArguments: List<Any?>,
            namedArguments: Map<String, Any?>?
        ): Any? {
            if (method.isStatic) {
               xxxx
            } else {
                xxxx
                throw IllegalStateException("error instance")
            }
            return null
        }
    }

 

静态方法可以直接使用方法名压栈处理

fun scopedInvoke(            
       parameters: List<_ParameterImpl>?, 
       positionalArguments: List<Any?>,
       namedArguments: Map<String, Any?>?,
       invoke: (List<Any?>, Map<String, Any?>?) -> Any?): Any? {            
       val programStack = context.get<ProgramStack>(ProgramStack::class.java) ?: return null            
       programStack.push(name = "parameters scope")            
       parameters?.forEachIndexed { index, parameter ->                
         if (parameter.isNamed) {                    
           programStack.putVariable(parameter.simpleName,namedArguments?.get(parameter.simpleName))} else { programStack.putVariable( parameter.simpleName, positionalArguments.getOrNull(index))}}
           val result = invoke(positionalArguments, namedArguments)           
            programStack.pop()            
            return result }   
             }
           }

 

如果非静态方法,根据类的名称分别从相对路径自定义类或者系统类去加载对应的对象并且压栈

自定义类

对于自定义类,我们通过模拟类系统存储符号信息,在解析时将Kotlin的原始文件里的节点转换为可处理对象:属性声明转为Variable类型,方法声明转为不同类型的Function类型。

如果是相对路径重新生成对应的元信息node

if (newNode != null) {
        if (!dependencies.contains(newNode)) {
            dependencies.add(newNode)
            _visitNode(newEntity, dependencies, newNode)
        }
    } else {
        //反射

    }

在获取方法的时候通过加载刚刚自定义类的元数据完成

fun getClass(className: String): AstClass? {
    return context.run(
        name = "AstRuntime",
        body = {
            _program?.getClass(className)
        },
        overrides = mapOf(
            ProgramStack::class.java to { programStack },
            AstRuntime::class.java to { this }
        )
    ) as AstClass?
}
fun getClass(className: String, recursive: Boolean = true): AstClass? {
     //xxx
    dependencies.forEach {
        it.getClass(className, recursive = false)?.let { clazz -> return clazz }
    }
    return null
}

对于系统方法

最简单的就是反射传入类的方法和路径,精简反射的类的信息,通过getClass去构建一套基于系统的元数据结构。

经过层层拆解,最终还原一个精简版本的数据结构,用户下一步的处理

Scope上下文:

Scope 本质上承担着上下文环境的角色。当解释器对每个方法或表达式进行求值时,必须接收一个由外部传递的 Scope 参数作为执行基础。这种上下文对象具有链式传递特性:当前语句执行完成后,其可能修改过的 Scope 状态会成为下一条语句执行的输入参数。所以这里需要有一个跨线程的上下文,使用ThreadLocal维护,通过 Scope 的引用机制获取其存储位置,这种设计不仅支持数据属性的跨语句存取,还能实现方法定义的动态调用——包括方法内部嵌套调用其他已注册方法的场景。正是基于这种可传递、可扩展的上下文容器机制,系统才具备了处理用户自定义方法及其内部复杂逻辑的能力。

object AppContextManager {
   private val contextHolder = ThreadLocal.withInitial { AppContext(null, "ROOT", emptyMap(), emptyMap()) }
    // xxx
    val context: AppContext  get() = contextHolder.get()
    // 设置当前线程的 AppContext
    fun set(context: AppContext) {
        contextHolder.set(context)
    }
    fun get() :AppContext{
       return context
    }
    // 移除当前线程的 AppContext
    fun removeContext() {
        contextHolder.remove()
    }
}

context.run(
    name = " instance scope", body = {
        AstMethod.apply2(
            method,
            positionalArguments = mutableListOf(),
            namedArguments = mutableMapOf()
        )
    }, overrides = overrides
)
Card(
        elevation = CardDefaults.cardElevation(4.dp),
        modifier = Modifier.fillMaxWidth()
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(
                text = title,
                style = MaterialTheme.typography.headlineSmall,
                color = MaterialTheme.colorScheme.primary
            )
            Spacer(modifier = Modifier.height(4.dp))
            Text(
                text = content,
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.secondary
            )
        }
    }

 

它的结构简化下来就是如下样式,如果生成对应DSL更为庞大,这里就不展示了。

Compose Program
├── Composable Function: GreetingScreen
├── Composable Function: MessageCard
│   ├── Card 容器
│   │   ├── elevation 属性
│   │   └── Column 布局
│   │       ├── Text (标题)
│   │       │   ├── typography 样式
│   │       │   └── color 颜色
│   │       ├── Spacer
│   │       └── Text (内容)

简化流程:每一个组件都是对应的一个特定的语法树,语法树的解析,以来上文说的递归使用上面说的动态反射机制就能够还原这些View,

四、挑战

  1. 工作量巨大,需要保持耐心,笔者在业余时间进行探索,几乎已经全部时间进行投入。
  2. 项目较为复杂,核心链路虽然跑通了,但是一些复杂的语法还未支持,需要保持一定耐心去兼容,之所以开源出来也是希望依赖于社区同学一起投入进来。
  3. 因为牵扯到AST和DSL的转换和解析执行虚拟机的加载,对于排查问题有一定的门槛,边界异常逻辑处理需要后继者更为注意。

五、未来支撑的生态

虽然Aether的设计理念和效率上都有很大的技术创新和亮点,能够做到对于90%以上场景媲美原生,做到了真正的兼容性能与跨端和动态化的能力,并且整个链路最小单元已经跑通了,但是目前还是依赖于笔者一个人去开发,还缺乏一些脚手架,希望能够涵盖研发、调试、监控、测试、一体的建设。大图如下

六、展望未来

Aether 业内首次创新性基于AST + Runtime 构建KMP的动态化方案,实现逻辑页面动态下发,全流程覆盖开发至运维。提升发版效率与热修复能力,有效缓解KMP缺失的动态化,可大范围的推动Android KMP的生态发展,甚至可以革命掉现有Lynx和Weex的生态,但因为个人精力有限,还有很多工程化的能力需要建设,期待社区一起未来后续将强化复杂语法支持与生态建设,降低开发成本、优化体验并扩大业务覆盖;推进大前端融合,实现跨终端一致性体验。

我的第一篇blog

2025-4-29 评论(0) 分类:生活 Tags:

很开心能第一次在这里写blog,以前偶尔会在微信公众号去写一些,但会被身边熟人看到,希望能有自己的站点记录生活、记录青春,记录成长,终于在今天达成,希望能够坚持写下去。