We are looking for someone who: - Has experience in C# (.NET 8+, ASP.NET Core) - Enjoys and has experience building RESTful APIs, preferably having built APIs from scratch - Has experience with Agile/Scrum - Has experience with CI/CD tools to get projects to production - Has experience applying good coding principles, testing and backend system architecture design - Has experience with SQL databases; familiarity with MySQL, MariaDB or PostgreSQL is a plus - Experience with Kubernetes and MS Azure hosting is a strong advantage - Annual Bonus: A well-deserved reward for your contributions. - Hybrid Working: Enjoy the flexibility to balance office and WFH. - 6 Weeks Working from Abroad: Take your work to inspiring locations around the world. - 25 Paid Days Off: Plus the option to buy more. 地点 Amsterdam 可以从中国之间请,不能远程 全英文交流,这个必须 有意就请发英文简历到 am9obmF3ZXNvbWUxN0BnbWFpbC5jb20= 顺便英语手写介绍一下情况,联系电话和方便联系的时间
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
我做了十几年游戏开发,从 Unity C# 做到 Unreal C++,后来又碰了不少自研引擎。去年开始深度用 Claude Code 和 Codex CLI 写代码——不是那种"帮我写个排序"的用法,是让它读整条渲染管线,跨十几个文件改逻辑,加物理调试工具,修多线程 bug 。 Claude Code 的质量确实能打。它会先读项目结构,搞清楚调用链,再动手改。改了之后跑编译验证,报错了自己查,查到再修,循环到过。Codex 补刀也准,尤其是 C++ 模板报错的时候,它能把那一大坨 error 日志翻译成人话,告诉我问题在哪一行。 但账单是真的顶不住。 Claude Code 的 Dynamic Workflows 很能打,但账单更狠 先说一下 Claude Code 的 Dynamic Workflows 是什么——不是"付费功能",是 Claude Code 内置的一套工作流执行系统。你写一个 .js 脚本,里面用 agent() 、 parallel() 、 pipeline() 、 consensus() 这些函数定义步骤,Claude Code 就会按你的流程自动调度——串行、并行、投票、验收,全自动跑。 举个例子,你想审查代码质量,可以这样写: parallel([ agent("扫描潜在 bug"), agent("检查安全漏洞"), agent("审查性能热点"), agent("评估可维护性"), ]); consensus([...], { strategy: "multi-lens" }); 四个 agent 并行扫代码,一个 consensus 节点汇总投票。一个简单的流程。 但这玩意儿一旦展开就是算力黑洞。一个 parallel 块 5 个 agent ,一个 pipeline 里 3 个阶段,每个阶段再并行 5 个—— 三层嵌套就是 75 个 agent 出来。 每个 agent 独立调 API 、读代码、推理、输出,一场下来几千次 API 调用。改个复杂点的功能,几十上百个 agent 不是夸张,是常态。 Claude Code 的 Dynamic Workflows 本身不收费。 但它是"多 agent = 多 token"的架构——agent 的规模和你承担的成本线性相关。你用 100 个 Claude agent 跑一个大型重构,不管 Anthropic 收不收费,token 账单都能把月预算打穿。 这才是核心矛盾: 多 agent 协作是刚需,但全程用 premium 模型养一支 agent 军团,成本根本兜不住。 免费模型到处都有,但没统一的管理方式 我手上有这些免费/低成本渠道的 key: GitHub Models :免费 playground access ,有速率限制,需要 GitHub token ( models:read scope ) Hugging Face Router :免费用户每月有 Inference Provider 积分 SambaNova Cloud :Free Tier ,不绑支付方式就能用,有日请求/Token 上限 Together AI :注册送试用额度 Groq :免费层,推理速度确实快 Gemini :Google 免费层 DeepSeek / Kimi :白菜价 NVIDIA NIM / OpenRouter / Mistral / Cerebras / Fireworks / Z.ai :各有免费或试用渠道 LLM7 / Kilo Gateway :无 key 渠道,开了就能用 本地的 Ollama / LM Studio / llama.cpp 渠道是不少。但每个都要单独注册、配 key 、记 env 。今天想用 Groq 切一下,翻半天邮件找 key ;明天想试 SambaNova 的 DeepSeek-V3.1 ,又得重新配一轮。 更关键的是: 你有了便宜模型,不等于它能写出好代码。 免费模型的单次回答质量,跟 Claude Code / Codex 比确实有差距——推理深度不够、上下文一长就跑偏、复杂重构容易翻车。所以很多人手里攒着一堆免费 key ,最后还是老老实实给 Claude 充钱。 我想解决的问题就一个: 用免费/便宜的模型,通过工作流编排,跑出跟 Claude Code 、Codex 一样质量的产出。 一个模型单打独斗不行,那让它当一条流水线里的一个工位——规划用便宜的、执行用便宜的、验证用便宜的,多个便宜模型并行交叉审查,质量差距靠结构和协作来弥合。 FreeUltraCode:把这些渠道统一管起来 FreeUltraCode 是个本地桌面应用( Tauri 2 + Rust , 源码在 GitHub 上 )。它做的事情很直接: 一个下拉菜单,切渠道。 底部 Channel 选择器里列出所有你配好的渠道。想用哪个点哪个,接下来的对话走那条线。注册三步:选 channel → 点"去注册"跳官网拿 key → 贴回来保存,状态变绿色 Ready 。 它不替你翻墙、不替你注册、不存你的 key 到任何服务器。所有配置、聊天记录、API key 全在你本地机器上。 关键是: 中间切模型,上下文保留。 前面聊的文件引用、中间结论、工具输出,换渠道之后还能接着用。不用重新喂上下文。 实际用法(游戏开发场景) 需求:"给这个第三人称角色控制器加上攀爬系统" 第一步 → 切 GitHub Models / Groq 让 AI 扫项目结构,找到 CharacterMovement 、Input 、Animation 三层 读相关代码,列出现有接口和需要改的地方 (免费模型干这些够了) 第二步 → 切 Claude Code / Codex 核心逻辑来了——状态机新增 Climbing 状态, 物理查询改 Raycast → CapsuleTrace , 动画蓝图加 BlendSpace (贵的模型做关键设计决策) 第三步 → 切 Together AI / DeepSeek 补测试用例、跑 Lint 、生成注释、写提交信息 (量大但简单,免费渠道并行跑) 第四步 → 切回 Claude Code 最终审查——walk through 所有改动,检查边界条件, 确认网络同步逻辑没漏 (把关必须用稳的) Free Auto:切渠道的脏活让工具干 上面说的是手动切渠道——你知道哪个模型适合当前任务,自己选。但有时候你不想管这些。比如凌晨两点跑 CI 挂了一个 linting 任务,你只想让随便一个免费渠道把事情做了,别来烦你。 这时候 FreeUltraCode 的 Auto 渠道 ( freecc:auto ,Channel 下拉菜单第一个选项)就上场了。它不是某个固定的上游 API——它是一个 智能路由器 : 你配好 20+ 个免费渠道的 key (愿意配几个配几个) 切到 Auto ,发请求 代理按顺序轮转尝试——哪个渠道先返回正常结果,用哪个 碰了 429 (限流)?自动跳过,等 30 秒冷却再重试 碰了 5xx (上游挂了)?标记故障,本轮不再尝试 所有渠道都挂了?返回 503 + 失败日志,告诉你谁挂了、为什么 连接超时有预算 ——每个渠道不会死等,超时就换下一个。成功的渠道天然排在前面(冷却状态清空),有问题的被推到队尾。 实际效果就是:发请求,等结果,渠道切换完全无感。你配了 8 个渠道,Auto 就是 8 个渠道的容灾池——一个挂了自动下一个顶上。 Auto 也可以固定模型。 比如你在 Settings 里给 Auto 设 model override 为 z-ai/glm-5.1 ,那无论 Auto 这次路由到 Groq 、Together 还是 DeepSeek ,都会要求上游跑同一个模型。适合你对模型效果有偏好的场景。 实际场景(游戏开发): 凌晨两点,CI 挂了,Claude Code 报了个 lint 错误。 你不在电脑前,但 FreeUltraCode 的定时任务还在跑。 Auto 渠道自动尝试: GitHub Models → 429 ,跳过,冷却 30s Groq → 正常,几分钟修完 (后面的 DeepSeek 、Together 、HuggingFace 根本不用动) 第二天早上起床,CI 绿了,commit 写好了。 你甚至不知道昨晚到底是 Groq 还是 DeepSeek 修的问题。 也不需要知道。 本地代理:不用改全局配置,多条线同时跑 市面上类似的工具有 cc-switch ,但它的做法是改 Claude Code 的全局环境变量——切一次渠道,改一轮 ANTHROPIC_BASE_URL 。这意味着 同一时间只能走一条线 ,而且是对全局生效的,你开两个终端窗口也全切过去了。 FreeUltraCode 没走这条路。它内置一个 Rust 写的本地反向代理,监听 127.0.0.1 ,按端口路径路由。你的 Claude Code 不用改任何配置,它以为自己还在跟 Anthropic 官方 API 说话,但实际上: Claude Code → 127.0.0.1:8766/ch/official → Anthropic 官方 Claude Code → 127.0.0.1:8766/ch/deepseek → DeepSeek Claude Code → 127.0.0.1:8766/ch/kimi → Kimi Claude Code → 127.0.0.1:8766/ch/auto → Free Auto 智能切换 每个渠道对应一个端口路径,互不干扰。 你可以同时开着官方 Claude 、DeepSeek 、Kimi 三个渠道的 Claude Code 会话。 代理在中间做 Anthropic ↔ OpenAI 协议互译,上游是 OpenAI 协议的( Groq 、Together 、DeepSeek ),代理帮你翻;上游本身就是 Anthropic 协议的( Kimi 、 Z.ai ),直接透传。 更关键的是: 同一个 Claude Code 会话里也能动态切渠道。 Claude Code 每次都从环境变量 ANTHROPIC_BASE_URL 读 API 地址——FreeUltraCode 的 gateway 在每次调用时动态注入这个值。这就意味着: 第一轮对话: DeepSeek 扫项目结构,找问题 → 便宜 第二轮对话: 切回 Claude 官方 → 精准修复 同一场会话,上下文全保留。 不需要重新开终端,不需要重喂文件引用和中间结论。DeepSeek 负责定位问题,Claude 官方负责动手改——各干各擅长的,成本可控。 所以对比 cc-switch 和 FreeUltraCode 的渠道切换方式: cc-switch FreeUltraCode 配置方式 改全局环境变量 Gateway + 端口转发,不改全局配置 同时多渠道 ❌ 同一时间只能一条线 ✅ 多终端不同渠道,互不干扰 同会话动态切换 ❌ 需要改配置重启 ✅ 每次调 API 动态注入 base URL 协议翻译 依赖上游兼容性 Rust 代理内置 Anthropic↔OpenAI 互译 /ultracode:用便宜模型跑出贵模型的质量 这就是 FreeUltraCode 里 /ultracode 干的事。一句话描述任务,自动生成执行方案,并行跑多个子 agent——规划、执行、审查、对抗验证、验收门——整条链路全走你配的免费渠道。 fuc ultracode "把武器系统的伤害计算从客户端移到服务端,处理好预测回滚" 六个内置策略自动选择:分类执行、并行合成、对抗验证、生成过滤、锦标赛、循环直到完成。 底层逻辑: 用结构化流水线替代单模型深度推理。 单个便宜模型做不好的事,让五个便宜模型分步骤干,互相审查,层层验收。成本加起来可能还是 Claude 单次调用的一个零头。 每次运行在 .fuc-run/<run-id>/ 下留完整日志:任务账本、事件流、裁决、最终结果。 技术栈 层 技术 桌面壳 Tauri 2 + Rust 前端 React 18 + Vite 5 + TypeScript 5 状态管理 Zustand 样式 Tailwind CSS 渠道代理 Rust tiny_http + ureq ,本地反向代理,Anthropic ↔ OpenAI 协议互译 存储 纯本地,不依赖任何服务端 适合谁 每天用 Claude Code / Codex 写代码,token 账单肉疼的 手上有好几个免费渠道的 key ,但切来切去配置麻烦的 知道哪些任务可以放便宜模型跑、哪些必须用贵的,想精细化省成本的 做游戏/图形/系统开发的——这类项目文件多、编译重、AI 调用量大 不适合偶尔问一句的轻度用户。轻度使用直接开终端跑 Claude Code 就够了,不需要套个壳。 当前默认模型(部分) 渠道 默认模型 费用模式 GitHub Models openai/gpt-4.1-mini 免费,需 GitHub token ,有速率限制 Hugging Face Router deepseek-ai/DeepSeek-V4-Pro 每月免费推理积分 SambaNova Cloud DeepSeek-V3.1 Free Tier ,不绑卡,有日限额 Together AI Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8 注册送试用额度 Kilo Gateway poolside/laguna-xs.2:free 无 key ,200 req/hr LLM7 codestral-latest 无 key ,100 req/hr 项目地址 GitHub: github.com/wellingfeng/FreeUltraCode Discord: discord.gg/2C9ptSEFG QQ 群:149523963 跑起来 cd app npm install npm run dev # Web → localhost:5173 npm run desktop # Tauri 桌面应用 Windows 直接双击仓库根目录的 run.bat 。
原文: Why I'm Building a Database Engine in C# 作者: Loïc Baumann 译者: liuliuliuliu 译注:本文翻译使用 AI+人工校对的方式完成 Typhon 是一个用 .NET 编写的嵌入式、持久化、支持 ACID 的数据库引擎,原生支持游戏服务器与实时仿真所熟悉的实体-组件-系统( ECS )范式。 它通过 MVCC 快照隔离提供完整事务安全,并以缓存行感知的存储、零拷贝访问和可配置持久化能力为基础,目标是达到亚微秒级延迟。 这将是一个系列文章:A Database That Thinks Like a Game Engine Why I'm Building a Database Engine in C#(本文) What Game Engines Know About Data That Databases Forgot Microsecond Latency in a Managed Language Deadlock-Free by Construction Three Durability Modes, One WAL Epoch-Based Page Cache (即将发布) 当我告诉别人我正在用 C# 构建一个 ACID 数据库引擎 时,第一反应总是如出一辙:“那 GC (垃圾回收)停顿怎么办?” 这是一个合情合理的问题。几乎没有人会在 .NET 中构建高性能数据库引擎。人们普遍认为,这类软件必须使用 C 、C++ 或 Rust 编写——托管语言基本上被排除在“微秒级延迟俱乐部”之外。 但在构建了 30 年实时 3D 引擎和系统软件之后,我还是选择了 C#。这个项目名为 Typhon :一个嵌入式 ACID 数据库引擎,目标是实现 1–2 微秒的事务提交。 选择它的原因可能会改变你对 C# 能力的看法。 反对 C# 的理由(让我们直面它吧) 在我阐述理由之前,让我诚实地列出所有反对选择 C# 的理由。这些是现实存在的担忧,而非“稻草人”谬论。 GC 是非确定性的 :它可以在任何时候暂停你的所有线程。对于一个承诺微秒级延迟的数据库引擎来说,一次 10 毫秒的 Gen2 (第 2 代)回收是灾难性的——这超出了你延迟预算的 10,000 倍。 你无法控制内存布局 :托管堆决定了对象的存放位置。GC 可以在压缩过程中移动它们。你无法保证 B+ 树节点位于缓存行边界上,也无法保证你的页面缓存缓冲区不会在事务中途被重新定位。 JIT (即时编译)预热是真实存在的 :对任何方法的第一次调用都要支付编译成本。在数据库引擎中,启动后的第一个事务不应该比稳定状态慢 100 倍。 虚函数分发和边界检查会增加开销 :每次数组访问都有隐藏的边界检查。每次接口调用都要经过虚函数表( vtable )。在处理数百万实体的热点循环中,这些纳秒级的开销会不断累积。 这些都是合理的问题。我不会假装它们不存在。但这就是大多数人忽略的一点: 现代 C# 对每一个问题都有对应的解决方案。 大多数人不知道的 C# 另一面 很多开发者熟悉的 C# 是类、垃圾回收和 LINQ 。但这只是语言的一半。.NET Runtime 团队在过去十年里逐步建立了另一面能力,而这部分看起来和许多人想象中的 C# 很不一样。 unsafe 提供接近 C 级别的控制 :原始指针、指针运算、栈上缓冲区的 stackalloc 、固定大小数组等。JIT 可以生成与 C 中相同类型的底层指令。 GCHandle.Alloc(Pinned) 可以让 GC 在关键位置变得不再相关 。开发者可以固定字节数组,让 GC 不移动它们。Typhon 的整个页缓存都是固定内存; GC 不触碰、不扫描、不移动它。对引擎核心而言,它就是一个固定地址上的原始字节区。 ref struct 可以消除热路径上的堆分配 。 ref struct 不能逃逸到堆,只能存在于栈上,并在作用域结束时消失。Typhon 的实体访问器 EntityRef 是一个 96 字节的 ref struct ,目标是零分配、零 GC 压力。 受约束泛型可以提供真正的单态化( Monomorphization ) 。当代码写成 where T : unmanaged 时,JIT 能为每个类型参数生成单独的原生代码路径。 sizeof(T) 会成为常量,无用分支会被消除。这接近 Rust 泛型带来的优化效果:不是运行时分派,而是编译期专门化。 硬件原语( Hardware intrinsics )是一等公民 。 System.Runtime.Intrinsics 提供 Vector256 、 Sse42.Crc32 、 BitOperations.TrailingZeroCount 等能力。它们对应 C/C++ 中可用的 SIMD 指令,并且带有运行时特性检测,可以在不支持相关指令的平台上回退。 [StructLayout(Explicit)] 则可以精确控制内存布局 :字段偏移、填充、大小等,你可以控制每一个字节。缓存行对齐、防止伪共享( False-sharing )、位打包( Bit-packing )——这些功能一应俱全。 这不是“C# 试图成为 C”,而是 C# 在其顶级的托管生态系统之上,提供了一个真正的系统级编程层。 Typhon 实际是什么样 硬件加速的 WAL 校验和 写入预写日志( WAL )的每个页面都需要 CRC32C 校验和。这是它在 C# 中的样子——直接按名字调用 CPU 指令: private static uint ComputePartial(uint crc, ReadOnlySpan<byte> data) { if (Sse42.X64.IsSupported) return ComputeSse42X64(crc, data); if (Sse42.IsSupported) return ComputeSse42X32(crc, data); if (ArmCrc32.Arm64.IsSupported) return ComputeArm64(crc, data); return ComputeSoftware(crc, data); } private static uint ComputeSse42X64(uint crc, ReadOnlySpan<byte> data) { ulong crc64 = crc; ref byte ptr = ref MemoryMarshal.GetReference(data); int offset = 0; int aligned = data.Length & ~7; while (offset < aligned) { // 编译为单条 x86 crc32 指令 crc64 = Sse42.X64.Crc32(crc64, Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref ptr, offset))); offset += 8; } uint crc32 = (uint)crc64; while (offset < data.Length) { crc32 = Sse42.Crc32(crc32, Unsafe.Add(ref ptr, offset)); offset++; } return crc32; } Sse42.X64.Crc32() 会编译成单条 x86 crc32 指令。运行时检测 CPU 能力,JIT 消除无用分支,最后执行的是 C 程序员会写出的同类机器代码,同时还保留了不支持 SSE4.2 时的自动回退。 结果: 每 8 KB 页面约 1.3 微秒 。 SIMD 数据块访问器 Typhon 的页缓存热路径使用一个 16 槽缓存,通过三层路径查找数据。 // === 超快路径:MRU (最近最常使用)检查 === var mru = _mruSlot; if (_pageIndices[mru] == pageIndex) { var headerOffset = pageIndex == 0 ? _rootHeaderOffset : _otherHeaderOffset; return (byte*)_baseAddresses[mru] + headerOffset + offset * _stride; } // === 快路径:通过 SIMD 搜索所有 16 个缓存插槽 === fixed (int* indices = _pageIndices) { var target = Vector256.Create(pageIndex); var v0 = Vector256.Load(indices); var mask0 = Vector256.Equals(v0, target).ExtractMostSignificantBits(); if (mask0 != 0) { var slot = BitOperations.TrailingZeroCount(mask0); return GetFromSlot(slot, pageIndex, offset, dirty); } var v1 = Vector256.Load(indices + 8); var mask1 = Vector256.Equals(v1, target).ExtractMostSignificantBits(); if (mask1 != 0) { var slot = 8 + BitOperations.TrailingZeroCount(mask1); return GetFromSlot(slot, pageIndex, offset, dirty); } } _pageIndices 是一个 fixed int[16] ,共 64 字节,刚好一个缓存行,并且以适合 SIMD 的方式排列。一次 Vector256.Equals 可以比较 8 个页索引。MRU 快路径覆盖“重复访问同一页”这个常见场景,对分支预测友好,成本接近于零。 零拷贝实体读取 EntityRef 是一个只存在于栈上的 ref struct ,大小为 96 字节,其中包含一个内联固定数组,用来缓存组件位置。 public unsafe ref struct EntityRef { internal readonly EntityId _id; // ... 其他元数据 private fixed int _locations[16]; // 内联组件数据块 ID [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref readonly T Read<T>(Comp<T> comp) where T : unmanaged { byte slot = _archetype.GetSlot(comp._componentTypeId); int chunkId = _locations[slot]; var table = _engineState.SlotToComponentTable[slot]; return ref _tx.ReadEcsComponentData<T>(table, chunkId); } } 这个 Read<T> 调用经历了:方法调用 → 插槽查找 → 数据块 ID → 页面缓存 → 指针算术 → 指向固定内存页面的 ref readonly T 。零拷贝,零分配,零 GC 介入。由于泛型约束是 where T : unmanaged ,JIT 知道确切布局,最终会编译成指针运算。 JIT 特化的哈希函数 Typhon 的哈希函数也利用了 JIT 。由于受约束泛型下的 sizeof(TKey) 是编译期常量,不需要的分支会被消除。 [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint ComputeHash<TKey>(TKey key) where TKey : unmanaged { if (sizeof(TKey) == 4) return FastHash32(Unsafe.As<TKey, uint>(ref key)); if (sizeof(TKey) == 8) return XxHash32_8Bytes(Unsafe.As<TKey, long>(ref key)); return XxHash32_Bytes((byte*)Unsafe.AsPointer(ref key), sizeof(TKey)); } 例如调用 ComputeHash<int>(42) 时,JIT 只会生成 4 字节路径,其余分支会被完全移除。这是真正的单态化( Monomorphization ),而非运行时分发( Runtime Dispatch )。 生产力论据 一个数据库引擎不仅仅只有热路径。在核心引擎周围包裹着庞大的基础设施:配置管理、结构化日志、遥测、依赖注入、测试、基准测试。 在 C 或 Rust 中,你需要自己构建其中的大部分,或者缝合质量参差不齐的库。 到了 .NET 里,这些能力已经是生产级且免费的: ILogger 和 OpenTelemetry 用于可观测性, BenchmarkDotNet 用于严谨的微基准测试,NUnit 用于测试, IConfiguration 用于配置。它们文档充分、互相兼容,并由 Microsoft 或经受过检验的开源社区维护。 对一个独立开发者来说,这是实际的竞争优势。我可以把时间花在并发原语和页缓存淘汰上,而不是重新发明日志框架。 核心在于内存布局,而非语言 这是我多年实时 3D 引擎开发经验教给我的洞察:数据库引擎的瓶颈在于内存访问模式,而非指令吞吐量。 在 Ryzen 7950X 上,一次 DRAM 缓存未命中大约需要 61 到 73 纳秒。这相当于 CPU 空等约 250 个周期。一次命中 L1 的 CAS 操作约 1.4 纳秒,两者比例约为 50:1 。 如果你的数据结构导致缓存未命中,无论你的语言有多少“零成本抽象”都救不了你。反之,如果你的数据布局对缓存友好——连续、对齐、访问模式可预测——那么语言几乎无关紧要。带有 unsafe 的 C# 在热点路径上生成的机器码与 C 语言相同。JIT 就是这么强大。 真正重要的是: 缓存行感知:Typhon 的 B+ 树节点是 128 字节——两个缓存行。Zen4 上的步进预取器会自动覆盖第二行。仅此一项就比 64 字节节点减少了 53% 的插入延迟和 30% 的查找延迟。 数据导向设计( DoD ): 采用 SOA (结构数组)而非 AOS (数组结构)。SIMD 友好的布局。仅使用可直接复制( Blittable )的类型。 最小化间接引用: 每次指针追踪都是一次潜在的缓存未命中。SIMD 数据块访问器的 MRU 命中完全避免了追踪。 因此,写什么语言远不如设计什么样的内存布局重要。 数据表现 所有测量均在 Ryzen 9 7950X 、.NET 10.0 、BenchmarkDotNet 、Release 配置下进行。 操作 延迟 吞吐 CRUD lifecycle MVCC ( spawn 、read 、update 、destroy 、commit ) 1.2 微秒 830K ops/sec 90 读 / 10 更新负载(每个事务 100 次操作,MVCC ) 22 微秒 约 4.5M entity-ops/sec B+Tree 查找(命中) 267 纳秒 3.7M ops/sec B+Tree 顺序扫描(每个 key ) 2.1 纳秒 479M keys/sec 无竞争锁获取 7.8 纳秒 128M ops/sec 页缓存命中 5.3 纳秒 - 作为参考,Zen4 上一次无竞争 CAS 约 1.4 纳秒;一次 DRAM 往返约 61 到 73 纳秒。Typhon 的锁获取是 7.8 纳秒,大约相当于 5 次 CAS ;考虑到它还处理共享/独占仲裁和等待者跟踪,这个结果已经很紧凑。267 纳秒的 B+Tree 查找意味着大约 6 到 7 次内存访问,符合穿过 L2/L3 缓存进行树遍历的预期。 这些仍然是早期 Alpha 版本的数据,还有优化空间。但它们验证了核心论点: C# 不是瓶颈 。 权衡 任何选择都有成本。以下是我对考虑走同样道路的人的建议。 内存安全要由开发者自己负责 。在 unsafe 代码块里,你可以破坏内存、解引用错误指针、写爆缓冲区,编译器不会拯救你。 Span<T> 是稍慢但完全安全的替代方案。 GC 目前还不是问题,但它可能成为问题 。通过固定页缓存,并在热路径使用 ref struct ,Gen2 回收既少又便宜。但这不保证任何场景都如此。如果某个工作负载在事务之间大量分配托管对象,暂停仍然可能出现。解决办法是纪律:不要在热路径上分配。语言允许你分配,但不会强迫你分配。 “但 Rust 会给你编译时安全。” ,没错,借用检查器可以捕获 unsafe C# 捕获不了的所有权和生命周期错误。但 C# 也有 Rust 没有的工具: Roslyn analyzers 。我编写了一套自定义分析器( TYPHON001–007 ),将特定领域的安全规则强制转化为编译器错误: [NoCopy] 特效和分析器:像 ChunkAccessor 这样的性能关键结构不能按值传递。如果忘记使用 ref ,编译器会报错。它给关键类型提供了类似 Rust move 语义的保证,但范围只覆盖真正需要的类型。 所有权跟踪:如果创建了 ChunkAccessor 或 Transaction 却没有释放,那就是编译错误,而不是运行时泄漏。analyzer 会通过赋值、返回值、 ref / out 参数追踪所有权转移,也可以用 [return: TransfersOwnership] 表达返回值所有权转移。 释放完整性:如果类型持有关键 disposable 字段,而 Dispose() 漏掉了它,或者存在提前返回导致字段没有释放,也会成为编译错误。 // 这在 Typhon 中是一个编译错误 —— TYPHON001 void Process(ChunkAccessor accessor) { ... } // ✗ 错误:必须通过 ref 传递 void Process(ref ChunkAccessor accessor) { ... } // ✓ OK 也就是说,C# 不会免费得到 Rust 的安全性。但可以构建精确适合领域的一组编译期规则。与 Rust 的借用检查器不同,这些规则在诊断中带有领域上下文:“会导致页面缓存死锁”比“值已在此处移动”更具可操作性。 此外,Rust 在周边基础设施(日志、DI 、配置、测试)方面的生态系统不如 .NET 成熟,作为独立开发者,我的开发速度至关重要。我选择了能让我发布更快的语言。 JIT 预热是真问题,但可以管理 。冷启动后的前几笔事务会更慢。对嵌入式引擎来说,这是可以接受的,因为宿主应用通常有自己的预热阶段。如果是服务端数据库,则应考虑分层编译或 AOT 。 下一步 在下一篇文章中,我将解释为什么 ACID 数据库引擎会从游戏引擎中借鉴存储架构——特别是实体-组件-系统( ECS )模式。游戏引擎和数据库在解决同一个基本问题:管理具有极端性能约束的结构化数据。它们只是演化出了完全不同的解决方案。
原文: Why I'm Building a Database Engine in C# 作者: Loïc Baumann 译者: liuliuliuliu 译注:本文翻译使用 AI+人工校对的方式完成 Typhon 是一个用 .NET 编写的嵌入式、持久化、支持 ACID 的数据库引擎,原生支持游戏服务器与实时仿真所熟悉的实体-组件-系统( ECS )范式。 它通过 MVCC 快照隔离提供完整事务安全,并以缓存行感知的存储、零拷贝访问和可配置持久化能力为基础,目标是达到亚微秒级延迟。 这将是一个系列文章:A Database That Thinks Like a Game Engine Why I'm Building a Database Engine in C#(本文) What Game Engines Know About Data That Databases Forgot Microsecond Latency in a Managed Language Deadlock-Free by Construction Three Durability Modes, One WAL Epoch-Based Page Cache (即将发布) 当我告诉别人我正在用 C# 构建一个 ACID 数据库引擎 时,第一反应总是如出一辙:“那 GC (垃圾回收)停顿怎么办?” 这是一个合情合理的问题。几乎没有人会在 .NET 中构建高性能数据库引擎。人们普遍认为,这类软件必须使用 C 、C++ 或 Rust 编写——托管语言基本上被排除在“微秒级延迟俱乐部”之外。 但在构建了 30 年实时 3D 引擎和系统软件之后,我还是选择了 C#。这个项目名为 Typhon :一个嵌入式 ACID 数据库引擎,目标是实现 1–2 微秒的事务提交。 选择它的原因可能会改变你对 C# 能力的看法。 反对 C# 的理由(让我们直面它吧) 在我阐述理由之前,让我诚实地列出所有反对选择 C# 的理由。这些是现实存在的担忧,而非“稻草人”谬论。 GC 是非确定性的 :它可以在任何时候暂停你的所有线程。对于一个承诺微秒级延迟的数据库引擎来说,一次 10 毫秒的 Gen2 (第 2 代)回收是灾难性的——这超出了你延迟预算的 10,000 倍。 你无法控制内存布局 :托管堆决定了对象的存放位置。GC 可以在压缩过程中移动它们。你无法保证 B+ 树节点位于缓存行边界上,也无法保证你的页面缓存缓冲区不会在事务中途被重新定位。 JIT (即时编译)预热是真实存在的 :对任何方法的第一次调用都要支付编译成本。在数据库引擎中,启动后的第一个事务不应该比稳定状态慢 100 倍。 虚函数分发和边界检查会增加开销 :每次数组访问都有隐藏的边界检查。每次接口调用都要经过虚函数表( vtable )。在处理数百万实体的热点循环中,这些纳秒级的开销会不断累积。 这些都是合理的问题。我不会假装它们不存在。但这就是大多数人忽略的一点: 现代 C# 对每一个问题都有对应的解决方案。 大多数人不知道的 C# 另一面 很多开发者熟悉的 C# 是类、垃圾回收和 LINQ 。但这只是语言的一半。.NET Runtime 团队在过去十年里逐步建立了另一面能力,而这部分看起来和许多人想象中的 C# 很不一样。 unsafe 提供接近 C 级别的控制 :原始指针、指针运算、栈上缓冲区的 stackalloc 、固定大小数组等。JIT 可以生成与 C 中相同类型的底层指令。 GCHandle.Alloc(Pinned) 可以让 GC 在关键位置变得不再相关 。开发者可以固定字节数组,让 GC 不移动它们。Typhon 的整个页缓存都是固定内存; GC 不触碰、不扫描、不移动它。对引擎核心而言,它就是一个固定地址上的原始字节区。 ref struct 可以消除热路径上的堆分配 。 ref struct 不能逃逸到堆,只能存在于栈上,并在作用域结束时消失。Typhon 的实体访问器 EntityRef 是一个 96 字节的 ref struct ,目标是零分配、零 GC 压力。 受约束泛型可以提供真正的单态化( Monomorphization ) 。当代码写成 where T : unmanaged 时,JIT 能为每个类型参数生成单独的原生代码路径。 sizeof(T) 会成为常量,无用分支会被消除。这接近 Rust 泛型带来的优化效果:不是运行时分派,而是编译期专门化。 硬件原语( Hardware intrinsics )是一等公民 。 System.Runtime.Intrinsics 提供 Vector256 、 Sse42.Crc32 、 BitOperations.TrailingZeroCount 等能力。它们对应 C/C++ 中可用的 SIMD 指令,并且带有运行时特性检测,可以在不支持相关指令的平台上回退。 [StructLayout(Explicit)] 则可以精确控制内存布局 :字段偏移、填充、大小等,你可以控制每一个字节。缓存行对齐、防止伪共享( False-sharing )、位打包( Bit-packing )——这些功能一应俱全。 这不是“C# 试图成为 C”,而是 C# 在其顶级的托管生态系统之上,提供了一个真正的系统级编程层。 Typhon 实际是什么样 硬件加速的 WAL 校验和 写入预写日志( WAL )的每个页面都需要 CRC32C 校验和。这是它在 C# 中的样子——直接按名字调用 CPU 指令: private static uint ComputePartial(uint crc, ReadOnlySpan<byte> data) { if (Sse42.X64.IsSupported) return ComputeSse42X64(crc, data); if (Sse42.IsSupported) return ComputeSse42X32(crc, data); if (ArmCrc32.Arm64.IsSupported) return ComputeArm64(crc, data); return ComputeSoftware(crc, data); } private static uint ComputeSse42X64(uint crc, ReadOnlySpan<byte> data) { ulong crc64 = crc; ref byte ptr = ref MemoryMarshal.GetReference(data); int offset = 0; int aligned = data.Length & ~7; while (offset < aligned) { // 编译为单条 x86 crc32 指令 crc64 = Sse42.X64.Crc32(crc64, Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref ptr, offset))); offset += 8; } uint crc32 = (uint)crc64; while (offset < data.Length) { crc32 = Sse42.Crc32(crc32, Unsafe.Add(ref ptr, offset)); offset++; } return crc32; } Sse42.X64.Crc32() 会编译成单条 x86 crc32 指令。运行时检测 CPU 能力,JIT 消除无用分支,最后执行的是 C 程序员会写出的同类机器代码,同时还保留了不支持 SSE4.2 时的自动回退。 结果: 每 8 KB 页面约 1.3 微秒 。 SIMD 数据块访问器 Typhon 的页缓存热路径使用一个 16 槽缓存,通过三层路径查找数据。 // === 超快路径:MRU (最近最常使用)检查 === var mru = _mruSlot; if (_pageIndices[mru] == pageIndex) { var headerOffset = pageIndex == 0 ? _rootHeaderOffset : _otherHeaderOffset; return (byte*)_baseAddresses[mru] + headerOffset + offset * _stride; } // === 快路径:通过 SIMD 搜索所有 16 个缓存插槽 === fixed (int* indices = _pageIndices) { var target = Vector256.Create(pageIndex); var v0 = Vector256.Load(indices); var mask0 = Vector256.Equals(v0, target).ExtractMostSignificantBits(); if (mask0 != 0) { var slot = BitOperations.TrailingZeroCount(mask0); return GetFromSlot(slot, pageIndex, offset, dirty); } var v1 = Vector256.Load(indices + 8); var mask1 = Vector256.Equals(v1, target).ExtractMostSignificantBits(); if (mask1 != 0) { var slot = 8 + BitOperations.TrailingZeroCount(mask1); return GetFromSlot(slot, pageIndex, offset, dirty); } } _pageIndices 是一个 fixed int[16] ,共 64 字节,刚好一个缓存行,并且以适合 SIMD 的方式排列。一次 Vector256.Equals 可以比较 8 个页索引。MRU 快路径覆盖“重复访问同一页”这个常见场景,对分支预测友好,成本接近于零。 零拷贝实体读取 EntityRef 是一个只存在于栈上的 ref struct ,大小为 96 字节,其中包含一个内联固定数组,用来缓存组件位置。 public unsafe ref struct EntityRef { internal readonly EntityId _id; // ... 其他元数据 private fixed int _locations[16]; // 内联组件数据块 ID [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref readonly T Read<T>(Comp<T> comp) where T : unmanaged { byte slot = _archetype.GetSlot(comp._componentTypeId); int chunkId = _locations[slot]; var table = _engineState.SlotToComponentTable[slot]; return ref _tx.ReadEcsComponentData<T>(table, chunkId); } } 这个 Read<T> 调用经历了:方法调用 → 插槽查找 → 数据块 ID → 页面缓存 → 指针算术 → 指向固定内存页面的 ref readonly T 。零拷贝,零分配,零 GC 介入。由于泛型约束是 where T : unmanaged ,JIT 知道确切布局,最终会编译成指针运算。 JIT 特化的哈希函数 Typhon 的哈希函数也利用了 JIT 。由于受约束泛型下的 sizeof(TKey) 是编译期常量,不需要的分支会被消除。 [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint ComputeHash<TKey>(TKey key) where TKey : unmanaged { if (sizeof(TKey) == 4) return FastHash32(Unsafe.As<TKey, uint>(ref key)); if (sizeof(TKey) == 8) return XxHash32_8Bytes(Unsafe.As<TKey, long>(ref key)); return XxHash32_Bytes((byte*)Unsafe.AsPointer(ref key), sizeof(TKey)); } 例如调用 ComputeHash<int>(42) 时,JIT 只会生成 4 字节路径,其余分支会被完全移除。这是真正的单态化( Monomorphization ),而非运行时分发( Runtime Dispatch )。 生产力论据 一个数据库引擎不仅仅只有热路径。在核心引擎周围包裹着庞大的基础设施:配置管理、结构化日志、遥测、依赖注入、测试、基准测试。 在 C 或 Rust 中,你需要自己构建其中的大部分,或者缝合质量参差不齐的库。 到了 .NET 里,这些能力已经是生产级且免费的: ILogger 和 OpenTelemetry 用于可观测性, BenchmarkDotNet 用于严谨的微基准测试,NUnit 用于测试, IConfiguration 用于配置。它们文档充分、互相兼容,并由 Microsoft 或经受过检验的开源社区维护。 对一个独立开发者来说,这是实际的竞争优势。我可以把时间花在并发原语和页缓存淘汰上,而不是重新发明日志框架。 核心在于内存布局,而非语言 这是我多年实时 3D 引擎开发经验教给我的洞察:数据库引擎的瓶颈在于内存访问模式,而非指令吞吐量。 在 Ryzen 7950X 上,一次 DRAM 缓存未命中大约需要 61 到 73 纳秒。这相当于 CPU 空等约 250 个周期。一次命中 L1 的 CAS 操作约 1.4 纳秒,两者比例约为 50:1 。 如果你的数据结构导致缓存未命中,无论你的语言有多少“零成本抽象”都救不了你。反之,如果你的数据布局对缓存友好——连续、对齐、访问模式可预测——那么语言几乎无关紧要。带有 unsafe 的 C# 在热点路径上生成的机器码与 C 语言相同。JIT 就是这么强大。 真正重要的是: 缓存行感知:Typhon 的 B+ 树节点是 128 字节——两个缓存行。Zen4 上的步进预取器会自动覆盖第二行。仅此一项就比 64 字节节点减少了 53% 的插入延迟和 30% 的查找延迟。 数据导向设计( DoD ): 采用 SOA (结构数组)而非 AOS (数组结构)。SIMD 友好的布局。仅使用可直接复制( Blittable )的类型。 最小化间接引用: 每次指针追踪都是一次潜在的缓存未命中。SIMD 数据块访问器的 MRU 命中完全避免了追踪。 因此,写什么语言远不如设计什么样的内存布局重要。 数据表现 所有测量均在 Ryzen 9 7950X 、.NET 10.0 、BenchmarkDotNet 、Release 配置下进行。 操作 延迟 吞吐 CRUD lifecycle MVCC ( spawn 、read 、update 、destroy 、commit ) 1.2 微秒 830K ops/sec 90 读 / 10 更新负载(每个事务 100 次操作,MVCC ) 22 微秒 约 4.5M entity-ops/sec B+Tree 查找(命中) 267 纳秒 3.7M ops/sec B+Tree 顺序扫描(每个 key ) 2.1 纳秒 479M keys/sec 无竞争锁获取 7.8 纳秒 128M ops/sec 页缓存命中 5.3 纳秒 - 作为参考,Zen4 上一次无竞争 CAS 约 1.4 纳秒;一次 DRAM 往返约 61 到 73 纳秒。Typhon 的锁获取是 7.8 纳秒,大约相当于 5 次 CAS ;考虑到它还处理共享/独占仲裁和等待者跟踪,这个结果已经很紧凑。267 纳秒的 B+Tree 查找意味着大约 6 到 7 次内存访问,符合穿过 L2/L3 缓存进行树遍历的预期。 这些仍然是早期 Alpha 版本的数据,还有优化空间。但它们验证了核心论点: C# 不是瓶颈 。 权衡 任何选择都有成本。以下是我对考虑走同样道路的人的建议。 内存安全要由开发者自己负责 。在 unsafe 代码块里,你可以破坏内存、解引用错误指针、写爆缓冲区,编译器不会拯救你。 Span<T> 是稍慢但完全安全的替代方案。 GC 目前还不是问题,但它可能成为问题 。通过固定页缓存,并在热路径使用 ref struct ,Gen2 回收既少又便宜。但这不保证任何场景都如此。如果某个工作负载在事务之间大量分配托管对象,暂停仍然可能出现。解决办法是纪律:不要在热路径上分配。语言允许你分配,但不会强迫你分配。 “但 Rust 会给你编译时安全。” ,没错,借用检查器可以捕获 unsafe C# 捕获不了的所有权和生命周期错误。但 C# 也有 Rust 没有的工具: Roslyn analyzers 。我编写了一套自定义分析器( TYPHON001–007 ),将特定领域的安全规则强制转化为编译器错误: [NoCopy] 特效和分析器:像 ChunkAccessor 这样的性能关键结构不能按值传递。如果忘记使用 ref ,编译器会报错。它给关键类型提供了类似 Rust move 语义的保证,但范围只覆盖真正需要的类型。 所有权跟踪:如果创建了 ChunkAccessor 或 Transaction 却没有释放,那就是编译错误,而不是运行时泄漏。analyzer 会通过赋值、返回值、 ref / out 参数追踪所有权转移,也可以用 [return: TransfersOwnership] 表达返回值所有权转移。 释放完整性:如果类型持有关键 disposable 字段,而 Dispose() 漏掉了它,或者存在提前返回导致字段没有释放,也会成为编译错误。 // 这在 Typhon 中是一个编译错误 —— TYPHON001 void Process(ChunkAccessor accessor) { ... } // ✗ 错误:必须通过 ref 传递 void Process(ref ChunkAccessor accessor) { ... } // ✓ OK 也就是说,C# 不会免费得到 Rust 的安全性。但可以构建精确适合领域的一组编译期规则。与 Rust 的借用检查器不同,这些规则在诊断中带有领域上下文:“会导致页面缓存死锁”比“值已在此处移动”更具可操作性。 此外,Rust 在周边基础设施(日志、DI 、配置、测试)方面的生态系统不如 .NET 成熟,作为独立开发者,我的开发速度至关重要。我选择了能让我发布更快的语言。 JIT 预热是真问题,但可以管理 。冷启动后的前几笔事务会更慢。对嵌入式引擎来说,这是可以接受的,因为宿主应用通常有自己的预热阶段。如果是服务端数据库,则应考虑分层编译或 AOT 。 下一步 在下一篇文章中,我将解释为什么 ACID 数据库引擎会从游戏引擎中借鉴存储架构——特别是实体-组件-系统( ECS )模式。游戏引擎和数据库在解决同一个基本问题:管理具有极端性能约束的结构化数据。它们只是演化出了完全不同的解决方案。
原文: Why I'm Building a Database Engine in C# 作者: Loïc Baumann 译者: liuliuliuliu 译注:本文翻译使用 AI+人工校对的方式完成 Typhon 是一个用 .NET 编写的嵌入式、持久化、支持 ACID 的数据库引擎,原生支持游戏服务器与实时仿真所熟悉的实体-组件-系统( ECS )范式。 它通过 MVCC 快照隔离提供完整事务安全,并以缓存行感知的存储、零拷贝访问和可配置持久化能力为基础,目标是达到亚微秒级延迟。 这将是一个系列文章:A Database That Thinks Like a Game Engine Why I'm Building a Database Engine in C#(本文) What Game Engines Know About Data That Databases Forgot Microsecond Latency in a Managed Language Deadlock-Free by Construction Three Durability Modes, One WAL Epoch-Based Page Cache (即将发布) 当我告诉别人我正在用 C# 构建一个 ACID 数据库引擎 时,第一反应总是如出一辙:“那 GC (垃圾回收)停顿怎么办?” 这是一个合情合理的问题。几乎没有人会在 .NET 中构建高性能数据库引擎。人们普遍认为,这类软件必须使用 C 、C++ 或 Rust 编写——托管语言基本上被排除在“微秒级延迟俱乐部”之外。 但在构建了 30 年实时 3D 引擎和系统软件之后,我还是选择了 C#。这个项目名为 Typhon :一个嵌入式 ACID 数据库引擎,目标是实现 1–2 微秒的事务提交。 选择它的原因可能会改变你对 C# 能力的看法。 反对 C# 的理由(让我们直面它吧) 在我阐述理由之前,让我诚实地列出所有反对选择 C# 的理由。这些是现实存在的担忧,而非“稻草人”谬论。 GC 是非确定性的 :它可以在任何时候暂停你的所有线程。对于一个承诺微秒级延迟的数据库引擎来说,一次 10 毫秒的 Gen2 (第 2 代)回收是灾难性的——这超出了你延迟预算的 10,000 倍。 你无法控制内存布局 :托管堆决定了对象的存放位置。GC 可以在压缩过程中移动它们。你无法保证 B+ 树节点位于缓存行边界上,也无法保证你的页面缓存缓冲区不会在事务中途被重新定位。 JIT (即时编译)预热是真实存在的 :对任何方法的第一次调用都要支付编译成本。在数据库引擎中,启动后的第一个事务不应该比稳定状态慢 100 倍。 虚函数分发和边界检查会增加开销 :每次数组访问都有隐藏的边界检查。每次接口调用都要经过虚函数表( vtable )。在处理数百万实体的热点循环中,这些纳秒级的开销会不断累积。 这些都是合理的问题。我不会假装它们不存在。但这就是大多数人忽略的一点: 现代 C# 对每一个问题都有对应的解决方案。 大多数人不知道的 C# 另一面 很多开发者熟悉的 C# 是类、垃圾回收和 LINQ 。但这只是语言的一半。.NET Runtime 团队在过去十年里逐步建立了另一面能力,而这部分看起来和许多人想象中的 C# 很不一样。 unsafe 提供接近 C 级别的控制 :原始指针、指针运算、栈上缓冲区的 stackalloc 、固定大小数组等。JIT 可以生成与 C 中相同类型的底层指令。 GCHandle.Alloc(Pinned) 可以让 GC 在关键位置变得不再相关 。开发者可以固定字节数组,让 GC 不移动它们。Typhon 的整个页缓存都是固定内存; GC 不触碰、不扫描、不移动它。对引擎核心而言,它就是一个固定地址上的原始字节区。 ref struct 可以消除热路径上的堆分配 。 ref struct 不能逃逸到堆,只能存在于栈上,并在作用域结束时消失。Typhon 的实体访问器 EntityRef 是一个 96 字节的 ref struct ,目标是零分配、零 GC 压力。 受约束泛型可以提供真正的单态化( Monomorphization ) 。当代码写成 where T : unmanaged 时,JIT 能为每个类型参数生成单独的原生代码路径。 sizeof(T) 会成为常量,无用分支会被消除。这接近 Rust 泛型带来的优化效果:不是运行时分派,而是编译期专门化。 硬件原语( Hardware intrinsics )是一等公民 。 System.Runtime.Intrinsics 提供 Vector256 、 Sse42.Crc32 、 BitOperations.TrailingZeroCount 等能力。它们对应 C/C++ 中可用的 SIMD 指令,并且带有运行时特性检测,可以在不支持相关指令的平台上回退。 [StructLayout(Explicit)] 则可以精确控制内存布局 :字段偏移、填充、大小等,你可以控制每一个字节。缓存行对齐、防止伪共享( False-sharing )、位打包( Bit-packing )——这些功能一应俱全。 这不是“C# 试图成为 C”,而是 C# 在其顶级的托管生态系统之上,提供了一个真正的系统级编程层。 Typhon 实际是什么样 硬件加速的 WAL 校验和 写入预写日志( WAL )的每个页面都需要 CRC32C 校验和。这是它在 C# 中的样子——直接按名字调用 CPU 指令: private static uint ComputePartial(uint crc, ReadOnlySpan<byte> data) { if (Sse42.X64.IsSupported) return ComputeSse42X64(crc, data); if (Sse42.IsSupported) return ComputeSse42X32(crc, data); if (ArmCrc32.Arm64.IsSupported) return ComputeArm64(crc, data); return ComputeSoftware(crc, data); } private static uint ComputeSse42X64(uint crc, ReadOnlySpan<byte> data) { ulong crc64 = crc; ref byte ptr = ref MemoryMarshal.GetReference(data); int offset = 0; int aligned = data.Length & ~7; while (offset < aligned) { // 编译为单条 x86 crc32 指令 crc64 = Sse42.X64.Crc32(crc64, Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref ptr, offset))); offset += 8; } uint crc32 = (uint)crc64; while (offset < data.Length) { crc32 = Sse42.Crc32(crc32, Unsafe.Add(ref ptr, offset)); offset++; } return crc32; } Sse42.X64.Crc32() 会编译成单条 x86 crc32 指令。运行时检测 CPU 能力,JIT 消除无用分支,最后执行的是 C 程序员会写出的同类机器代码,同时还保留了不支持 SSE4.2 时的自动回退。 结果: 每 8 KB 页面约 1.3 微秒 。 SIMD 数据块访问器 Typhon 的页缓存热路径使用一个 16 槽缓存,通过三层路径查找数据。 // === 超快路径:MRU (最近最常使用)检查 === var mru = _mruSlot; if (_pageIndices[mru] == pageIndex) { var headerOffset = pageIndex == 0 ? _rootHeaderOffset : _otherHeaderOffset; return (byte*)_baseAddresses[mru] + headerOffset + offset * _stride; } // === 快路径:通过 SIMD 搜索所有 16 个缓存插槽 === fixed (int* indices = _pageIndices) { var target = Vector256.Create(pageIndex); var v0 = Vector256.Load(indices); var mask0 = Vector256.Equals(v0, target).ExtractMostSignificantBits(); if (mask0 != 0) { var slot = BitOperations.TrailingZeroCount(mask0); return GetFromSlot(slot, pageIndex, offset, dirty); } var v1 = Vector256.Load(indices + 8); var mask1 = Vector256.Equals(v1, target).ExtractMostSignificantBits(); if (mask1 != 0) { var slot = 8 + BitOperations.TrailingZeroCount(mask1); return GetFromSlot(slot, pageIndex, offset, dirty); } } _pageIndices 是一个 fixed int[16] ,共 64 字节,刚好一个缓存行,并且以适合 SIMD 的方式排列。一次 Vector256.Equals 可以比较 8 个页索引。MRU 快路径覆盖“重复访问同一页”这个常见场景,对分支预测友好,成本接近于零。 零拷贝实体读取 EntityRef 是一个只存在于栈上的 ref struct ,大小为 96 字节,其中包含一个内联固定数组,用来缓存组件位置。 public unsafe ref struct EntityRef { internal readonly EntityId _id; // ... 其他元数据 private fixed int _locations[16]; // 内联组件数据块 ID [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref readonly T Read<T>(Comp<T> comp) where T : unmanaged { byte slot = _archetype.GetSlot(comp._componentTypeId); int chunkId = _locations[slot]; var table = _engineState.SlotToComponentTable[slot]; return ref _tx.ReadEcsComponentData<T>(table, chunkId); } } 这个 Read<T> 调用经历了:方法调用 → 插槽查找 → 数据块 ID → 页面缓存 → 指针算术 → 指向固定内存页面的 ref readonly T 。零拷贝,零分配,零 GC 介入。由于泛型约束是 where T : unmanaged ,JIT 知道确切布局,最终会编译成指针运算。 JIT 特化的哈希函数 Typhon 的哈希函数也利用了 JIT 。由于受约束泛型下的 sizeof(TKey) 是编译期常量,不需要的分支会被消除。 [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint ComputeHash<TKey>(TKey key) where TKey : unmanaged { if (sizeof(TKey) == 4) return FastHash32(Unsafe.As<TKey, uint>(ref key)); if (sizeof(TKey) == 8) return XxHash32_8Bytes(Unsafe.As<TKey, long>(ref key)); return XxHash32_Bytes((byte*)Unsafe.AsPointer(ref key), sizeof(TKey)); } 例如调用 ComputeHash<int>(42) 时,JIT 只会生成 4 字节路径,其余分支会被完全移除。这是真正的单态化( Monomorphization ),而非运行时分发( Runtime Dispatch )。 生产力论据 一个数据库引擎不仅仅只有热路径。在核心引擎周围包裹着庞大的基础设施:配置管理、结构化日志、遥测、依赖注入、测试、基准测试。 在 C 或 Rust 中,你需要自己构建其中的大部分,或者缝合质量参差不齐的库。 到了 .NET 里,这些能力已经是生产级且免费的: ILogger 和 OpenTelemetry 用于可观测性, BenchmarkDotNet 用于严谨的微基准测试,NUnit 用于测试, IConfiguration 用于配置。它们文档充分、互相兼容,并由 Microsoft 或经受过检验的开源社区维护。 对一个独立开发者来说,这是实际的竞争优势。我可以把时间花在并发原语和页缓存淘汰上,而不是重新发明日志框架。 核心在于内存布局,而非语言 这是我多年实时 3D 引擎开发经验教给我的洞察:数据库引擎的瓶颈在于内存访问模式,而非指令吞吐量。 在 Ryzen 7950X 上,一次 DRAM 缓存未命中大约需要 61 到 73 纳秒。这相当于 CPU 空等约 250 个周期。一次命中 L1 的 CAS 操作约 1.4 纳秒,两者比例约为 50:1 。 如果你的数据结构导致缓存未命中,无论你的语言有多少“零成本抽象”都救不了你。反之,如果你的数据布局对缓存友好——连续、对齐、访问模式可预测——那么语言几乎无关紧要。带有 unsafe 的 C# 在热点路径上生成的机器码与 C 语言相同。JIT 就是这么强大。 真正重要的是: 缓存行感知:Typhon 的 B+ 树节点是 128 字节——两个缓存行。Zen4 上的步进预取器会自动覆盖第二行。仅此一项就比 64 字节节点减少了 53% 的插入延迟和 30% 的查找延迟。 数据导向设计( DoD ): 采用 SOA (结构数组)而非 AOS (数组结构)。SIMD 友好的布局。仅使用可直接复制( Blittable )的类型。 最小化间接引用: 每次指针追踪都是一次潜在的缓存未命中。SIMD 数据块访问器的 MRU 命中完全避免了追踪。 因此,写什么语言远不如设计什么样的内存布局重要。 数据表现 所有测量均在 Ryzen 9 7950X 、.NET 10.0 、BenchmarkDotNet 、Release 配置下进行。 操作 延迟 吞吐 CRUD lifecycle MVCC ( spawn 、read 、update 、destroy 、commit ) 1.2 微秒 830K ops/sec 90 读 / 10 更新负载(每个事务 100 次操作,MVCC ) 22 微秒 约 4.5M entity-ops/sec B+Tree 查找(命中) 267 纳秒 3.7M ops/sec B+Tree 顺序扫描(每个 key ) 2.1 纳秒 479M keys/sec 无竞争锁获取 7.8 纳秒 128M ops/sec 页缓存命中 5.3 纳秒 - 作为参考,Zen4 上一次无竞争 CAS 约 1.4 纳秒;一次 DRAM 往返约 61 到 73 纳秒。Typhon 的锁获取是 7.8 纳秒,大约相当于 5 次 CAS ;考虑到它还处理共享/独占仲裁和等待者跟踪,这个结果已经很紧凑。267 纳秒的 B+Tree 查找意味着大约 6 到 7 次内存访问,符合穿过 L2/L3 缓存进行树遍历的预期。 这些仍然是早期 Alpha 版本的数据,还有优化空间。但它们验证了核心论点: C# 不是瓶颈 。 权衡 任何选择都有成本。以下是我对考虑走同样道路的人的建议。 内存安全要由开发者自己负责 。在 unsafe 代码块里,你可以破坏内存、解引用错误指针、写爆缓冲区,编译器不会拯救你。 Span<T> 是稍慢但完全安全的替代方案。 GC 目前还不是问题,但它可能成为问题 。通过固定页缓存,并在热路径使用 ref struct ,Gen2 回收既少又便宜。但这不保证任何场景都如此。如果某个工作负载在事务之间大量分配托管对象,暂停仍然可能出现。解决办法是纪律:不要在热路径上分配。语言允许你分配,但不会强迫你分配。 “但 Rust 会给你编译时安全。” ,没错,借用检查器可以捕获 unsafe C# 捕获不了的所有权和生命周期错误。但 C# 也有 Rust 没有的工具: Roslyn analyzers 。我编写了一套自定义分析器( TYPHON001–007 ),将特定领域的安全规则强制转化为编译器错误: [NoCopy] 特效和分析器:像 ChunkAccessor 这样的性能关键结构不能按值传递。如果忘记使用 ref ,编译器会报错。它给关键类型提供了类似 Rust move 语义的保证,但范围只覆盖真正需要的类型。 所有权跟踪:如果创建了 ChunkAccessor 或 Transaction 却没有释放,那就是编译错误,而不是运行时泄漏。analyzer 会通过赋值、返回值、 ref / out 参数追踪所有权转移,也可以用 [return: TransfersOwnership] 表达返回值所有权转移。 释放完整性:如果类型持有关键 disposable 字段,而 Dispose() 漏掉了它,或者存在提前返回导致字段没有释放,也会成为编译错误。 // 这在 Typhon 中是一个编译错误 —— TYPHON001 void Process(ChunkAccessor accessor) { ... } // ✗ 错误:必须通过 ref 传递 void Process(ref ChunkAccessor accessor) { ... } // ✓ OK 也就是说,C# 不会免费得到 Rust 的安全性。但可以构建精确适合领域的一组编译期规则。与 Rust 的借用检查器不同,这些规则在诊断中带有领域上下文:“会导致页面缓存死锁”比“值已在此处移动”更具可操作性。 此外,Rust 在周边基础设施(日志、DI 、配置、测试)方面的生态系统不如 .NET 成熟,作为独立开发者,我的开发速度至关重要。我选择了能让我发布更快的语言。 JIT 预热是真问题,但可以管理 。冷启动后的前几笔事务会更慢。对嵌入式引擎来说,这是可以接受的,因为宿主应用通常有自己的预热阶段。如果是服务端数据库,则应考虑分层编译或 AOT 。 下一步 在下一篇文章中,我将解释为什么 ACID 数据库引擎会从游戏引擎中借鉴存储架构——特别是实体-组件-系统( ECS )模式。游戏引擎和数据库在解决同一个基本问题:管理具有极端性能约束的结构化数据。它们只是演化出了完全不同的解决方案。
原文: Why I'm Building a Database Engine in C# 作者: Loïc Baumann 译者: liuliuliuliu 译注:本文翻译使用 AI+人工校对的方式完成 Typhon 是一个用 .NET 编写的嵌入式、持久化、支持 ACID 的数据库引擎,原生支持游戏服务器与实时仿真所熟悉的实体-组件-系统( ECS )范式。 它通过 MVCC 快照隔离提供完整事务安全,并以缓存行感知的存储、零拷贝访问和可配置持久化能力为基础,目标是达到亚微秒级延迟。 这将是一个系列文章:A Database That Thinks Like a Game Engine Why I'm Building a Database Engine in C#(本文) What Game Engines Know About Data That Databases Forgot Microsecond Latency in a Managed Language Deadlock-Free by Construction Three Durability Modes, One WAL Epoch-Based Page Cache (即将发布) 当我告诉别人我正在用 C# 构建一个 ACID 数据库引擎 时,第一反应总是如出一辙:“那 GC (垃圾回收)停顿怎么办?” 这是一个合情合理的问题。几乎没有人会在 .NET 中构建高性能数据库引擎。人们普遍认为,这类软件必须使用 C 、C++ 或 Rust 编写——托管语言基本上被排除在“微秒级延迟俱乐部”之外。 但在构建了 30 年实时 3D 引擎和系统软件之后,我还是选择了 C#。这个项目名为 Typhon :一个嵌入式 ACID 数据库引擎,目标是实现 1–2 微秒的事务提交。 选择它的原因可能会改变你对 C# 能力的看法。 反对 C# 的理由(让我们直面它吧) 在我阐述理由之前,让我诚实地列出所有反对选择 C# 的理由。这些是现实存在的担忧,而非“稻草人”谬论。 GC 是非确定性的 :它可以在任何时候暂停你的所有线程。对于一个承诺微秒级延迟的数据库引擎来说,一次 10 毫秒的 Gen2 (第 2 代)回收是灾难性的——这超出了你延迟预算的 10,000 倍。 你无法控制内存布局 :托管堆决定了对象的存放位置。GC 可以在压缩过程中移动它们。你无法保证 B+ 树节点位于缓存行边界上,也无法保证你的页面缓存缓冲区不会在事务中途被重新定位。 JIT (即时编译)预热是真实存在的 :对任何方法的第一次调用都要支付编译成本。在数据库引擎中,启动后的第一个事务不应该比稳定状态慢 100 倍。 虚函数分发和边界检查会增加开销 :每次数组访问都有隐藏的边界检查。每次接口调用都要经过虚函数表( vtable )。在处理数百万实体的热点循环中,这些纳秒级的开销会不断累积。 这些都是合理的问题。我不会假装它们不存在。但这就是大多数人忽略的一点: 现代 C# 对每一个问题都有对应的解决方案。 大多数人不知道的 C# 另一面 很多开发者熟悉的 C# 是类、垃圾回收和 LINQ 。但这只是语言的一半。.NET Runtime 团队在过去十年里逐步建立了另一面能力,而这部分看起来和许多人想象中的 C# 很不一样。 unsafe 提供接近 C 级别的控制 :原始指针、指针运算、栈上缓冲区的 stackalloc 、固定大小数组等。JIT 可以生成与 C 中相同类型的底层指令。 GCHandle.Alloc(Pinned) 可以让 GC 在关键位置变得不再相关 。开发者可以固定字节数组,让 GC 不移动它们。Typhon 的整个页缓存都是固定内存; GC 不触碰、不扫描、不移动它。对引擎核心而言,它就是一个固定地址上的原始字节区。 ref struct 可以消除热路径上的堆分配 。 ref struct 不能逃逸到堆,只能存在于栈上,并在作用域结束时消失。Typhon 的实体访问器 EntityRef 是一个 96 字节的 ref struct ,目标是零分配、零 GC 压力。 受约束泛型可以提供真正的单态化( Monomorphization ) 。当代码写成 where T : unmanaged 时,JIT 能为每个类型参数生成单独的原生代码路径。 sizeof(T) 会成为常量,无用分支会被消除。这接近 Rust 泛型带来的优化效果:不是运行时分派,而是编译期专门化。 硬件原语( Hardware intrinsics )是一等公民 。 System.Runtime.Intrinsics 提供 Vector256 、 Sse42.Crc32 、 BitOperations.TrailingZeroCount 等能力。它们对应 C/C++ 中可用的 SIMD 指令,并且带有运行时特性检测,可以在不支持相关指令的平台上回退。 [StructLayout(Explicit)] 则可以精确控制内存布局 :字段偏移、填充、大小等,你可以控制每一个字节。缓存行对齐、防止伪共享( False-sharing )、位打包( Bit-packing )——这些功能一应俱全。 这不是“C# 试图成为 C”,而是 C# 在其顶级的托管生态系统之上,提供了一个真正的系统级编程层。 Typhon 实际是什么样 硬件加速的 WAL 校验和 写入预写日志( WAL )的每个页面都需要 CRC32C 校验和。这是它在 C# 中的样子——直接按名字调用 CPU 指令: private static uint ComputePartial(uint crc, ReadOnlySpan<byte> data) { if (Sse42.X64.IsSupported) return ComputeSse42X64(crc, data); if (Sse42.IsSupported) return ComputeSse42X32(crc, data); if (ArmCrc32.Arm64.IsSupported) return ComputeArm64(crc, data); return ComputeSoftware(crc, data); } private static uint ComputeSse42X64(uint crc, ReadOnlySpan<byte> data) { ulong crc64 = crc; ref byte ptr = ref MemoryMarshal.GetReference(data); int offset = 0; int aligned = data.Length & ~7; while (offset < aligned) { // 编译为单条 x86 crc32 指令 crc64 = Sse42.X64.Crc32(crc64, Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref ptr, offset))); offset += 8; } uint crc32 = (uint)crc64; while (offset < data.Length) { crc32 = Sse42.Crc32(crc32, Unsafe.Add(ref ptr, offset)); offset++; } return crc32; } Sse42.X64.Crc32() 会编译成单条 x86 crc32 指令。运行时检测 CPU 能力,JIT 消除无用分支,最后执行的是 C 程序员会写出的同类机器代码,同时还保留了不支持 SSE4.2 时的自动回退。 结果: 每 8 KB 页面约 1.3 微秒 。 SIMD 数据块访问器 Typhon 的页缓存热路径使用一个 16 槽缓存,通过三层路径查找数据。 // === 超快路径:MRU (最近最常使用)检查 === var mru = _mruSlot; if (_pageIndices[mru] == pageIndex) { var headerOffset = pageIndex == 0 ? _rootHeaderOffset : _otherHeaderOffset; return (byte*)_baseAddresses[mru] + headerOffset + offset * _stride; } // === 快路径:通过 SIMD 搜索所有 16 个缓存插槽 === fixed (int* indices = _pageIndices) { var target = Vector256.Create(pageIndex); var v0 = Vector256.Load(indices); var mask0 = Vector256.Equals(v0, target).ExtractMostSignificantBits(); if (mask0 != 0) { var slot = BitOperations.TrailingZeroCount(mask0); return GetFromSlot(slot, pageIndex, offset, dirty); } var v1 = Vector256.Load(indices + 8); var mask1 = Vector256.Equals(v1, target).ExtractMostSignificantBits(); if (mask1 != 0) { var slot = 8 + BitOperations.TrailingZeroCount(mask1); return GetFromSlot(slot, pageIndex, offset, dirty); } } _pageIndices 是一个 fixed int[16] ,共 64 字节,刚好一个缓存行,并且以适合 SIMD 的方式排列。一次 Vector256.Equals 可以比较 8 个页索引。MRU 快路径覆盖“重复访问同一页”这个常见场景,对分支预测友好,成本接近于零。 零拷贝实体读取 EntityRef 是一个只存在于栈上的 ref struct ,大小为 96 字节,其中包含一个内联固定数组,用来缓存组件位置。 public unsafe ref struct EntityRef { internal readonly EntityId _id; // ... 其他元数据 private fixed int _locations[16]; // 内联组件数据块 ID [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref readonly T Read<T>(Comp<T> comp) where T : unmanaged { byte slot = _archetype.GetSlot(comp._componentTypeId); int chunkId = _locations[slot]; var table = _engineState.SlotToComponentTable[slot]; return ref _tx.ReadEcsComponentData<T>(table, chunkId); } } 这个 Read<T> 调用经历了:方法调用 → 插槽查找 → 数据块 ID → 页面缓存 → 指针算术 → 指向固定内存页面的 ref readonly T 。零拷贝,零分配,零 GC 介入。由于泛型约束是 where T : unmanaged ,JIT 知道确切布局,最终会编译成指针运算。 JIT 特化的哈希函数 Typhon 的哈希函数也利用了 JIT 。由于受约束泛型下的 sizeof(TKey) 是编译期常量,不需要的分支会被消除。 [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint ComputeHash<TKey>(TKey key) where TKey : unmanaged { if (sizeof(TKey) == 4) return FastHash32(Unsafe.As<TKey, uint>(ref key)); if (sizeof(TKey) == 8) return XxHash32_8Bytes(Unsafe.As<TKey, long>(ref key)); return XxHash32_Bytes((byte*)Unsafe.AsPointer(ref key), sizeof(TKey)); } 例如调用 ComputeHash<int>(42) 时,JIT 只会生成 4 字节路径,其余分支会被完全移除。这是真正的单态化( Monomorphization ),而非运行时分发( Runtime Dispatch )。 生产力论据 一个数据库引擎不仅仅只有热路径。在核心引擎周围包裹着庞大的基础设施:配置管理、结构化日志、遥测、依赖注入、测试、基准测试。 在 C 或 Rust 中,你需要自己构建其中的大部分,或者缝合质量参差不齐的库。 到了 .NET 里,这些能力已经是生产级且免费的: ILogger 和 OpenTelemetry 用于可观测性, BenchmarkDotNet 用于严谨的微基准测试,NUnit 用于测试, IConfiguration 用于配置。它们文档充分、互相兼容,并由 Microsoft 或经受过检验的开源社区维护。 对一个独立开发者来说,这是实际的竞争优势。我可以把时间花在并发原语和页缓存淘汰上,而不是重新发明日志框架。 核心在于内存布局,而非语言 这是我多年实时 3D 引擎开发经验教给我的洞察:数据库引擎的瓶颈在于内存访问模式,而非指令吞吐量。 在 Ryzen 7950X 上,一次 DRAM 缓存未命中大约需要 61 到 73 纳秒。这相当于 CPU 空等约 250 个周期。一次命中 L1 的 CAS 操作约 1.4 纳秒,两者比例约为 50:1 。 如果你的数据结构导致缓存未命中,无论你的语言有多少“零成本抽象”都救不了你。反之,如果你的数据布局对缓存友好——连续、对齐、访问模式可预测——那么语言几乎无关紧要。带有 unsafe 的 C# 在热点路径上生成的机器码与 C 语言相同。JIT 就是这么强大。 真正重要的是: 缓存行感知:Typhon 的 B+ 树节点是 128 字节——两个缓存行。Zen4 上的步进预取器会自动覆盖第二行。仅此一项就比 64 字节节点减少了 53% 的插入延迟和 30% 的查找延迟。 数据导向设计( DoD ): 采用 SOA (结构数组)而非 AOS (数组结构)。SIMD 友好的布局。仅使用可直接复制( Blittable )的类型。 最小化间接引用: 每次指针追踪都是一次潜在的缓存未命中。SIMD 数据块访问器的 MRU 命中完全避免了追踪。 因此,写什么语言远不如设计什么样的内存布局重要。 数据表现 所有测量均在 Ryzen 9 7950X 、.NET 10.0 、BenchmarkDotNet 、Release 配置下进行。 操作 延迟 吞吐 CRUD lifecycle MVCC ( spawn 、read 、update 、destroy 、commit ) 1.2 微秒 830K ops/sec 90 读 / 10 更新负载(每个事务 100 次操作,MVCC ) 22 微秒 约 4.5M entity-ops/sec B+Tree 查找(命中) 267 纳秒 3.7M ops/sec B+Tree 顺序扫描(每个 key ) 2.1 纳秒 479M keys/sec 无竞争锁获取 7.8 纳秒 128M ops/sec 页缓存命中 5.3 纳秒 - 作为参考,Zen4 上一次无竞争 CAS 约 1.4 纳秒;一次 DRAM 往返约 61 到 73 纳秒。Typhon 的锁获取是 7.8 纳秒,大约相当于 5 次 CAS ;考虑到它还处理共享/独占仲裁和等待者跟踪,这个结果已经很紧凑。267 纳秒的 B+Tree 查找意味着大约 6 到 7 次内存访问,符合穿过 L2/L3 缓存进行树遍历的预期。 这些仍然是早期 Alpha 版本的数据,还有优化空间。但它们验证了核心论点: C# 不是瓶颈 。 权衡 任何选择都有成本。以下是我对考虑走同样道路的人的建议。 内存安全要由开发者自己负责 。在 unsafe 代码块里,你可以破坏内存、解引用错误指针、写爆缓冲区,编译器不会拯救你。 Span<T> 是稍慢但完全安全的替代方案。 GC 目前还不是问题,但它可能成为问题 。通过固定页缓存,并在热路径使用 ref struct ,Gen2 回收既少又便宜。但这不保证任何场景都如此。如果某个工作负载在事务之间大量分配托管对象,暂停仍然可能出现。解决办法是纪律:不要在热路径上分配。语言允许你分配,但不会强迫你分配。 “但 Rust 会给你编译时安全。” ,没错,借用检查器可以捕获 unsafe C# 捕获不了的所有权和生命周期错误。但 C# 也有 Rust 没有的工具: Roslyn analyzers 。我编写了一套自定义分析器( TYPHON001–007 ),将特定领域的安全规则强制转化为编译器错误: [NoCopy] 特效和分析器:像 ChunkAccessor 这样的性能关键结构不能按值传递。如果忘记使用 ref ,编译器会报错。它给关键类型提供了类似 Rust move 语义的保证,但范围只覆盖真正需要的类型。 所有权跟踪:如果创建了 ChunkAccessor 或 Transaction 却没有释放,那就是编译错误,而不是运行时泄漏。analyzer 会通过赋值、返回值、 ref / out 参数追踪所有权转移,也可以用 [return: TransfersOwnership] 表达返回值所有权转移。 释放完整性:如果类型持有关键 disposable 字段,而 Dispose() 漏掉了它,或者存在提前返回导致字段没有释放,也会成为编译错误。 // 这在 Typhon 中是一个编译错误 —— TYPHON001 void Process(ChunkAccessor accessor) { ... } // ✗ 错误:必须通过 ref 传递 void Process(ref ChunkAccessor accessor) { ... } // ✓ OK 也就是说,C# 不会免费得到 Rust 的安全性。但可以构建精确适合领域的一组编译期规则。与 Rust 的借用检查器不同,这些规则在诊断中带有领域上下文:“会导致页面缓存死锁”比“值已在此处移动”更具可操作性。 此外,Rust 在周边基础设施(日志、DI 、配置、测试)方面的生态系统不如 .NET 成熟,作为独立开发者,我的开发速度至关重要。我选择了能让我发布更快的语言。 JIT 预热是真问题,但可以管理 。冷启动后的前几笔事务会更慢。对嵌入式引擎来说,这是可以接受的,因为宿主应用通常有自己的预热阶段。如果是服务端数据库,则应考虑分层编译或 AOT 。 下一步 在下一篇文章中,我将解释为什么 ACID 数据库引擎会从游戏引擎中借鉴存储架构——特别是实体-组件-系统( ECS )模式。游戏引擎和数据库在解决同一个基本问题:管理具有极端性能约束的结构化数据。它们只是演化出了完全不同的解决方案。
原文: Why I'm Building a Database Engine in C# 作者: Loïc Baumann 译者: liuliuliuliu 译注:本文翻译使用 AI+人工校对的方式完成 Typhon 是一个用 .NET 编写的嵌入式、持久化、支持 ACID 的数据库引擎,原生支持游戏服务器与实时仿真所熟悉的实体-组件-系统( ECS )范式。 它通过 MVCC 快照隔离提供完整事务安全,并以缓存行感知的存储、零拷贝访问和可配置持久化能力为基础,目标是达到亚微秒级延迟。 这将是一个系列文章:A Database That Thinks Like a Game Engine Why I'm Building a Database Engine in C#(本文) What Game Engines Know About Data That Databases Forgot Microsecond Latency in a Managed Language Deadlock-Free by Construction Three Durability Modes, One WAL Epoch-Based Page Cache (即将发布) 当我告诉别人我正在用 C# 构建一个 ACID 数据库引擎 时,第一反应总是如出一辙:“那 GC (垃圾回收)停顿怎么办?” 这是一个合情合理的问题。几乎没有人会在 .NET 中构建高性能数据库引擎。人们普遍认为,这类软件必须使用 C 、C++ 或 Rust 编写——托管语言基本上被排除在“微秒级延迟俱乐部”之外。 但在构建了 30 年实时 3D 引擎和系统软件之后,我还是选择了 C#。这个项目名为 Typhon :一个嵌入式 ACID 数据库引擎,目标是实现 1–2 微秒的事务提交。 选择它的原因可能会改变你对 C# 能力的看法。 反对 C# 的理由(让我们直面它吧) 在我阐述理由之前,让我诚实地列出所有反对选择 C# 的理由。这些是现实存在的担忧,而非“稻草人”谬论。 GC 是非确定性的 :它可以在任何时候暂停你的所有线程。对于一个承诺微秒级延迟的数据库引擎来说,一次 10 毫秒的 Gen2 (第 2 代)回收是灾难性的——这超出了你延迟预算的 10,000 倍。 你无法控制内存布局 :托管堆决定了对象的存放位置。GC 可以在压缩过程中移动它们。你无法保证 B+ 树节点位于缓存行边界上,也无法保证你的页面缓存缓冲区不会在事务中途被重新定位。 JIT (即时编译)预热是真实存在的 :对任何方法的第一次调用都要支付编译成本。在数据库引擎中,启动后的第一个事务不应该比稳定状态慢 100 倍。 虚函数分发和边界检查会增加开销 :每次数组访问都有隐藏的边界检查。每次接口调用都要经过虚函数表( vtable )。在处理数百万实体的热点循环中,这些纳秒级的开销会不断累积。 这些都是合理的问题。我不会假装它们不存在。但这就是大多数人忽略的一点: 现代 C# 对每一个问题都有对应的解决方案。 大多数人不知道的 C# 另一面 很多开发者熟悉的 C# 是类、垃圾回收和 LINQ 。但这只是语言的一半。.NET Runtime 团队在过去十年里逐步建立了另一面能力,而这部分看起来和许多人想象中的 C# 很不一样。 unsafe 提供接近 C 级别的控制 :原始指针、指针运算、栈上缓冲区的 stackalloc 、固定大小数组等。JIT 可以生成与 C 中相同类型的底层指令。 GCHandle.Alloc(Pinned) 可以让 GC 在关键位置变得不再相关 。开发者可以固定字节数组,让 GC 不移动它们。Typhon 的整个页缓存都是固定内存; GC 不触碰、不扫描、不移动它。对引擎核心而言,它就是一个固定地址上的原始字节区。 ref struct 可以消除热路径上的堆分配 。 ref struct 不能逃逸到堆,只能存在于栈上,并在作用域结束时消失。Typhon 的实体访问器 EntityRef 是一个 96 字节的 ref struct ,目标是零分配、零 GC 压力。 受约束泛型可以提供真正的单态化( Monomorphization ) 。当代码写成 where T : unmanaged 时,JIT 能为每个类型参数生成单独的原生代码路径。 sizeof(T) 会成为常量,无用分支会被消除。这接近 Rust 泛型带来的优化效果:不是运行时分派,而是编译期专门化。 硬件原语( Hardware intrinsics )是一等公民 。 System.Runtime.Intrinsics 提供 Vector256 、 Sse42.Crc32 、 BitOperations.TrailingZeroCount 等能力。它们对应 C/C++ 中可用的 SIMD 指令,并且带有运行时特性检测,可以在不支持相关指令的平台上回退。 [StructLayout(Explicit)] 则可以精确控制内存布局 :字段偏移、填充、大小等,你可以控制每一个字节。缓存行对齐、防止伪共享( False-sharing )、位打包( Bit-packing )——这些功能一应俱全。 这不是“C# 试图成为 C”,而是 C# 在其顶级的托管生态系统之上,提供了一个真正的系统级编程层。 Typhon 实际是什么样 硬件加速的 WAL 校验和 写入预写日志( WAL )的每个页面都需要 CRC32C 校验和。这是它在 C# 中的样子——直接按名字调用 CPU 指令: private static uint ComputePartial(uint crc, ReadOnlySpan<byte> data) { if (Sse42.X64.IsSupported) return ComputeSse42X64(crc, data); if (Sse42.IsSupported) return ComputeSse42X32(crc, data); if (ArmCrc32.Arm64.IsSupported) return ComputeArm64(crc, data); return ComputeSoftware(crc, data); } private static uint ComputeSse42X64(uint crc, ReadOnlySpan<byte> data) { ulong crc64 = crc; ref byte ptr = ref MemoryMarshal.GetReference(data); int offset = 0; int aligned = data.Length & ~7; while (offset < aligned) { // 编译为单条 x86 crc32 指令 crc64 = Sse42.X64.Crc32(crc64, Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref ptr, offset))); offset += 8; } uint crc32 = (uint)crc64; while (offset < data.Length) { crc32 = Sse42.Crc32(crc32, Unsafe.Add(ref ptr, offset)); offset++; } return crc32; } Sse42.X64.Crc32() 会编译成单条 x86 crc32 指令。运行时检测 CPU 能力,JIT 消除无用分支,最后执行的是 C 程序员会写出的同类机器代码,同时还保留了不支持 SSE4.2 时的自动回退。 结果: 每 8 KB 页面约 1.3 微秒 。 SIMD 数据块访问器 Typhon 的页缓存热路径使用一个 16 槽缓存,通过三层路径查找数据。 // === 超快路径:MRU (最近最常使用)检查 === var mru = _mruSlot; if (_pageIndices[mru] == pageIndex) { var headerOffset = pageIndex == 0 ? _rootHeaderOffset : _otherHeaderOffset; return (byte*)_baseAddresses[mru] + headerOffset + offset * _stride; } // === 快路径:通过 SIMD 搜索所有 16 个缓存插槽 === fixed (int* indices = _pageIndices) { var target = Vector256.Create(pageIndex); var v0 = Vector256.Load(indices); var mask0 = Vector256.Equals(v0, target).ExtractMostSignificantBits(); if (mask0 != 0) { var slot = BitOperations.TrailingZeroCount(mask0); return GetFromSlot(slot, pageIndex, offset, dirty); } var v1 = Vector256.Load(indices + 8); var mask1 = Vector256.Equals(v1, target).ExtractMostSignificantBits(); if (mask1 != 0) { var slot = 8 + BitOperations.TrailingZeroCount(mask1); return GetFromSlot(slot, pageIndex, offset, dirty); } } _pageIndices 是一个 fixed int[16] ,共 64 字节,刚好一个缓存行,并且以适合 SIMD 的方式排列。一次 Vector256.Equals 可以比较 8 个页索引。MRU 快路径覆盖“重复访问同一页”这个常见场景,对分支预测友好,成本接近于零。 零拷贝实体读取 EntityRef 是一个只存在于栈上的 ref struct ,大小为 96 字节,其中包含一个内联固定数组,用来缓存组件位置。 public unsafe ref struct EntityRef { internal readonly EntityId _id; // ... 其他元数据 private fixed int _locations[16]; // 内联组件数据块 ID [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref readonly T Read<T>(Comp<T> comp) where T : unmanaged { byte slot = _archetype.GetSlot(comp._componentTypeId); int chunkId = _locations[slot]; var table = _engineState.SlotToComponentTable[slot]; return ref _tx.ReadEcsComponentData<T>(table, chunkId); } } 这个 Read<T> 调用经历了:方法调用 → 插槽查找 → 数据块 ID → 页面缓存 → 指针算术 → 指向固定内存页面的 ref readonly T 。零拷贝,零分配,零 GC 介入。由于泛型约束是 where T : unmanaged ,JIT 知道确切布局,最终会编译成指针运算。 JIT 特化的哈希函数 Typhon 的哈希函数也利用了 JIT 。由于受约束泛型下的 sizeof(TKey) 是编译期常量,不需要的分支会被消除。 [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint ComputeHash<TKey>(TKey key) where TKey : unmanaged { if (sizeof(TKey) == 4) return FastHash32(Unsafe.As<TKey, uint>(ref key)); if (sizeof(TKey) == 8) return XxHash32_8Bytes(Unsafe.As<TKey, long>(ref key)); return XxHash32_Bytes((byte*)Unsafe.AsPointer(ref key), sizeof(TKey)); } 例如调用 ComputeHash<int>(42) 时,JIT 只会生成 4 字节路径,其余分支会被完全移除。这是真正的单态化( Monomorphization ),而非运行时分发( Runtime Dispatch )。 生产力论据 一个数据库引擎不仅仅只有热路径。在核心引擎周围包裹着庞大的基础设施:配置管理、结构化日志、遥测、依赖注入、测试、基准测试。 在 C 或 Rust 中,你需要自己构建其中的大部分,或者缝合质量参差不齐的库。 到了 .NET 里,这些能力已经是生产级且免费的: ILogger 和 OpenTelemetry 用于可观测性, BenchmarkDotNet 用于严谨的微基准测试,NUnit 用于测试, IConfiguration 用于配置。它们文档充分、互相兼容,并由 Microsoft 或经受过检验的开源社区维护。 对一个独立开发者来说,这是实际的竞争优势。我可以把时间花在并发原语和页缓存淘汰上,而不是重新发明日志框架。 核心在于内存布局,而非语言 这是我多年实时 3D 引擎开发经验教给我的洞察:数据库引擎的瓶颈在于内存访问模式,而非指令吞吐量。 在 Ryzen 7950X 上,一次 DRAM 缓存未命中大约需要 61 到 73 纳秒。这相当于 CPU 空等约 250 个周期。一次命中 L1 的 CAS 操作约 1.4 纳秒,两者比例约为 50:1 。 如果你的数据结构导致缓存未命中,无论你的语言有多少“零成本抽象”都救不了你。反之,如果你的数据布局对缓存友好——连续、对齐、访问模式可预测——那么语言几乎无关紧要。带有 unsafe 的 C# 在热点路径上生成的机器码与 C 语言相同。JIT 就是这么强大。 真正重要的是: 缓存行感知:Typhon 的 B+ 树节点是 128 字节——两个缓存行。Zen4 上的步进预取器会自动覆盖第二行。仅此一项就比 64 字节节点减少了 53% 的插入延迟和 30% 的查找延迟。 数据导向设计( DoD ): 采用 SOA (结构数组)而非 AOS (数组结构)。SIMD 友好的布局。仅使用可直接复制( Blittable )的类型。 最小化间接引用: 每次指针追踪都是一次潜在的缓存未命中。SIMD 数据块访问器的 MRU 命中完全避免了追踪。 因此,写什么语言远不如设计什么样的内存布局重要。 数据表现 所有测量均在 Ryzen 9 7950X 、.NET 10.0 、BenchmarkDotNet 、Release 配置下进行。 操作 延迟 吞吐 CRUD lifecycle MVCC ( spawn 、read 、update 、destroy 、commit ) 1.2 微秒 830K ops/sec 90 读 / 10 更新负载(每个事务 100 次操作,MVCC ) 22 微秒 约 4.5M entity-ops/sec B+Tree 查找(命中) 267 纳秒 3.7M ops/sec B+Tree 顺序扫描(每个 key ) 2.1 纳秒 479M keys/sec 无竞争锁获取 7.8 纳秒 128M ops/sec 页缓存命中 5.3 纳秒 - 作为参考,Zen4 上一次无竞争 CAS 约 1.4 纳秒;一次 DRAM 往返约 61 到 73 纳秒。Typhon 的锁获取是 7.8 纳秒,大约相当于 5 次 CAS ;考虑到它还处理共享/独占仲裁和等待者跟踪,这个结果已经很紧凑。267 纳秒的 B+Tree 查找意味着大约 6 到 7 次内存访问,符合穿过 L2/L3 缓存进行树遍历的预期。 这些仍然是早期 Alpha 版本的数据,还有优化空间。但它们验证了核心论点: C# 不是瓶颈 。 权衡 任何选择都有成本。以下是我对考虑走同样道路的人的建议。 内存安全要由开发者自己负责 。在 unsafe 代码块里,你可以破坏内存、解引用错误指针、写爆缓冲区,编译器不会拯救你。 Span<T> 是稍慢但完全安全的替代方案。 GC 目前还不是问题,但它可能成为问题 。通过固定页缓存,并在热路径使用 ref struct ,Gen2 回收既少又便宜。但这不保证任何场景都如此。如果某个工作负载在事务之间大量分配托管对象,暂停仍然可能出现。解决办法是纪律:不要在热路径上分配。语言允许你分配,但不会强迫你分配。 “但 Rust 会给你编译时安全。” ,没错,借用检查器可以捕获 unsafe C# 捕获不了的所有权和生命周期错误。但 C# 也有 Rust 没有的工具: Roslyn analyzers 。我编写了一套自定义分析器( TYPHON001–007 ),将特定领域的安全规则强制转化为编译器错误: [NoCopy] 特效和分析器:像 ChunkAccessor 这样的性能关键结构不能按值传递。如果忘记使用 ref ,编译器会报错。它给关键类型提供了类似 Rust move 语义的保证,但范围只覆盖真正需要的类型。 所有权跟踪:如果创建了 ChunkAccessor 或 Transaction 却没有释放,那就是编译错误,而不是运行时泄漏。analyzer 会通过赋值、返回值、 ref / out 参数追踪所有权转移,也可以用 [return: TransfersOwnership] 表达返回值所有权转移。 释放完整性:如果类型持有关键 disposable 字段,而 Dispose() 漏掉了它,或者存在提前返回导致字段没有释放,也会成为编译错误。 // 这在 Typhon 中是一个编译错误 —— TYPHON001 void Process(ChunkAccessor accessor) { ... } // ✗ 错误:必须通过 ref 传递 void Process(ref ChunkAccessor accessor) { ... } // ✓ OK 也就是说,C# 不会免费得到 Rust 的安全性。但可以构建精确适合领域的一组编译期规则。与 Rust 的借用检查器不同,这些规则在诊断中带有领域上下文:“会导致页面缓存死锁”比“值已在此处移动”更具可操作性。 此外,Rust 在周边基础设施(日志、DI 、配置、测试)方面的生态系统不如 .NET 成熟,作为独立开发者,我的开发速度至关重要。我选择了能让我发布更快的语言。 JIT 预热是真问题,但可以管理 。冷启动后的前几笔事务会更慢。对嵌入式引擎来说,这是可以接受的,因为宿主应用通常有自己的预热阶段。如果是服务端数据库,则应考虑分层编译或 AOT 。 下一步 在下一篇文章中,我将解释为什么 ACID 数据库引擎会从游戏引擎中借鉴存储架构——特别是实体-组件-系统( ECS )模式。游戏引擎和数据库在解决同一个基本问题:管理具有极端性能约束的结构化数据。它们只是演化出了完全不同的解决方案。
原文: Why I'm Building a Database Engine in C# 作者: Loïc Baumann 译者: liuliuliuliu 译注:本文翻译使用 AI+人工校对的方式完成 Typhon 是一个用 .NET 编写的嵌入式、持久化、支持 ACID 的数据库引擎,原生支持游戏服务器与实时仿真所熟悉的实体-组件-系统( ECS )范式。 它通过 MVCC 快照隔离提供完整事务安全,并以缓存行感知的存储、零拷贝访问和可配置持久化能力为基础,目标是达到亚微秒级延迟。 这将是一个系列文章:A Database That Thinks Like a Game Engine Why I'm Building a Database Engine in C#(本文) What Game Engines Know About Data That Databases Forgot Microsecond Latency in a Managed Language Deadlock-Free by Construction Three Durability Modes, One WAL Epoch-Based Page Cache (即将发布) 当我告诉别人我正在用 C# 构建一个 ACID 数据库引擎 时,第一反应总是如出一辙:“那 GC (垃圾回收)停顿怎么办?” 这是一个合情合理的问题。几乎没有人会在 .NET 中构建高性能数据库引擎。人们普遍认为,这类软件必须使用 C 、C++ 或 Rust 编写——托管语言基本上被排除在“微秒级延迟俱乐部”之外。 但在构建了 30 年实时 3D 引擎和系统软件之后,我还是选择了 C#。这个项目名为 Typhon :一个嵌入式 ACID 数据库引擎,目标是实现 1–2 微秒的事务提交。 选择它的原因可能会改变你对 C# 能力的看法。 反对 C# 的理由(让我们直面它吧) 在我阐述理由之前,让我诚实地列出所有反对选择 C# 的理由。这些是现实存在的担忧,而非“稻草人”谬论。 GC 是非确定性的 :它可以在任何时候暂停你的所有线程。对于一个承诺微秒级延迟的数据库引擎来说,一次 10 毫秒的 Gen2 (第 2 代)回收是灾难性的——这超出了你延迟预算的 10,000 倍。 你无法控制内存布局 :托管堆决定了对象的存放位置。GC 可以在压缩过程中移动它们。你无法保证 B+ 树节点位于缓存行边界上,也无法保证你的页面缓存缓冲区不会在事务中途被重新定位。 JIT (即时编译)预热是真实存在的 :对任何方法的第一次调用都要支付编译成本。在数据库引擎中,启动后的第一个事务不应该比稳定状态慢 100 倍。 虚函数分发和边界检查会增加开销 :每次数组访问都有隐藏的边界检查。每次接口调用都要经过虚函数表( vtable )。在处理数百万实体的热点循环中,这些纳秒级的开销会不断累积。 这些都是合理的问题。我不会假装它们不存在。但这就是大多数人忽略的一点: 现代 C# 对每一个问题都有对应的解决方案。 大多数人不知道的 C# 另一面 很多开发者熟悉的 C# 是类、垃圾回收和 LINQ 。但这只是语言的一半。.NET Runtime 团队在过去十年里逐步建立了另一面能力,而这部分看起来和许多人想象中的 C# 很不一样。 unsafe 提供接近 C 级别的控制 :原始指针、指针运算、栈上缓冲区的 stackalloc 、固定大小数组等。JIT 可以生成与 C 中相同类型的底层指令。 GCHandle.Alloc(Pinned) 可以让 GC 在关键位置变得不再相关 。开发者可以固定字节数组,让 GC 不移动它们。Typhon 的整个页缓存都是固定内存; GC 不触碰、不扫描、不移动它。对引擎核心而言,它就是一个固定地址上的原始字节区。 ref struct 可以消除热路径上的堆分配 。 ref struct 不能逃逸到堆,只能存在于栈上,并在作用域结束时消失。Typhon 的实体访问器 EntityRef 是一个 96 字节的 ref struct ,目标是零分配、零 GC 压力。 受约束泛型可以提供真正的单态化( Monomorphization ) 。当代码写成 where T : unmanaged 时,JIT 能为每个类型参数生成单独的原生代码路径。 sizeof(T) 会成为常量,无用分支会被消除。这接近 Rust 泛型带来的优化效果:不是运行时分派,而是编译期专门化。 硬件原语( Hardware intrinsics )是一等公民 。 System.Runtime.Intrinsics 提供 Vector256 、 Sse42.Crc32 、 BitOperations.TrailingZeroCount 等能力。它们对应 C/C++ 中可用的 SIMD 指令,并且带有运行时特性检测,可以在不支持相关指令的平台上回退。 [StructLayout(Explicit)] 则可以精确控制内存布局 :字段偏移、填充、大小等,你可以控制每一个字节。缓存行对齐、防止伪共享( False-sharing )、位打包( Bit-packing )——这些功能一应俱全。 这不是“C# 试图成为 C”,而是 C# 在其顶级的托管生态系统之上,提供了一个真正的系统级编程层。 Typhon 实际是什么样 硬件加速的 WAL 校验和 写入预写日志( WAL )的每个页面都需要 CRC32C 校验和。这是它在 C# 中的样子——直接按名字调用 CPU 指令: private static uint ComputePartial(uint crc, ReadOnlySpan<byte> data) { if (Sse42.X64.IsSupported) return ComputeSse42X64(crc, data); if (Sse42.IsSupported) return ComputeSse42X32(crc, data); if (ArmCrc32.Arm64.IsSupported) return ComputeArm64(crc, data); return ComputeSoftware(crc, data); } private static uint ComputeSse42X64(uint crc, ReadOnlySpan<byte> data) { ulong crc64 = crc; ref byte ptr = ref MemoryMarshal.GetReference(data); int offset = 0; int aligned = data.Length & ~7; while (offset < aligned) { // 编译为单条 x86 crc32 指令 crc64 = Sse42.X64.Crc32(crc64, Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref ptr, offset))); offset += 8; } uint crc32 = (uint)crc64; while (offset < data.Length) { crc32 = Sse42.Crc32(crc32, Unsafe.Add(ref ptr, offset)); offset++; } return crc32; } Sse42.X64.Crc32() 会编译成单条 x86 crc32 指令。运行时检测 CPU 能力,JIT 消除无用分支,最后执行的是 C 程序员会写出的同类机器代码,同时还保留了不支持 SSE4.2 时的自动回退。 结果: 每 8 KB 页面约 1.3 微秒 。 SIMD 数据块访问器 Typhon 的页缓存热路径使用一个 16 槽缓存,通过三层路径查找数据。 // === 超快路径:MRU (最近最常使用)检查 === var mru = _mruSlot; if (_pageIndices[mru] == pageIndex) { var headerOffset = pageIndex == 0 ? _rootHeaderOffset : _otherHeaderOffset; return (byte*)_baseAddresses[mru] + headerOffset + offset * _stride; } // === 快路径:通过 SIMD 搜索所有 16 个缓存插槽 === fixed (int* indices = _pageIndices) { var target = Vector256.Create(pageIndex); var v0 = Vector256.Load(indices); var mask0 = Vector256.Equals(v0, target).ExtractMostSignificantBits(); if (mask0 != 0) { var slot = BitOperations.TrailingZeroCount(mask0); return GetFromSlot(slot, pageIndex, offset, dirty); } var v1 = Vector256.Load(indices + 8); var mask1 = Vector256.Equals(v1, target).ExtractMostSignificantBits(); if (mask1 != 0) { var slot = 8 + BitOperations.TrailingZeroCount(mask1); return GetFromSlot(slot, pageIndex, offset, dirty); } } _pageIndices 是一个 fixed int[16] ,共 64 字节,刚好一个缓存行,并且以适合 SIMD 的方式排列。一次 Vector256.Equals 可以比较 8 个页索引。MRU 快路径覆盖“重复访问同一页”这个常见场景,对分支预测友好,成本接近于零。 零拷贝实体读取 EntityRef 是一个只存在于栈上的 ref struct ,大小为 96 字节,其中包含一个内联固定数组,用来缓存组件位置。 public unsafe ref struct EntityRef { internal readonly EntityId _id; // ... 其他元数据 private fixed int _locations[16]; // 内联组件数据块 ID [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref readonly T Read<T>(Comp<T> comp) where T : unmanaged { byte slot = _archetype.GetSlot(comp._componentTypeId); int chunkId = _locations[slot]; var table = _engineState.SlotToComponentTable[slot]; return ref _tx.ReadEcsComponentData<T>(table, chunkId); } } 这个 Read<T> 调用经历了:方法调用 → 插槽查找 → 数据块 ID → 页面缓存 → 指针算术 → 指向固定内存页面的 ref readonly T 。零拷贝,零分配,零 GC 介入。由于泛型约束是 where T : unmanaged ,JIT 知道确切布局,最终会编译成指针运算。 JIT 特化的哈希函数 Typhon 的哈希函数也利用了 JIT 。由于受约束泛型下的 sizeof(TKey) 是编译期常量,不需要的分支会被消除。 [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static uint ComputeHash<TKey>(TKey key) where TKey : unmanaged { if (sizeof(TKey) == 4) return FastHash32(Unsafe.As<TKey, uint>(ref key)); if (sizeof(TKey) == 8) return XxHash32_8Bytes(Unsafe.As<TKey, long>(ref key)); return XxHash32_Bytes((byte*)Unsafe.AsPointer(ref key), sizeof(TKey)); } 例如调用 ComputeHash<int>(42) 时,JIT 只会生成 4 字节路径,其余分支会被完全移除。这是真正的单态化( Monomorphization ),而非运行时分发( Runtime Dispatch )。 生产力论据 一个数据库引擎不仅仅只有热路径。在核心引擎周围包裹着庞大的基础设施:配置管理、结构化日志、遥测、依赖注入、测试、基准测试。 在 C 或 Rust 中,你需要自己构建其中的大部分,或者缝合质量参差不齐的库。 到了 .NET 里,这些能力已经是生产级且免费的: ILogger 和 OpenTelemetry 用于可观测性, BenchmarkDotNet 用于严谨的微基准测试,NUnit 用于测试, IConfiguration 用于配置。它们文档充分、互相兼容,并由 Microsoft 或经受过检验的开源社区维护。 对一个独立开发者来说,这是实际的竞争优势。我可以把时间花在并发原语和页缓存淘汰上,而不是重新发明日志框架。 核心在于内存布局,而非语言 这是我多年实时 3D 引擎开发经验教给我的洞察:数据库引擎的瓶颈在于内存访问模式,而非指令吞吐量。 在 Ryzen 7950X 上,一次 DRAM 缓存未命中大约需要 61 到 73 纳秒。这相当于 CPU 空等约 250 个周期。一次命中 L1 的 CAS 操作约 1.4 纳秒,两者比例约为 50:1 。 如果你的数据结构导致缓存未命中,无论你的语言有多少“零成本抽象”都救不了你。反之,如果你的数据布局对缓存友好——连续、对齐、访问模式可预测——那么语言几乎无关紧要。带有 unsafe 的 C# 在热点路径上生成的机器码与 C 语言相同。JIT 就是这么强大。 真正重要的是: 缓存行感知:Typhon 的 B+ 树节点是 128 字节——两个缓存行。Zen4 上的步进预取器会自动覆盖第二行。仅此一项就比 64 字节节点减少了 53% 的插入延迟和 30% 的查找延迟。 数据导向设计( DoD ): 采用 SOA (结构数组)而非 AOS (数组结构)。SIMD 友好的布局。仅使用可直接复制( Blittable )的类型。 最小化间接引用: 每次指针追踪都是一次潜在的缓存未命中。SIMD 数据块访问器的 MRU 命中完全避免了追踪。 因此,写什么语言远不如设计什么样的内存布局重要。 数据表现 所有测量均在 Ryzen 9 7950X 、.NET 10.0 、BenchmarkDotNet 、Release 配置下进行。 操作 延迟 吞吐 CRUD lifecycle MVCC ( spawn 、read 、update 、destroy 、commit ) 1.2 微秒 830K ops/sec 90 读 / 10 更新负载(每个事务 100 次操作,MVCC ) 22 微秒 约 4.5M entity-ops/sec B+Tree 查找(命中) 267 纳秒 3.7M ops/sec B+Tree 顺序扫描(每个 key ) 2.1 纳秒 479M keys/sec 无竞争锁获取 7.8 纳秒 128M ops/sec 页缓存命中 5.3 纳秒 - 作为参考,Zen4 上一次无竞争 CAS 约 1.4 纳秒;一次 DRAM 往返约 61 到 73 纳秒。Typhon 的锁获取是 7.8 纳秒,大约相当于 5 次 CAS ;考虑到它还处理共享/独占仲裁和等待者跟踪,这个结果已经很紧凑。267 纳秒的 B+Tree 查找意味着大约 6 到 7 次内存访问,符合穿过 L2/L3 缓存进行树遍历的预期。 这些仍然是早期 Alpha 版本的数据,还有优化空间。但它们验证了核心论点: C# 不是瓶颈 。 权衡 任何选择都有成本。以下是我对考虑走同样道路的人的建议。 内存安全要由开发者自己负责 。在 unsafe 代码块里,你可以破坏内存、解引用错误指针、写爆缓冲区,编译器不会拯救你。 Span<T> 是稍慢但完全安全的替代方案。 GC 目前还不是问题,但它可能成为问题 。通过固定页缓存,并在热路径使用 ref struct ,Gen2 回收既少又便宜。但这不保证任何场景都如此。如果某个工作负载在事务之间大量分配托管对象,暂停仍然可能出现。解决办法是纪律:不要在热路径上分配。语言允许你分配,但不会强迫你分配。 “但 Rust 会给你编译时安全。” ,没错,借用检查器可以捕获 unsafe C# 捕获不了的所有权和生命周期错误。但 C# 也有 Rust 没有的工具: Roslyn analyzers 。我编写了一套自定义分析器( TYPHON001–007 ),将特定领域的安全规则强制转化为编译器错误: [NoCopy] 特效和分析器:像 ChunkAccessor 这样的性能关键结构不能按值传递。如果忘记使用 ref ,编译器会报错。它给关键类型提供了类似 Rust move 语义的保证,但范围只覆盖真正需要的类型。 所有权跟踪:如果创建了 ChunkAccessor 或 Transaction 却没有释放,那就是编译错误,而不是运行时泄漏。analyzer 会通过赋值、返回值、 ref / out 参数追踪所有权转移,也可以用 [return: TransfersOwnership] 表达返回值所有权转移。 释放完整性:如果类型持有关键 disposable 字段,而 Dispose() 漏掉了它,或者存在提前返回导致字段没有释放,也会成为编译错误。 // 这在 Typhon 中是一个编译错误 —— TYPHON001 void Process(ChunkAccessor accessor) { ... } // ✗ 错误:必须通过 ref 传递 void Process(ref ChunkAccessor accessor) { ... } // ✓ OK 也就是说,C# 不会免费得到 Rust 的安全性。但可以构建精确适合领域的一组编译期规则。与 Rust 的借用检查器不同,这些规则在诊断中带有领域上下文:“会导致页面缓存死锁”比“值已在此处移动”更具可操作性。 此外,Rust 在周边基础设施(日志、DI 、配置、测试)方面的生态系统不如 .NET 成熟,作为独立开发者,我的开发速度至关重要。我选择了能让我发布更快的语言。 JIT 预热是真问题,但可以管理 。冷启动后的前几笔事务会更慢。对嵌入式引擎来说,这是可以接受的,因为宿主应用通常有自己的预热阶段。如果是服务端数据库,则应考虑分层编译或 AOT 。 下一步 在下一篇文章中,我将解释为什么 ACID 数据库引擎会从游戏引擎中借鉴存储架构——特别是实体-组件-系统( ECS )模式。游戏引擎和数据库在解决同一个基本问题:管理具有极端性能约束的结构化数据。它们只是演化出了完全不同的解决方案。