graphics.gd FFI 性能突破

graphics.gd 把 Go→Engine 的 FFI 降到 8–44 ns/op

文本版 · 供搜索与朗读

graphics.gd FFI 性能突破 — 深度研究速查表

graphics.gd FFI 性能突破

Discussion #277 深度研究 · Go cgo / 三层优化栈 / 1.0 路径 · 一页纸速览

graphics.gd
GDExtension
cgo
Go 1.26
Godot 4.7
WebAssembly

⚡ 一句话总结

graphics.gd 把 Go→Engine 的 FFI 降到 8–44 ns/op

借 Go 1.26 移除 _Psyscall(−30% cgo 开销)+ 自研 ring buffer / leaf trampoline / object 表示重构三层栈

Go 1.26 + graphics.gd 全部自研优化 = arm64 method call 8.1 ns/op(快于 GDScript 的 12.8 ns/op)

Go 1.26 + ring batch = x86_64 void 23.3 ns/op(与 GDScript 持平,摊销后)

已超过 gdext (Rust) 的 safe mode(推断 ~50–100+ ns/op,独立数据未公开)

Web 路径仍慢 desktop 9–18 倍(return value 786 ns vs 44 ns)

1.0 阻碍不是 API 稳定性,是 runtime 稳定性(测试覆盖 + sample projects)

📊 关键数字(一眼看完)

benchmark 数字的精确读法

原帖 #277 含两组数字:x86_64 + arm64,对应不同硬件 / 优化状态

来源
硬件
MethodBind return
MethodBind void
GDScript
解读

原帖 #277 帖首
x86_64
44.17 ns
23.30 ns
23.10 ns
Go 1.25 末 / 1.26 初,部分优化

原帖 #277 帖末
arm64
8.114 ns
13.10 ns
12.84 ns
Go 1.26 + 全部自研优化(峰值)

原帖 #277 Web
wasm32
786.1 ns
209.2 ns
50.62 ns
Web 慢 desktop 9–18 倍

陷阱 ①:23.30 ns 是 ring batch 摊销后数字(紧循环),孤立调用紧跟返回值时实际接近 44 ns。

陷阱 ②:GDScript 是"用户感知最低延迟参照",不是 GDExtension FFI 理论极限(裸 extern "C" 约 4–10 ns)。

陷阱 ③:"Go ahead of safe Rust" 是定性宣称,Rust 同口径 ns/op 数字无公开数据。

Go 1.26 cgo 开销实测

单一 commit 7244e92 移除 _Psyscall 状态

Benchmark硬件BeforeAfterΔ

CgoCall-64EPYC 7B1343.6935.83−17.99%

CgoCall-8Apple M128.5519.02−33.40%

CgoCallWithCallback-8M172.7657.38−21.14%

Syscall-8M1195.6178.1−8.95%

~30% 是跨硬件几何均值,单硬件在 18%–33% 间浮动。

FFI 跨语言对比

不同 binding 在不同条件下的 FFI 实测

路径ns/op

Rust extern "C" 裸 FFI4–10

gdext builtin_ffi(缓存后)9–10

Go 1.26 cgo(arm64 全优化)8.1

GDScript call12.8

gdext utilities_ffi(缓存后)30–32

graphics.gd MethodBind return44.17

gdext "safe mode"(推断)~50–100+

Go 1.26 cgo 与 Rust gdext 缓存 FFI 同数量级。

🏗️ 三层 FFI 优化栈

graphics.gd 自研三层栈(按调用热度分层)

2026 年 2 月 Splizard 10 天内全部上线

🥇 Tier 1 — Leaf Method(约 25% API)
jumponly.Call → 汇编 CALL PtrcallFn(零 cgo 切换)

🥈 Tier 2 — 主线程连续 void 调用
ring.Main.Buffer+Flush → 256 槽位批量执行,1 次 cgo

🥉 Tier 3 — 非 leaf、非主线程、需返回值
noescape.Call + cgocall → //go:noescape 跳过逃逸探测

协同关键:Jumponly 调用前检查 ring.Main.Pending()——需要返回值则先 flush 保序;void 则改走 noescape 保护 batching。

🥇 Ring Buffer Batching

internal/ring/ — 主线程专用 ring,size = 256

