D如何支持写障碍
原文
我开始思考编译器中的指针障碍
.不把它们看作新类型
,而是当作类似切片
中的增加了安全性和功能
的检查区间
,但在特例下,如果必要
,可在本地和全局
绕过它们.
你仍然拥有今天D所拥有的指针
.唯一
区别是添加了一种绕过GC
写障碍的方法,可能类似:
core.memory.__raw(T)
//
struct __raw(T) {
T ptr;
alias ptr this;
auto opAssign(T rhs) { /*绕过边界检查*/ }
}
注意别名 本
.如果真有
这样隐式
转换回普通
指针的指针.
另一方面,构造
时,总是显式绕过检查.唯一工作
是表明你想绕过正常检查
,甚至可类似
slice.ptr[0]
在本地
绕过边界检查
.你不必真正用它;严格说是一种可选的性能增强,用于就像可绕过边界检查
一样的特例
.
编译器中,同样类似边界检查
,可添加:
-barrier=[none|writes]
命令行开关来全局
控制它.如果没有编译
屏障,不能用一些GC
实现.
屏障
自身被转发给druntime
函数或llvm
内部函数,或适合编译器和垃集
的函数(这会使运行时
交换出收集器
成为额外性能损失,因为屏障也是函数指针
,但这是合理决定).
函数像:
core.memory.__ptr_write(void** where, void* what)
因此,如果实现有最大
灵活性.用内部函数和内联
,当关闭
屏障时,会化简为:
mov [where], what;
注意:
1,因为从未写入常指针
,它不需要写屏障
.
const T* a; a = something;
在前端
不需要特别注意,因为编译不过.
2,自赋值
指针,
int* a; a++;
int[] a;
a = a[1 .. $];
也不需要写屏障
.因为目的是保护改变GC
行为的竞争条件,而GC
只关心它指向的块,这些操作本身禁止
改变它指向的块.如果切片
越界,则通不过
检查,甚至根据C
规则,原始指针
超出原始赋值块
也是未定义行为.
只需确保是原子
的替换实际
指针;确保实际生成inc ptr
,而不是读-改-写
.我肯定
编译器已这样做了,但必须指定
来确保.
编译器
前端可闲着
,因为后端
可检测到这些自修改
并消除检查.
我想,即即使启用它,很多D函数
也根本不会生成障碍
除非遗漏了些重点.你重新赋值
指针到另一块
时,就会生成障碍
.
3,也可消除借用引用
的障碍,因为你知道GC
会看到它在其他地方的规范引用
.关键是:写操作
会影响GC
在运行过程中的行为吗?如果是,则需要保护它.如果不是,可跳过它.安全最好;不需要的额外障碍
对性能会造成很小影响
,而缺少需要的障碍
则是严重
运行时错误.
4,省略了保守的栈扫描
,和复制/移动
的GC
实现.但对世代GC
和大多数增量和并发GC
的实现足够了.当然,需要命令行开关
来生成更多代码
,来支持其他方案,但是我勾画出可能工作的最简单的.
5,上面都仅适用直接
写至指针
,而不是通过指针
写.
char* a;
a = x; // 有写障
*a = x; // 没有.
指针值
变化才是重要
的,GC
不关心随机字符值
,只关心引用和未引用
哪些内存块.
不同GC
实现的好处:
当前,GC
标记阶段停止所有线程
,这样就不会竞争
,当它标记时,如果GC
在其他
地方工作时,它会变化.有了写障
,除非实际写指针
,可避免停止线程
.
假设音频
线程正在消耗GC
资源,但只写入到现有的ushort[]
缓冲区,它的活动永远不会使GC
工作进程失效
,所以可继续运行音频
线程.这不需要显式
用户工作,也不需要注销
它.
另一个
工作线程也许正在处理
一些数据,然后发送缓冲
到一个函数
.根据借用
指针是否管用,它可能一直运行,但是更简单的
,更少漏洞
的实现
会让它完成
处理数据,然后只在复制缓冲
指针到函数
时才因为触发写屏障
而真正停止
.到那时,GC
可能几乎完成了,即实际
停止时间比当前
实现要小得多.
写障碍
是完全的净收入
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现