FlashProxy Logo

FlashProxy

代理技术新闻教程指南公告

3 倍连接数。同样的核心。FlashProxy Accept 引擎性能研究背后的故事

3 倍连接数。同样的核心。FlashProxy Accept 引擎性能研究背后的故事

FlashProxy 如何利用自主 AI 优化器发现了一个 TCP accept 引擎,其 CPU 指令数比 Go 少 3 倍。开源且采用 MIT 许可证。

F
FlashProxy 团队
2026年6月21日
2 分钟阅读

存在一类性能问题,只有在规模达到一定程度时才会显现,当它出现时确实令人警醒。你的瓶颈不在路由逻辑上。不在加密上。不在应用程序应该做的任何事情上。你的瓶颈在基础设施上。在管道上。

这正是我们遭遇的情况。

在每秒约 40,000 个连接的规模下,我们的生产级 Go 代理开始在 accept 路径上触及 CPU 限制。罪魁祸首是 Go 网络服务的标准模型:每个连接一个 goroutine,每个操作一次系统调用。每个生命周期短的连接(健康检查、负载均衡器探测、小型重定向)都会接触内核四次:accept、read、write、close。在高连接变动时,这四次往返不再只是开销,而成为了整个成本。应用程序代码几乎是免费的。让字节进出内核才是代价。

大多数人采用的解决方案是 io_uring,Linux 的异步 I/O 接口,它可以批量处理内核操作,大幅减少跨越用户/内核边界的次数。我们知道这一点。更难的问题是如何调优它。

为什么我们没有自己调优它

io_uring 有一长串记录在案的优化技术:multishot accept、registered file descriptors、DEFER_TASKRUN、completion chains、MSG_MORE 合并。问题在于这些技术之间的相互作用方式确实难以预测。某些组合会相互增强。某些会相互抵消。某些性能退化完全不可见,直到你在真实硬件上进行实际负载基准测试。

一名开发者手握 perf stat 一天内可以测试少数几个组合。但真正的搜索空间,涵盖组合、顺序、参数值和内核版本交互,远大于此。更重要的是,人类的直觉在这里是一个障碍。我们倾向于坚持看起来在纸面上正确的理论,即使数据另有说明。

所以我们搭建了一个闭合测量循环,把搜索问题交给了一个 AI 智能体。

研究循环如何工作

我们并排运行了两个服务器实现。对照组是一个 Go 服务器,精确模拟我们生产代理的 accept 路径:每个连接一个 goroutine,SO_REUSEPORT 扇出。它在整个实验期间被冻结。处理组是使用 liburing 的 C 服务器,从一个基础的单次 io_uring accept 循环开始。这是唯一可以更改的代码。

两个服务器遵循同样的约定:接受连接、读取请求字节、写入固定的 HTTP 200 响应、关闭。同样的硬件、同样的内核、同样的负载。唯一的变量是它们如何处理。

Claude Code 作为优化器无头运行。每次迭代时,它读取当前的冠军、存储为 git 提交的完整历史变化、跨运行积累的知识库,以及性能分析数据库。它形成一个假设并进行编辑。然后完全移交。

一个独立的 bash 工具脚本(AI 无法触碰)编译变化、将其固定到隔离的 CPU 核心、运行负载生成器,并评分结果。评分公式为:

score = 1,000,000,000/mean(每个连接的指令数)

CPU 指令计数是精确的硬件测量。与吞吐量数字不同,它们不受热节流和时钟频率变化的影响。它们精确告诉你 CPU 每个连接做了多少工作,这正是决定规模下容量的因素。

如果一个变化相对于当前冠军的得分提高了超过 3%,它就会被晋升。如果没有,它会被用 git reset --hard 删除,循环继续。AI 编写假设。工具脚本进行每一个保留或回滚决策。两者互不干扰。

为了防止优化器作弊基准测试,每次评分运行都要验证:回复字节必须完全正确、连接必须端到端完成,失败率必须保持在 0.01% 以下。任何违反都得零分。

我们发现了什么

优化器运行了两天,收敛到六项变化,这些变化共同解释了整个性能差距。

  • DEFER_TASKRUN 和 SINGLE_ISSUER ring 标志。 这些将 completion 任务工作移入 worker 线程自己的事件循环,消除跨 CPU 唤醒。这是整个运行中单次最大的跳跃,直接降低了每个操作的内核 CPU 成本,不是吞吐量技巧。

  • Registered file descriptors。 使用直接描述符,被接受的连接活在 ring 自己的表中而非进程文件描述符表。这跳过了 accept 上的 fd 表安装和后续每个操作上的查找。优化器测试了多个表大小,发现 4,096 条目是最优配置。

  • Multishot accept。 与其在每个连接后重新武装 accept 操作,不如武装一次,内核为每个新连接自动发布一个 completion。这将每个连接的 io_uring_enter 调用减少到 0.34。

  • Per-worker 连接空闲列表。 每个 worker 预分配 128 个连接对象完全消除了热路径上的 malloc。测量的 libc 消耗的 CPU 指令份额从 1.32% 降至 0.92%。

  • MSG_MORE 回复和 FIN 融合。 使用 MSG_MORE 发送回复时会将其保留在 TCP 写队列中,以便连接的 FIN 可以搭载在上面,以单个 TCP 段的形式发送两者。一次 NIC 敲门而非两次。优化器通过注意到一个低级内核写函数消耗的指令数超过预期两倍,并将其追踪回不必要的段分割而发现了这一点。

  • 批量 completion 和 CQE_SKIP_SUCCESS。 标记 send 和 close 操作使其在成功时不生成 completion 事件意味着只有 accept 和 receive 产生 completion,每个连接大约两个而非四个。一次 submit-and-wait 调用驱动许多连接同时进行。