const Size = 256
type Entry struct {
Object uintptr; Method uintptr; Shape uint64
Args [256]byte // packed args
Result [64]byte // result slot
PC uintptr // caller PC for crash debug
}

Flush 触发:缓冲区满 / 需要返回值前 / 对象 Free 前

首次引入:eb7eef4 (2026-02-17)

🥇 Jumponly(Leaf Function 直跳)

internal/jumponly/ — 汇编 trampoline 跳过 cgo

TEXT ·call(SB), NOSPLIT, $0-32
MOVQ method+0(FP), DI ; RDI = method bind
MOVQ ·PtrcallFn(SB), AX ; gdext ptrcall 指针
CALL AX ; 直接 CALL,不经 cgocall
RET

检测:Clang LibTooling 静态分析(trivial_methods.cpp)

跳过:implicit、模板、ctor/dtor、virtual 方法

首次引入:b26bcf6 (2026-02-23, 667 文件)

🥇 Object 表示重构

[1]gdreference.Object + pointers.Generic[T,S]

type Generic[T, S] interface {
~struct {
_ [0]*T // phantom 防误转
sentinal uint64
revision revision // pinned/active/closed
checksum S // up to 24 bytes
}
Free()
}

收益:单元素数组 = 隐式 *T,逃逸分析可消除堆分配

API 影响:几乎无破坏,Advanced() 显式出现

🔬 cgo 内部机制

cgo 调用结构(Go ≤ 1.25)

runtime.cgocall → entersyscall → asmcgocall → exitsyscall

// Go 代码 C.foo()
└─ _Cfunc_foo // cgo 生成的 wrapper
└─ runtime.cgocall(fn, arg)
├─ mp.ncgocall++ // 计数
├─ entersyscall() // M 解绑 P,G→_Gsyscall,P→_Psyscall
├─ asmcgocall (汇编) // SP ← g0.stack.hi (2 MB 系统栈)
│ └─ CALL fn(arg) // 真 C 调用
├─ exitsyscall() // M CAS 抢回 P
└─ KeepAlive(fn/arg) // 防 GC

真正大头:一次 atomic Store(P → _Psyscall)+ 一次 atomic CAS(M 抢回 P)

g0 栈切换:相对便宜(2 MB 系统栈 vs 2–8 KB goroutine 栈)

Go 1.26 关键 commit

7244e9221ff25b0c93a13ad8f1aa8917ca50f697

作者:Michael Knyszek (Google)

CR:646198

变更:12 文件,+508/-312

本质:消除 _Psyscall 状态,改用 G 状态 _Gsyscall 同步

收益:删 atomic Store + atomic CAS

副作用:短暂窗口 G 在 _Grunning 但未持 P(author 自述 "somewhat risky")

commit message:"This removes an atomic Store and an atomic CAS on the syscall path, which reduces syscall and cgo overheads."

Go 1.20 → 1.26 cgo 优化累积

30% 单一由 1.26 commit 贡献

版本关键改动

1.20–1.23无

1.24#cgo noescape / nocallback(用户侧)

1.25Green Tea GC 实验

1.26eliminates _Psyscall(cgo −30%)

🌐 WebAssembly 路径

Web 路径:直桥不是 wasm2c,net 收益无法量化

原帖未提供桥前 Web 基线;直桥独立 commit 未锚定

数字:return 786.1 ns / void 209.2 ns / GDScript 50.62 ns(比 desktop 慢 9–18 倍)

机制:Go 以 //go:wasmexport gd_on_* 暴露 engine→Go 入口,省掉 JS trampoline、值装箱、64 位拆装

Go wasm 限制:GOOS=js GOARCH=wasm 无完整 cgo,graphics.gd 用生成式 ABI shim

threadsafe on web:commit c83a441 维护 mainG/frameG 二元状态,OS 单线程下区分帧 goroutine 与自由 goroutine

不要写"786ns 降到

👍 1

想参与讨论或点赞?登录后使用完整功能

讨论回复(0)

暂无回复,登录后可参与讨论

本文标签

合作

智谱 GLM-5 已上线

在智谱开放平台 BigModel.cn 打造 AI 应用。新一代旗舰模型 GLM-5 在推理、代码、智能体综合能力达到开源模型 SOTA。

领取 2000万 Tokens