教会最多东西的失败

在某一点,优化器试图将 receive、send 和 close 操作链接在一起,这是一个使每个操作在前一个完成时自动触发的技术。目标是减少内核往返,它确实有效:每个连接的 enter 调用从 1.90 降至 1.40。

得分下降了 34%。立即回滚。

进入知识库的教训:最小化内核条目不是杠杆。链序列化的成本超过了它节省的条目。

这正是打破人类直觉的那种结果。看起来像瓶颈的指标不是实际瓶颈。工程师可能会更长时间地为那个优化辩护。这个循环测量了它、拒绝了它、然后继续前进。

搜索停止的地方

在六个胜利之后,优化器探索了大约十几个更多的候选。全部被回滚,不是因为它们出现退化,而是因为测量噪音超过了 3% 晋升阈值。没有什么可以找的了。

在冠军配置下,剩余 CPU 的大约 94% 属于 Linux 内核的 TCP 栈。大约 1% 是应用程序代码。大约 4% 是利用内部机制。没有用户空间代码留下来有意义地优化。研究正确识别了下限并在那里停止。

结果

在一个固定的核心上,CPU 限制,有 512 个固定的环回上的在途连接:

  • Go goroutine-per-connection:每个连接 83,250 条指令

  • Vanilla io_uring 起始基线:每个连接 59,931 条指令

  • FlashProxy 的 accept 引擎每个连接 27,363 条指令

这是 比 Go 少 3.04 倍的每连接 CPU 指令数,以及 比 io_uring 基线少 2.19 倍。在单个饱和核心上,这相当于与 goroutine 模型相比大约六倍的连接吞吐量。

这些是环回基准,设计用于精确隔离 CPU 成本。比率很重要,不是绝对数字。底层技术(multishot accept、DEFER_TASKRUN、registered descriptors)记录在 io_uring 习语中。研究产生的是哪些组合实际上可以一起工作,哪些在纸面上看起来不错但在实践中会消耗你成本的证明。

开源库

获胜的设计现在是 flashaccept,一个开源 C 库,在一个简单的 API 后面打包了全部六个优化。你给它一个端口和一个请求处理器。它自动为每个核心运行一个优化的 io_uring accept 循环。

它需要 Linux 和 liburing 2.3 或更新版本。在更旧的内核上它会优雅降级。预期的用例是高变动、短生命周期连接:健康检查、重定向器、负载均衡器探测、小型 RPC 响应。完整的研究套件,包括基线、工具脚本、优化器配置和全部累积的知识库,都包含在内且完全可复现。

MIT 许可证。现已在 github.com/thealonlevi/flashaccept 推出。

这项研究直接来自构建 FlashProxy 的 代理基础设施。如果你正在运行高变动的 Linux 服务,accept 路径是你的瓶颈,我们为正好这个问题构建了它。如果你想扩展它或自己运行研究套件,代码库有你需要的一切。

常见问题

Flashaccept 是什么?

Flashaccept 是 FlashProxy 构建的开源 C 库,为 Linux 提供高性能 TCP accept 引擎。它在底层使用 io_uring,接受连接的 CPU 指令数比标准 Go goroutine-per-connection 服务器少 3.04 倍。它采用 MIT 许可证,可在 GitHub 获得。

它为哪些工作负载设计?

Flashaccept 为高变动、短生命周期连接而构建,其中请求-回复-关闭循环以高容量发生:健康检查端点、负载均衡器探测、HTTP 重定向器和小型 RPC 响应。在 v1 中它不为保活连接或多交换会话设计。

我需要哪个 Linux 版本和 liburing 版本?

你需要 Linux 配合 liburing 版本 2.3 或更新,随 Ubuntu 24.04 及更新版本附带。快速路径(multishot accept、direct descriptors)需要内核 5.19 或更新。在更旧的内核上库会优雅降级到单次 accept 和常规文件描述符。

什么是 io_uring,为什么它对代理性能很重要?

io_uring 是在内核 5.1 中引入的 Linux 内核接口,允许应用程序使用共享内存环缓冲区异步提交和接收 I/O 操作,大幅减少所需的系统调用数。对于处理每秒数万个短生命周期连接的 代理基础设施,重复跨越用户/内核边界的成本成为主要的 CPU 开销。io_uring 显著降低了该成本。

flashaccept 是 FlashProxy 在生产中运行的吗?

研究直接来自我们的生产扩展工作。FlashProxy 的 代理基础设施 跨越 190 多个国家运行并处理大量连接容量。accept 路径优化是真实的工程需求,不是研究练习。flashaccept 是该工作的提炼结果,开源以便其他人可以使用。

我今天可以使用 flashaccept 吗?

可以。它是版本 1.0.1,MIT 许可证,通过 GitHub、vcpkg、Conan 和 Arch AUR 提供。该库已在 AddressSanitizer 和 UBSan 下测试,跨越 400,000 多个连接,跨越所有四个配置路径。

我在哪里可以了解更多关于 FlashProxy 基础设施的信息?

你可以在 FlashProxy 博客 上阅读更多关于我们如何构建和扩展代理基础设施的内容,或直接探索我们的 代理网络


flashacceptFlashProxy性能提升代理服务器服务器提升代理服务器提升