GitHub user lizining1231 added a comment to the discussion: [OSPP 2026] Triple 
协议性能分析与优化

这里是一份针对Triple-unary的性能瓶颈分析报告,里面有一定的方案设计,如果没问题,之后的推进形式是issue和PR
# 性能瓶颈分析报告(一)Triple-unary 热路径 io.Pipe 专项

## 目录

一、全文摘要

二、数据分析与瓶颈假设

三、代码瓶颈定位

四、多工具交叉验证瓶颈

五、方案设计与演进

六、方案验证与实施

七、预估收益

八、参考链接

## 一、摘要

本文针对 Triple unary 热路径的固定开销(每请求新建 `io.Pipe` + 额外 
goroutine)做了一次完整分析:从基准数据定位到该瓶颈,用 pprof / trace 多工具交叉验证,再设计并实现 unary 快路径(去 
pipe、去每请求 goroutine、池化单拷贝,并声明 Content-Length 与 `fastPathBody`)。方案原型 A/B 实测多指标全正

快路径方案拟以 `unaryFastPathCall` 形态落地,并配套独立开关 
`unary-fast-path`(默认关闭,改配置即可回滚)。预估生产集成后收益收敛为:QPS **+8%\~+10%**、P99 
**-5%\~-15%**、延迟 **-10%\~-25%**、B/op **-15%\~-35%**、allocs **持平\~微增**。

## 二、数据分析与瓶颈假设

这里从基准测试数据报告入手,挑选一些特定场景的数据总结规律,这些规律均指向固定开销,然后定位到固定开销的瓶颈。

### 2.1.1 小报文性能差距最大:

**QPS 对比**

| 并发数 | dubbo-go    | grpc     | dubbo-java |
| --- | ----------- | -------- | ---------- |
| 50  | **4,425.4** | 40,561.9 | 10,625.8   |
| 100 | **4,537.6** | 47,064.7 | 10,667.0   |

**P99 对比**

| 并发数 | dubbo-go  | grpc | dubbo-java |
| --- | --------- | ---- | ---------- |
| 50  | **19.56** | 3.58 | 6.74       |
| 100 | **33.29** | 5.54 | 12.23      |

在 50 并发×128B 小包场景下,**gRPC 的 QPS 约为 dubbo-go 的 9.2 倍**(dubbo-go 明显落后),而 dubbo-go 
的 P99 延迟约为 gRPC 的 **5.5 倍**;在 100 并发×128B 小包场景下,**gRPC 的 QPS 约为 dubbo-go 的 10.4 
倍**,dubbo-go 的 P99 延迟约为 gRPC 的 **6.0 倍**。对比 50 并发可见:并发越高、小报文差距越大(QPS 差距 9.2x → 
10.4x,P99 差距 5.5x → 6.0x),**高并发 × 小报文是差距最大的场景**,也是本报告的主要场景。

### 2.1.2 性能差距随报文增大而降低

**50 并发 · QPS 差距**

| 报文    | dubbo-go QPS | grpc QPS | grpc/dubbo-go |
| ----- | ------------ | -------- | ------------- |
| 128B  | 4,425.4      | 40,561.9 | **9.2x**      |
| 1KiB  | 3,597.7      | 33,508.7 | **9.3x**      |
| 16KiB | 1,980.3      | 11,882.1 | **6.0x**      |
| 1MiB  | 122.9        | 284.5    | **2.3x**      |

**50 并发 · P99 差距**

| 报文    | dubbo-go P99 (ms) | grpc P99 (ms) | dg/grpc   |
| ----- | ----------------- | ------------- | --------- |
| 128B  | 19.56             | 3.58          | **5.5x**  |
| 1KiB  | 21.51             | 4.09          | **5.3x**  |
| 16KiB | 43.24             | 10.07         | **4.3x**  |
| 1MiB  | 1,074.68          | 261.09        | **4.1x**  |

**100 并发 · QPS 差距**

| 报文    | dubbo-go QPS | grpc QPS | grpc/dubbo-go |
| ----- | ------------ | -------- | ------------- |
| 128B  | 4,537.6      | 47,064.7 | **10.4x**     |
| 1KiB  | 3,670.8      | 42,400.3 | **11.6x**     |
| 16KiB | 1,829.6      | 12,852.9 | **7.0x**      |
| 1MiB  | 119.4        | 274.0    | **2.3x**      |

**100 并发 · P99 差距**

| 报文    | dubbo-go P99 (ms) | grpc P99 (ms) | dg/grpc   |
| ----- | ----------------- | ------------- | --------- |
| 128B  | 33.29             | 5.54          | **6.0x**  |
| 1KiB  | 42.96             | 6.80          | **6.3x**  |
| 16KiB | 93.16             | 16.28         | **5.7x**  |
| 1MiB  | 2,400.09          | 511.09        | **4.7x**  |

dubbo-go 与 gRPC 的**性能差距(QPS 与 P99)随报文增大呈现缩小趋势**:QPS 差距 128B 时约 9\~10 倍、1MiB 时约 
2.3 倍;P99 差距 128B 时约 5.5\~6 倍、1MiB 时约 4\~4.7 倍。但**并非严格单调递减**:1KiB(QPS 9.3x / 
P99 5.3x)与 128B 几乎持平,16KB 
以上才出现明显收窄。这非常符合**每请求的固定开销被摊薄**的模型,因为报文越小,固定开销在单请求耗时中占比越高,性能差距也就越大;当报文越大时,传输带宽替代固定开销成为主要耗时占比,差距自然变小。同时对比
 50/100 两档并发可见:**并发越高、小报文差距越大**(128B QPS 差距 9.2x → 10.4x、P99 差距 5.5x → 
6.0x),进一步印证高并发 × 小报文是固定开销最显著的主战场。

也可以参考这个简化模型来解释:
单次请求总耗时 = 固定开销 + ( 报文大小 / 物理带宽 )

所以此次优化不仅仅是定位小报文高并发场景,而是小报文瓶颈背后所体现的固定开销:

dubbo-go 的 **triple\_protocol 层 unary 客户端发送路径**(`duplexHTTPCall`,即基于 connect-go 
的 unary 发送实现)存在固定开销,包括:**每请求创建 io.Pipe、每请求启动 goroutine、多次系统调用、goroutine 
调度(futex)唤醒**。此次瓶颈定位范围仅限 unary 客户端发送路径,**不含** dubbo-go 上层框架与服务端路径开销,也不涉及与 gRPC 
共有的 HTTP/2 连接层。

### 2.1.3 内存分配差距不足以解释吞吐差距

| 指标(128B × 50并发) | dubbo-go | grpc     | 比值       |
| --------------- | -------- | -------- | -------- |
| QPS             | 4,425.4  | 40,561.9 | **9.2x** |
| B/op            | 23,307   | 5,899    | **3.9x** |
| allocs/op       | 151.5    | 79.4     | **1.9x** |

若吞吐与单请求分配量成反比(QPS ∝ 1/分配量),则 B/op 差距 3.9x 最多把吞吐差距解释到 3.9x,占实际差距 9.2x 的约 42%(3.9 
÷ 9.2 ≈ 0.42),即分配因素**最多解释约 4 成**差距,其余约 6 
成与分配量无关,只能由每请求固定开销(io.Pipe、goroutine、调度)解释。

### 2.2.1 瓶颈定位假设

先来定义一下什么叫dubbo-go triple unary存在的固定开销,

**①每请求固定发生、②与报文无关、③gRPC-Go 类似路径不执行**

| 候选                                      | unary 是否触发 | gRPC-Go 是否存在           
  |
| --------------------------------------- | ---------- | 
------------------------ |
| **`io.Pipe()`** **新建无缓冲管道**             | **每次**     | **否**                  
  |
| **`go makeRequest()`** **额外 goroutine** | **每次**     | **否(loopyWriter 
常驻连接级)** |
| `cloneURL` + `http.Request` 构造          | 每次         | 是(无管道)                 
  |
| Header 双重拷贝                             | 每次         | 单次                     
  |
| `bytes.NewBuffer(raw)` 包装               | 每次         | 是                      
  |

我们可以发现,其中 **`io.Pipe`** **与每请求 goroutine** 是 dubbo-go 独有、每次 unary 必发生、且 gRPC-Go 
完全缺失的固定开销,锁定这两项后,接下来看一下这些固定请求的代码。

## 三、代码瓶颈定位

### 3.1 每请求新建无缓冲管道 `io.Pipe()`:同步交接 + 4KB 中间缓冲 + 双 channel 同步

[duplex\_http\_call.go 
L59-L103](https://github.com/lizining1231/dubbo-go/blob/76631118/protocol/triple/triple_protocol/duplex_http_call.go#L59-L103)(`newDuplexHTTPCall`:cloneURL
 → io.Pipe → 请求体挂管道读端)

```go
func newDuplexHTTPCall(
        ctx context.Context,
        httpClient HTTPClient,
        url *url.URL,
        spec Spec,
        header http.Header,
) *duplexHTTPCall {
        url = cloneURL(url)                       // 每次拷贝 URL
        url.Path = spec.Procedure
        pipeReader, pipeWriter := io.Pipe()       // 每次调用创建无缓冲同步管道

        request := (&http.Request{
                Method: http.MethodPost,
                URL:    url,
                Header: header,
                Body:   pipeReader,                   // 请求体为管道读端
                Host:   url.Host,
        }).WithContext(ctx)
        return &duplexHTTPCall{ ... }
}
```

### 3.2 每请求 goroutine

[duplex\_http\_call.go 
L265-L307](https://github.com/lizining1231/dubbo-go/blob/76631118/protocol/triple/triple_protocol/duplex_http_call.go#L265-L307)(`makeRequest`:`httpClient.Do`
 发送路径)

```go
func (d *duplexHTTPCall) ensureRequestMade() {
        d.sendRequestOnce.Do(func() {
                go d.makeRequest()                    // 每次调用启动独立 goroutine
        })
}

func (d *duplexHTTPCall) makeRequest() {
        defer close(d.responseReady)
        response, err := d.httpClient.Do(d.request)   // x/net/http2 另为每个请求启动
        ...                                            // goroutine 关闭请求体
}
```

### 3.3 写路径的同步交接

([`duplex_http_call.go` 
L108-L126](https://github.com/lizining1231/dubbo-go/blob/76631118/protocol/triple/triple_protocol/duplex_http_call.go#L108-L126)):

```go
// duplex_http_call.go:写入管道写端,阻塞至 transport 读取(同步交接)
func (d *duplexHTTPCall) Write(data []byte) (int, error) {
        d.ensureRequestMade()                      // 触发 goroutine
        bytesWritten, err := d.requestBodyWriter.Write(data)   // 阻塞交接
        ...
}
```

> 注:Triple unary 请求体**无 5 字节 envelope 帧化**,`tripleUnaryMarshaler.write` 
> 将消息体单次写入 
> `writer.Write(data)`([`protocol_triple.go`](https://github.com/lizining1231/dubbo-go/blob/76631118/protocol/triple/triple_protocol/protocol_triple.go#L526-L527)
>  
> [L526-L527](https://github.com/lizining1231/dubbo-go/blob/76631118/protocol/triple/triple_protocol/protocol_triple.go#L526-L527));`envelopeWriter`(5
>  字节 prefix + body 分离两次写)仅 gRPC 
> 协议使用([`protocol_grpc.go`](https://github.com/lizining1231/dubbo-go/blob/76631118/protocol/triple/triple_protocol/protocol_grpc.go#L206)
>  
> [L206](https://github.com/lizining1231/dubbo-go/blob/76631118/protocol/triple/triple_protocol/protocol_grpc.go#L206)
>  / 
> [L298](https://github.com/lizining1231/dubbo-go/blob/76631118/protocol/triple/triple_protocol/protocol_grpc.go#L298)),不属于
>  Triple unary 写路径。

### 3.4 与gRPC此处实现差异

| 对比项             | dubbo-go(Triple)                        | gRPC-Go           
                              |
| --------------- | --------------------------------------- | 
----------------------------------------------- |
| unary 请求体承载     | io.Pipe(无缓冲同步管道)                        | 直接写入 HTTP/2 
stream                              |
| 每请求额外 goroutine | 1 个(makeRequest)+ http2 关闭请求体 goroutine | 0 个(loopyWriter 
每连接常驻 1 个)                      |
| 写路径             | 消息体单次写入(无帧化),经 io.Pipe 交接               | 5 字节 hdr 与 data 
打包进 controlBuf,loopyWriter 合并写出 |
| 同步机制            | 管道内部双 channel 同步(chanrecv 等待)           | 无                 
                              |

分析:`duplexHTTPCall` 的全双工设计(管道 + goroutine + responseReady channel)服务于 streaming 
语义。unary 场景下请求体在请求发起前已完整就绪,无需流式写入与并发读写,该机制属于设计冗余。gRPC-Go 的 unary 直接写入 
stream,不存在管道与每 RPC goroutine,这是小报文固定开销差距的主要来源。

## 四、多工具交叉验证瓶颈

### 4.1 pprof采集goroutine分布

dubbo-go
<img width="2000" height="1200" alt="image" 
src="https://github.com/user-attachments/assets/8e05424f-3969-4f01-ad2c-600973bc35d8";
 />

gRPC-go
<img width="2000" height="1200" alt="image" 
src="https://github.com/user-attachments/assets/b0d59278-2694-4b02-9270-1cc8d69d1f5b";
 />

解读一下,**gRPC** 压测稳态下共 **114 个 goroutine**:**99 个**是压测 worker 的请求 goroutine,阻塞在 
`waitOnHeader`(等待响应头到达);其余约 15 个是运行时基础设施

**dubbo-go 这边**,共 **312 个 goroutine**,分布为:

**99 个**阻塞在 `duplexHTTPCall.makeRequest`,每请求启动的发送 goroutine,等待 HTTP 响应

**90 个**阻塞在 `io.(*pipe).write`,请求体写入无缓冲管道,等待 transport 读取交接

**98 个**阻塞在 `http2 clientStream.writeRequest`,连接层写帧,等待流控窗口 / 等待管道数据

**8 个**阻塞在 `BlockUntilResponseReady`,等待响应就绪信号

其余约 **17 个**为运行时基础设施

对比可见:dubbo-go 的 goroutine 主要分布在 **io.Pipe 交接** 与 **每请求 goroutine(makeRequest)** 
两条机制上(99 + 90 = 189 / 312 ≈ **61%**);gRPC 没有这两条机制(其请求发送在 worker goroutine 
内同步完成,连接写由常驻 loopyWriter 处理)。这正是 unary 快路径方案想要去除的开销。

### 4.2 pprof 采集CPU热点

dubbo-go
<img width="2000" height="1200" alt="image" 
src="https://github.com/user-attachments/assets/bc3514f3-d9e6-4707-91bd-3c0ee5521439";
 />

dubbo-go的syscall6展开
<img width="2000" height="1200" alt="image" 
src="https://github.com/user-attachments/assets/c36a58b6-c69f-4796-bda0-7e21307fcf8f";
 />

grpc-go
<img width="2000" height="1200" alt="image" 
src="https://github.com/user-attachments/assets/4243260a-78d2-49cc-afcf-de4c5f2005dd";
 />
dubbo-go 侧 CPU 采样中 **syscall 占比 26.57%**、**futex 占比 12.16%**,总和达到近40%,分别为 gRPC 
侧(10.86% / 7.98%)的 2.4 倍与 1.5 倍;gRPC 侧 CPU 则相对集中于内存分配,mallocgc 占比 
18.18%(dubbo-go 为 9.39%)

syscall 与 futex 正是 `duplexHTTPCall` 的 io.Pipe 交接的直接运行成本,该机制包含三处开销:

① **io.Pipe** 将请求体分段读、写,放大单请求系统调用次数,`write()` 占 syscall 的 
**88%**,写请求头与写请求体两条链路均在 doRequest 上,单请求消耗 2 次系统调用,优化点从“每请求多次小写”改为“最少的必要写入”;

② **responseReady 就绪信号**引入跨 goroutine 唤醒与 futex 等待,请求发出后,客户端 goroutine 阻塞在 
`BlockUntilResponseReady`,等待 http2 连接层读取到响应头后经该就绪信号唤醒;此类跨 goroutine 
的等待与唤醒(连同管道内部的 channel 同步)由内核 futex 支撑,对应火焰图中 futex 占比 **12.16%**;

③ **makeRequest 独立 goroutine** 叠加调度与上下文切换,每个请求额外启动一个发送 goroutine,专职从 io.Pipe 
读取请求体并写入 http2 层;每请求多一次 goroutine 调度与上下文切换,goroutine 验证中 99 个 makeRequest 
阻塞栈即为该机制的直接观测。

unary 快路径整体绕过这三处,该部分开销随之消除,向 gRPC 水平收敛。此结论与 goroutine 对比验证(312 vs 
114)互相印证,两项数据同源于 `duplexHTTPCall` 的 io.Pipe 交接。

### 4.3 go tool trace 采集调度延迟

dubbo-go
<img width="2000" height="1400" alt="image" 
src="https://github.com/user-attachments/assets/07116b02-acb4-4863-8ae0-4ab95af283f2";
 />
gRPC-go
<img width="2000" height="1400" alt="image" 
src="https://github.com/user-attachments/assets/f1d92299-0527-4c43-8e05-f37a30781688";
 />
从阻塞的角度来看,dubbo-go 侧调度延迟 Top 显示,排名靠前的依次是互斥锁 `Unlock`(23.89%)、响应处理 
`endStream`(22.14%)、`processHeaders`(20.14%)、条件变量 `Signal`(15.86%)、channel 接收 
`chanrecv1`(13.01%)。对照三件套逐一解释:`chanrecv1`(13.01%)是 channel 接收等待,io.Pipe 
交接(写端等读端取走数据)与 
`BlockUntilResponseReady`(`<-responseReady`,`duplex_http_call.go` 
L261-262)均属此类;`Signal`(15.86%)为条件变量唤醒,不在 io.Pipe 交接路径上,responseReady 经 
`close()` 触发的是 channel 接收而非条件变量唤醒(`duplex_http_call.go` L274),现代 io.Pipe 内部亦为 
channel 同步、无 
sync.Cond;`Unlock`(23.89%)为响应读取路径(`transportResponseBody.Read`)上的锁竞争。

gRPC 侧调度延迟集中为 `operateHeaders`(90.67%,等待响应头到达)这一个合理等待点,无管道相关阻塞。

也可以与前两个小节数据印证:4.1 显示 io.Pipe 交接产生约 61% 的阻塞 goroutine,本节调度延迟栈表明这些 goroutine 
的阻塞真实发生;4.2 显示 syscall 占 CPU 26.57%,本节中 dubbo-go 的 syscall 阻塞总时长 8.14s、gRPC 为 
3.43s。CPU 占比与阻塞时长同时偏高,说明该开销在执行与阻塞两个维度都存在。

三小节的三项验证分别从机制存在、CPU 占比、调度阻塞三个角度出发,共同指向 io.Pipe 交接带来的性能瓶颈。

### 交叉验证的总结

| 采集内容                                     | 测试结果                               
                                                                                
    | 结论                                 |
| ---------------------------------------- | 
----------------------------------------------------------------------------------------------------------------------
 | ---------------------------------- |
| goroutine 分布(io.Pipe 两端 / makeRequest 栈) | dubbo-go 共 312:makeRequest 99 / 
io.Pipe 写 90 / writeRequest 98 / BlockUntilResponseReady 8;gRPC 共 
114(waitOnHeader 99) | **管道栈**仅 dubbo-go 存在,总数比约 **2.7x** |
| syscall / futex / mallocgc 占比            | dubbo-go:syscall **26.57%** / 
futex 12.16% / mallocgc 9.39%;gRPC:syscall 10.86% / futex 7.98% / mallocgc 
18.18%        | **syscall 与 futex** 均显著高于 gRPC     |
| 调度延迟栈、syscall 阻塞时长                       | dubbo-go 调度栈含 chanrecv1(io.Pipe 交接 
/ responseReady 等待)等 channel 等待;syscall 阻塞 8.14s vs gRPC 3.43s                  
    | io.Pipe 交接进入调度栈,**阻塞时长**约 2.4x     |

## 五、方案设计与演进

### 5.1 问题定位

源码定位将固定开销锁定在 `duplexHTTPCall` 每次 unary 调用创建的 `io.Pipe`(`duplex_http_call.go` 
L76)与 `go makeRequest()` goroutine(L267)dubbo-go 独有、gRPC 完全缺失的两项开销。

### 5.2 设计目标

在保证 streaming(client/server/bidi)行为完全不变的前提下,为 unary 建立一条无 `io.Pipe`、无每请求 
goroutine 的发送路径。

### 5.3 四个设计优化点

- **O1 同步直发**:unary 请求体一次就绪,不走流式交接。`MarshalAppend` 直接写入池化 buffer 作为 HTTP 
body,同步 `httpClient.Do`。消除 pipe 分配、goroutine、channel 同步(futex)与 pipe 交接 memmove。
- **O2 池化流式响应**:响应用池化 buffer 流式读取(对齐生产 `tripleUnaryUnmarshaler` 的 `ReadFrom` + 
bufferPool 模式),避免 `io.ReadAll` 全量分配;gzip 响应按 `Content-Encoding` 解压。
- **O3 Content-Length 参数**:请求是否声明 `Content-Length` 作为 A/B 参数。实测启用 CL 
全面更优(服务端按声明长度预分配 `http2dataBuffer`,避免动态扩容),决策为启用。
- **O4 fastPathBody 零分配 body 包装**:零额外分配 io.ReadCloser 替代 `io.NopCloser` + 
`bytes.Reader` 双重包装,消除 body 包装层的对象分配,从而使得 B/op/allocs 降低。

### 5.4 回滚与防御性设计

方案涉及请求行为模型改动(风险中高),提前应对风险的防御性设计如下:

1. **风险兜底**:
   - ① **一键回滚**:TripleConfig `unary-fast-path` 
配置开关控制(`protocol_config.triple.unary-fast-path`),默认关闭;

     已开启的业务若升级后出事故,回滚操作仅三步:配置文件把 `unary-fast-path` 改为 `false`(或删除该配置)→ 重启 → 
回到升级前的 duplex 基线,行为与升级前完全一致(wire 兼容 + 共享 marshaler,差异面最小),零代码变更(设计为配置入口 + 
`WithUnaryFastPath` option 双通道)。
   - ② **行为差异面最小**:快路径复用协议层现有 marshaler/unmarshaler 与 bufferPool,与社区路径共享编解码、压缩与 
header 语义;
   - ③ **上线前全量回归**:全量回归 + 互通测试 + 四象限 A/B 对照(duplex/fastpath × gzip/无 gzip)。
2. **请求体 buffer 延迟回收(防竞态)**:`CloseWrite` 发起请求后如果不立即回收 body buffer,x/net/http2 的 
bodyWriter 后台 goroutine 可能晚于 `Do` 返回(服务端提前响应时),从而触发 `http2: response body 
closed` 竞态;

   设计应当统一在 `CloseRead`(响应处理完)后回收,**且回收前需确认后台 bodyWriter 已退出**:服务端提前响应(非 2xx / 
404 / 鉴权失败)时其仍在读未发完的请求体 buffer,对照 x/net/http2 v0.56.0 
源码,`abortRequestBodyWrite` 触发的异步 `Close()` 对 no-op 无效,**可以采用委托 transport 对 
Close 的恰好一次回调 + 互斥锁**;并且上线前 `go test -race` 复现验证。
3. **错误处理链对齐**:沿用 `duplexHTTPCall` 完整错误包装链(context 取消、h2c/gRPC misuse 
提示、RST\_STREAM 映射、`CodeUnavailable` 兜底),错误码与提示行为与社区路径一致。
4. **并发安全**:错误状态互斥锁保护,`BlockUntilResponseReady` 就绪前不放行读取。
5. **请求体重放(GetBody,重试兜底)**:为请求设置 GetBody 与加锁 Rewind(Seek 0 回绕),传输层连接失败时可按 HTTP 
重试语义重放请求体(unary 幂等语义下安全)实现时配套重放路径测试。
6. **规划内的回归测试**:(约22个)
   - **配置接线层**:配置 round-trip 不丢字段(yaml/json/property 三通道读回 + Clone);URL 
attribute 类型断言错误不 panic;gRPC 协议收到开关字段无副作用;healthClient 固化走 duplex(不受开关影响);
   - **分支路由层**:`StreamType` 零值(=Unary 0b00)命中快路径、ClientStream/ServerStream 仍走 
duplex,断言防回归;
   - **核心实现层**:
     - `unaryRequestBody`(归还竞态核心):Read/Close 并发(`-race` 复现服务端提前响应 abort 
场景)、Close 幂等(防 buffer 双归还污染池)、Close 后 Read 返回 EOF;
     - `makeRequest`:CloseWrite 幂等(sendOnce 保证 Do 恰好一次)、空 body(NoBody + 
Content-Length=0 跳过写)往返、错误链与 duplex 一致(h2c 提示/RST/Unavailable 
兜底)、validateResponse 失败后响应体由 CloseRead 关闭;
     - `Write`:多段累积 + Content-Length 精确、SetError/ctx 取消后停止累积;
     - `Read/CloseRead`:CloseRead 后 Read 返回包装错误、CloseRead 幂等、错误响应路径不 panic;
   - **端到端**:fastpath vs duplex 同请求响应一致性(`-race`)、并发 unary 调用 buffer 
池化并发安全(`-race`)、Content-Length wire 断言(中间件层拦截:unary 请求 CL≥0、streaming 请求 
CL≤0)、GetBody 重放路径(服务端回 `Connection: close` 触发 GOAWAY,断言 transport 调用 GetBody 
重放请求体)。

### 5.5 备选方案

- **方案一(unary 快路径)**:结构改造,消除 pipe + goroutine。收益面覆盖全部 unary 场景。
- **方案二(写路径合并)**:局部微调,将 envelope 的两次写合并为一次。注意 `envelopeWriter` 仅 gRPC 
协议路径使用,不属于 Triple unary 写路径,与方案一优化的是两条独立路径,收益边际,仅作兜底。
- **组合**:方案一为主,方案二兜底/补充

### 5.6 设计依据

1. **原型实测**:方案在 4 档报文全部正收益;备选方案二收益在方案一实现后会被吸收大半,不单独实施,只做兜底补充。
2. **独立开关(即时回退)**:TripleConfig `unary-fast-path` 配置开关(默认 
false)控制,默认关闭,线上异常改配置重启即可回退社区路径,零代码变更。
3. **范围隔离与互通**:仅 unary 走快路径,streaming 保留 `duplexHTTPCall`(上游 #611 同样只做 unary 
特化),互通/流式回归不受影响;快路径仅替换 Triple unary 的请求发送路径,路由仅按 StreamType 分支,不区分底层 HTTP 
版本,HTTP/1.1 与 HTTP/2 下的 Triple unary 均命中快路径;gRPC 协议(独立实现)与 streaming(StreamType 
非 unary)走原路径;wire 字节级与 duplex 一致(POST + Content-Length + Content-Type 等 header 
语义不变);跨语言互通仅依赖标准 HTTP 语义,无自定义字段。server-streaming(请求体同样一次性写完)纳入快路径列为后续评估项,涉及 
trailer 语义验证,不阻塞本期交付。
4. **上游论据(connect-go 官方)**:triple\_protocol 包源自 
connect-go(duplex\_http\_call\_test.go 版权头与 `fixed upstream in connect-go 
v1.19.2` 注释可证)。connect-go 在 issue #609 中明确承认:所有 RPC 共用 `duplexHTTPCall` 对 unary 
是 overkill,其中的同步机制、每请求额外 goroutine 与 `io.Pipe` 对 unary 是 **pure overhead**,并通过 
PR #611与#649 落地 unary 专用发送流程(`sendUnary`:同步直发 + ContentLength + 
payloadCloser),与本方案 O1 方向一致。**上游实现面向 connect-go 通用协议,不会覆盖 Triple 
协议细节,本方案实现时需做以下本地化适配**:
   - **架构差异与权衡适配**:上游改造 duplexHTTPCall 本体。它在 Send 方法内部按请求类型分支,只有流式请求才创建 
io.Pipe,一个类承载所有 RPC 类型。好处是代码更优雅、**发送入口单一、抽象层级高**;

     代价是改动波及所有 RPC 共用的类,流式请求同样受影响,且 unary 特化与流式逻辑耦合在同一个类里,后续难以单独演进,没有灰度和开关。

     本方案采用接口抽象加分支路由:**duplexHTTPCall 原封不动**,NewConn 按 RPC 类型把 unary 路由到新建的 
unaryFastPathCall。**流式语义不受影响,快路径可以独立演进**,后续补 GetBody 重试、覆盖 server-streaming 
都不需要触碰 duplex;开关关闭时走原类,快路径零开销,与一键回滚策略适配。

     代价是多一个类、多一层接口分发,两个实现的行为需要持续对齐,这部分由 5.4 第 6 点的 Parity 测试兜底。
   - **复用现有组件**:O1 直接接入 triple\_protocol 现有 marshaler/unmarshaler 与 
bufferPool(O2 对齐生产 `tripleUnaryUnmarshaler` 的 `ReadFrom` 模式),而非照搬上游 
`payloadCloser` 的独立分配路径;
   - **协议行为继承**:Content-Encoding / Accept-Encoding / Trailer 语义完全不变,gzip 
默认行为与生产 connect 客户端一致;
   - **Content-Length(O3)**:上游用 `ContentLength` 做流式关闭优化,本方案将其扩展为 A/B 
参数,实测启用全面更优,决策为启用。
   - **请求体归还机制本地化**:上游 `sendUnary` 用 payloadCloser 包装上层传入的 
payload,不归还任何池(所有权在上层),Release 清引用后 Read 返回 EOF 自然终止;本方案请求体来自协议层 bufferPool(上限 
8MiB / 初始 512B,对齐生产 buffer\_pool.go),归还必须回到池中,因此实现 `unaryRequestBody` 加锁 
Read/Close,归还委托 transport 对 Close 的恰好一次回调(正常读完或 abort 时),与后台 bodyWriter 
读互斥,杜绝跨请求 buffer 污染。
5. **gzip 对齐(对齐生产 wire 形状)**:生产客户端默认发 `Accept-Encoding: gzip`,服务端默认 gzip 
压缩响应。原型早版未对齐,2MiB 档慢 2.4x;对齐后反超 -38%。快路径走生产同一客户端栈,天然带该 header。
6. **pool 对齐生产配置**:buffer 上限对齐生产 `buffer_pool.go`(8MiB,初始 512B),避免大 buffer 
被池丢弃导致每次重新分配。

### 5.7 方案原型演进实测

被测是构造的简化原型,指标是单请求耗时 ns/op;协议内 `go test -benchmem`,httptest h2c 服务;基线是同轮 duplex 
社区版

| 版本 | 128B   | 1024B  | 16384B | 2MiB      | 关键变更                              
   |
| -- | ------ | ------ | ------ | --------- | 
------------------------------------ |
| v1 | -      | -      | -      | +约100% ↓↓ | 裸原型,无池化                           
   |
| v2 | -32% ↑ | -25% ↑ | -13% ↑ | +107% ↓↓  | 引入池化(上限 1MiB,2MiB allocs 1,147)   
   |
| v3 | -24% ↑ | -29% ↑ | +12% ↓ | +139% ↓↓  | pool 对齐生产 8MiB(2MiB allocs 降至 
1,020) |
| v4 | -37% ↑ | -43% ↑ | -52% ↑ | -38% ↑    | gzip 对齐(生产默认 Accept-Encoding)     
   |

> 注:① 表中数值 = 单请求耗时相对**同轮** duplex 基线的变化率(负 = 更快、正 = 
> 更慢),每轮跑出的基线值不同,**跨行数值不具可比性**,仅看各版本内的档位趋势;

## 六、方案验证与实施

### 6.1 生产落地改动点

正式方案当前设计以**接口抽象 + 分支路由**的方式落地:`tripleUnaryClientConn` 持有的具体调用从 
`*duplexHTTPCall` 抽象为 `unaryClientCall` 接口,`NewConn` 按 RPC 类型路由,unary 走新实现的 
`unaryFastPathCall`(同步直发),streaming 与开关关闭时保留原 `duplexHTTPCall`。协议层 
marshaler/unmarshaler、bufferPool、错误链与 header 语义全部复用,改造对协议行为零侵入。

#### 6.1.1 改动范围

1. **新增** `unary_fastpath.go`:`unaryClientCall` 接口(`tripleUnaryClientConn` 依赖的 
duplex 
方法子集:Write/Read/Header/CloseWrite/CloseRead/BlockUntilResponseReady/SetValidateResponse)+
 `unaryFastPathCall` 实现 + `unaryRequestBody`(加锁的池化归还 body)。

   四个优化点全部放在这里:O1 消除 io.Pipe + 每请求 goroutine(同步直发)、O2 复用现有 
marshaler/unmarshaler 与 bufferPool、O3 显式 Content-Length、O4 零分配 body 包装。
2. **`NewConn`** **分支**:`spec.StreamType == StreamTypeUnary && c.UnaryFastPath` 
走 `newUnaryFastPathCall(ctx, httpClient, url, spec, header, bufferPool)`,否则走 
`newDuplexHTTPCall`。**范围隔离**:streaming(client/server/bidi)与开关关闭时路径与社区完全一致,零改动。
3. **调用对象接口化**:`tripleUnaryClientConn.call` 从具体类型改为 `unaryClientCall` 
接口,marshaler(`writer: call`)/ unmarshaler(`reader: call`)/ validateResponse 注入 
/ bufferPool 全部复用,社区与快路径共用同一套编解码、压缩与 header 语义(行为差异面最小)。
4. **请求发送语义变化**:duplex 的 `Write` 写 io.Pipe → 每请求 goroutine 后台 `Do` 改为 fastpath 
的 `Write` 累积 pooled buffer(零网络触碰)→ `CloseWrite` 同步 `Do`,请求体经 `unaryRequestBody` 
直接引用 pooled buffer,无管道交接。
5. **body 归还机制变化**:duplex 请求体随 pipe 读端流入 http2、无池化归还;fastpath 归还委托 
`unaryRequestBody.Close`(x/net/http2 对 `reqBody.Close()` 恰好调用一次:正常读完或 abort 
时),内部互斥锁保证与后台 bodyWriter 的读不竞态,彻底规避跨请求 buffer 污染。
6. **一键回滚开关**:`WithUnaryFastPath()` option(option.go,默认关闭)→ 
`clientConfig.UnaryFastPath`(client.go)→ 
`protocolClientParams.UnaryFastPath`(protocol.go)→ NewConn 分支;生产入口 
`TripleConfig.unary-fast-path` 配置(global/triple\_config.go,默认 false)经 
`newClientManager` 注入 option。

#### 6.1.2 流程图

**改造前(社区** **`duplexHTTPCall`,开关关闭时 = 默认路径), 每请求两个执行上下文**

```mermaid
flowchart TB
    subgraph caller["caller goroutine"]
        direction TB
        CC["tripleUnaryClientConn"] --> DC["duplexHTTPCall"]
        DC -->|"Write ① ensureRequestMade() 启动后台 goroutine"| GO["go 
makeRequest()"]
        DC -->|"Write ② pipeWriter.Write(data)(阻塞)"| PW["pipeWriter"]
        DC -->|"Receive: BlockUntilResponseReady() 阻塞等待"| READY["responseReady"]
    end
    subgraph bg["makeRequest goroutine(每请求一个)"]
        direction TB
        MR["makeRequest: httpClient.Do(request)"] -->|"req.Body = pipeReader 
读端"| PR["pipeReader"]
        PR --> T["http2 transport"]
    end
    caller ~~~ bg
    GO -. "sendRequestOnce.Do 派发" .-> MR
    PW -. "io.Pipe 无缓冲交接(写端阻塞至读端消费)" .-> PR
    T -. "Do 返回 → close(responseReady)" .-> READY
    T -. "响应体" .-> DC
```

每请求固定开销:① io.Pipe 交接 ② makeRequest goroutine ③ responseReady channel 跨 
goroutine 等待

**改造后(本方案** **`unaryFastPathCall`,`unary-fast-path`** **开关启用时), 单执行上下文同步直发**

```mermaid
flowchart TB
    subgraph caller2["caller goroutine(全程同步,单执行上下文)"]
        direction TB
        CC2["tripleUnaryClientConn"] --> FP["unaryFastPathCall"]
        FP -->|"Write: bufferPool.Get() → body.Write 累积"| BUF["pooled buffer"]
        FP -->|"CloseWrite: sendOnce.Do(makeRequest) 同步执行"| MR2["makeRequest"]
        MR2 -->|"Content-Length 声明(O3)"| CL["Content-Length"]
        MR2 -->|"Body = unaryRequestBody(锁保护归还)"| RB["unaryRequestBody"]
        MR2 -->|"httpClient.Do(request)"| T2["http2 transport"]
        T2 -. "响应(responseReady 已就绪,Receive 不阻塞)" .-> FP
        RB -. "Close → bufferPool.Put" .-> BUF
    end
    classDef diff fill:#ffe0b2,stroke:#e65100,stroke-width:2px;
    class BUF,MR2,CL,RB diff;
```

(突出色块 = 相对 duplex 的差异点:去 pipe 改池化缓冲、去后台 goroutine 改同步发送、新增 Content-Length 
声明、新增锁保护归还)

该路径由 `unary-fast-path` 开关启用(**默认关闭**,开启方式:`WithUnaryFastPath()` option 或 
`protocol_config.triple.unary-fast-path: 
true`),异常时改配置重启即可回退到上方社区路径。对比上方社区路径:**消除** io.Pipe + makeRequest goroutine + 
responseReady 等待;**新增** pooled buffer 累积 + 显式 Content-Length(O3)+ 锁保护归还。

#### 6.1.3 组件职责与调用关系对照

| 组件                                       | 改造前(duplex)                        
         | 改造后(fastpath)                                    | 变化                
        |
| ---------------------------------------- | 
------------------------------------------- | 
------------------------------------------------ | ------------------------- |
| `tripleUnaryClientConn`                  | 持有 `*duplexHTTPCall`               
         | 持有 `unaryClientCall` 接口                          | 接口化解耦,改动收敛于 
NewConn 一行分支  |
| marshaler(`tripleUnaryRequestMarshaler`) | `writer = duplexHTTPCall`          
         | `writer = unaryFastPathCall`                     | 复用,仅 writer 实现替换  
        |
| unmarshaler(`tripleUnaryUnmarshaler`)    | `reader = duplexHTTPCall`          
         | `reader = unaryFastPathCall`                     | 复用                
        |
| 请求体承载                                    | io.Pipe(读端交 http2,写端阻塞)            
         | pooled buffer 直接引用                               | 消除管道交接            
        |
| 请求发起                                     | 每请求 goroutine 异步 `Do`              
         | `CloseWrite` 同步 `Do`                             | 消除 goroutine + 
channel 等待 |
| Content-Length                           | 未知(流式管道)                           
         | 显式声明(O3)                                         | 新增,服务端预分配         
        |
| body 归还                                  | 无池化                                
         | `unaryRequestBody.Close` → `bufferPool.Put`(锁保护) | 新增(#F2)           
        |
| 错误链                                      | context / h2c / gRPC 提示 / RST / 
Unavailable | 同链逐行对齐                                           | 不变             
           |
| header / gzip / trailer 语义               | 原样                                 
         | 原样                                               | 不变                
        |

#### 6.1.4 调用序列对比(同一 unary 请求)

- **改造前**:`Send`(写 pipe) → `CloseWrite`(EOF 通知) →〔后台 goroutine:读 pipe + `Do` + 
close responseReady〕→ `Receive`(`BlockUntilResponseReady` 等) → `CloseResponse`
- **改造后**:`Send`(累积 buffer) → `CloseWrite`(同步 `Do` + 直接读 buffer) → 
`Receive`(直接读 response,无等待) → `CloseResponse`

调用序列形态完全不变(协议层 `Client` 与 marshaler/unmarshaler 无需任何改动),仅每次 RPC 内部的执行上下文由 2 
个收敛为 1 个。

以下为快路径核心实现(`unary_fastpath.go`):`unary_fastpath_bench_test.go` 为协议内 A/B 基准。

### 6.3 原型代码:unary 快路径(A/B 两版原型测试代码)

原型通过测试代码提炼社区原版与设计版 triple-unary 的关键逻辑进行 A/B 验证。两版原型共用同一服务端与报文档位(协议内 bench 走 
httptest h2c 服务端、端到端走真实服务端 :20000),仅请求发送路径不同,形成 A/B 对照组。

**当前原型(对照组)**:DubboGoClient(dubbo\_client.go),完整 dubbo-go 框架栈,unary 经 
duplexHTTPCall:io.Pipe 管道交接、每请求 makeRequest 后台 goroutine、responseReady 信号等待

```go
// 构造:完整 dubbo-go 框架客户端(NewDubboGoClient)
cli, err := client.NewClient(
        client.WithClientNoCheck(),
        client.WithClientSerialization("protobuf"),
        client.WithClientParam(constant.SerializationKey, "protobuf"),
        client.WithClientParam("compression", "none"),
)
service, err := benchmark.NewTripleBenchmarkService(cli,
        client.WithURL(fmt.Sprintf("tri://%s/%s", addr, 
benchmark.BenchmarkServiceName)),
        client.WithSerialization("protobuf"),
        client.WithParam(constant.MaxCallRecvMsgSize, "16MB"),
        client.WithParam(constant.MaxCallSendMsgSize, "16MB"),
        client.WithParam("compression", "none"),
)

// 每次调用:内部进入 duplexHTTPCall,io.Pipe 交接、makeRequest goroutine、responseReady 等待
func (c *DubboGoClient) unaryCall(ctx context.Context) error {
        req := &benchmark.BenchmarkRequest{Payload: c.payload}
        _, err := c.client.UnaryCall(ctx, req)
        return err
}
```

**设计优化原型(实验组)**:ProtoFastPathClient(proto\_fastpath\_client.go),raw HTTP/2 直发,无 
duplexHTTPCall、无 io.Pipe、无每请求 goroutine(完整实现):

```go
func (c *ProtoFastPathClient) Call(ctx context.Context) error {
        // O1 池化 marshal:MarshalAppend 直接写入池化 buffer
        reqBuf := c.pool.Get().(*bytes.Buffer)
        data, err := proto.MarshalOptions{}.MarshalAppend(reqBuf.Bytes()[:0],
                &benchmark.BenchmarkRequest{Payload: c.payload})
        if err != nil {
                c.put(reqBuf)
                return fmt.Errorf("marshal request: %w", err)
        }

        // O4 零分配 body:fastPathBody 直接引用 marshal 结果,不经 bytes.Reader + NopCloser
        req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.url, 
&fastPathBody{data: data})
        if err != nil {
                c.put(reqBuf)
                return fmt.Errorf("new request: %w", err)
        }
        req.Header.Set("Content-Type", "application/proto")
        req.ContentLength = int64(len(data)) // O3 显式 Content-Length

        resp, err := c.httpClient.Do(req) // O1 同步直发:无 io.Pipe、无后台 goroutine
        if err != nil {
                c.put(reqBuf)
                return fmt.Errorf("do request: %w", err)
        }
        if resp.StatusCode != http.StatusOK {
                resp.Body.Close()
                c.put(reqBuf)
                return fmt.Errorf("unexpected status %d", resp.StatusCode)
        }

        // O2 池化读响应:ReadFrom 入池化 buffer,对齐 tripleUnaryUnmarshaler
        respBuf := c.pool.Get().(*bytes.Buffer)
        body := io.Reader(resp.Body)
        if strings.EqualFold(resp.Header.Get("Content-Encoding"), "gzip") {
                gz, gzErr := gzip.NewReader(body)
                if gzErr != nil {
                        resp.Body.Close()
                        c.put(reqBuf)
                        c.put(respBuf)
                        return fmt.Errorf("new gzip reader: %w", gzErr)
                }
                defer gz.Close()
                body = gz
        }
        if _, err := respBuf.ReadFrom(body); err != nil {
                resp.Body.Close()
                c.put(reqBuf)
                c.put(respBuf)
                return fmt.Errorf("read response: %w", err)
        }
        resp.Body.Close()
        c.put(reqBuf) // unary 语义:服务端读完请求体才响应,此回收点安全

        var out benchmark.BenchmarkResponse
        if err := proto.Unmarshal(respBuf.Bytes(), &out); err != nil {
                c.put(respBuf)
                return fmt.Errorf("unmarshal response: %w", err)
        }
        c.put(respBuf)
        return nil
}
```

验证点:

- 无 io.Pipe 分配,无 makeRequest goroutine;
- `MarshalAppend` 单次拷贝入池化 buffer,消除 `codec.Marshal` + `bytes.NewBuffer(raw)` 
二次拷贝;
- 响应按 `Content-Encoding: gzip` 手动解压,协议语义与生产一致;
- `ContentLength` 声明后,服务端按长度预分配 `http2dataBuffer`,避免动态扩容。

### 6.4 方案原型 A/B 实测(协议内 bench,同栈同服务端)

**数据口径**:`go test -benchmem`(httptest h2c 服务端,`BenchmarkUnaryPathAB`),A/B 双方为同栈 
`tripleUnaryClientConn`(共享 marshaler/unmarshaler/bufferPool),仅发送路径不同:原版 
`duplexHTTPCall` vs 优化原型 `unaryFastPathCall`;count=3 取中位数,原始数据见 
`data/unary-fastpath-proto/ab_path_20260822.log`。QPS/P99 
依赖端到端固定并发压测口径,测试原型代码不产出该指标,故本表不列。

**6.4.1 128B**

| 指标        | duplex(原版) | fastpath(优化原型) | 变化     |
| --------- | ---------- | -------------- | ------ |
| allocs/op | 141        | **140**        | ≈0%    |
| B/op      | 30.6KB     | **27.6KB**     | -10% ↑ |

**6.4.2 1024B**

| 指标        | duplex(原版) | fastpath(优化原型) | 变化     |
| --------- | ---------- | -------------- | ------ |
| allocs/op | 139        | **139**        | 持平     |
| B/op      | 34.9KB     | **30.5KB**     | -13% ↑ |

**6.4.3 16384B**

| 指标        | duplex(原版) | fastpath(优化原型) | 变化    |
| --------- | ---------- | -------------- | ----- |
| allocs/op | 147        | **145**        | -1%   |
| B/op      | 165.8KB    | **150.9KB**    | -9% ↑ |

**6.4.4 2MiB**

| 指标        | duplex(原版) | fastpath(优化原型) | 变化     |
| --------- | ---------- | -------------- | ------ |
| allocs/op | 372        | **324**        | -13% ↑ |
| B/op      | 13.5MB     | **13.0MB**     | -4% ↑  |

- 同栈下内存指标改善温和:allocs 基本持平(协议层 duplex 与 fastpath 共享 bufferPool/marshaler),B/op 
小中包 -10%\~-13%、大包 -4% 上下。发送路径优化的主要收益在延迟/吞吐侧,内存侧不是本轮重点。
- 第 4 项优化 `fastPathBody`:零额外分配 io.ReadCloser 替代 `io.NopCloser`+`bytes.Reader` 
双重包装,是使方案原型 B/op/allocs 由负转正的关键

## 七、预估收益

### 7.1 生产中影响因素(预估收敛要素)

方案原型收益(协议层发送路径净收益)是优化基点,生产集成后受以下要素影响,预估时需计入:

1. **端到端摊薄**(最主要):协议内 A/B 
为同栈对比(6.4),收益即协议层发送路径净收益;端到端叠加框架栈/网络/服务端调用链耗时,延迟下降比例被摊薄,QPS 提升收敛至低两位数百分比量级。
2. **协议栈差异**(收敛):原型 A/B 走 h2c(HTTP/2 cleartext)测试服务端,生产**通常**走 HTTP/2(Triple 
主要部署形态,快路径本身不区分 HTTP 版本);HTTP/2 连接层为 dubbo-go 与 gRPC 共有部分,不在本次优化范围。
3. **gzip 压缩开关**(指标口径):用户开启压缩时,压缩路径自身临时分配会掩盖 B/op 账面收益;延迟与吞吐收益不受影响。
4. **Content-Length 依赖(O3)**(条件收益):快路径声明 Content-Length 后,依赖服务端按该长度预分配 buffer 
才吃到收益;该字段为 HTTP 标准语义,dubbo-go/gRPC/Java 服务端均兼容,风险低。
5. **业务负载与并发饱和**(收敛):真实调用带业务(0.5ms\~几ms/请求),io.Pipe 
固定开销占比被业务耗时摊薄;纯网络转发场景(业务≈0)才接近原型档位;流控/带宽天花板属预期。
6. **服务端调用链与带宽**(收敛):生产服务端带完整调用链(序列化/业务 
handler/下游依赖),响应时间以服务端为主,客户端发送路径优化被整体耗时稀释;大报文(1MiB)受服务端带宽天花板限制。
7. **GC 与长跑**(双向):生产长跑下 bufferPool 复用使 B/op 收益更稳定;GC 压力与内存池行为可能放大或缩小分配收益,取中性。
8. **P99 尾延迟**(收敛):io.Pipe + 每请求 goroutine 的调度(futex)唤醒抖动是客户端尾延迟来源之一,但端到端 P99 
叠加服务端尾延迟与网络抖动,改善幅度通常低于均值延迟。

### 7.2 生产预期区间(预测)

结合 7.1 要素综合测算:

- 小报文(128B/1KiB,主战场):基线 4,425 → 预期 **4,779 \~ 4,868**(**+8% \~ +10%**),P99 同步收敛
- 大报文(1MiB):基线 122.9 → 预期 **133 \~ 135**(**+8% \~ +10%**),受服务端带宽天花板限制
- 综合判断:**QPS +8% \~ +10%,P99 同步改善**

**预估性能区间(预测,非实测)**:

| 指标     | 预估区间             | 预估依据                                              
                 |
| ------ | ---------------- | 
------------------------------------------------------------------ |
| QPS    | **+8% \~ +10%**  | 端到端叠加框架栈/网络/服务端耗时,收益摊薄后收敛至低两位数百分比 |
| P99    | **-5% \~ -15%**  | 端到端 P99 受服务端尾延迟稀释,改善幅度通常低于均值延迟 |
| 延迟     | **-10% \~ -25%** | 协议层消除 io.Pipe + 每请求 goroutine 固定开销                
                 |
| B/op   | **-15% \~ -35%** | 请求体池化单次拷贝,小报文更明显                                  
                 |
| allocs | **持平 \~ 微增**     | O3(Content-Length)引入少量 header 分配,换取服务端预分配收益       
                 |

## 八、参考链接

- 本机基准测试报告
- 社区基准测试报告(benchmark/README\_CN.md):tools/benchmark/README\_CN.md
- RPC 协议 Triple\&Dubbo java 基准测试:[RPC Protocol Triple\&Dubbo Benchmark Testing 
| Apache 
Dubbo](https://cn.dubbo.apache.org/en/overview/mannual/java-sdk/reference-manual/performance/rpc-benchmarking/#12-data-analysis)
- connect-go 上游 issue #609(unary 
专用流程设计讨论):<https://github.com/connectrpc/connect-go/issues/609>
- connect-go 上游 PR #611(Special case unary HTTP 
calls):<https://github.com/connectrpc/connect-go/pull/611>
- connect-go 上游 PR #649(Special case unary HTTP 
calls):<https://github.com/connectrpc/connect-go/pull/649>

***

## 许可

Apache License 2.0


GitHub link: 
https://github.com/apache/dubbo-go/discussions/3673#discussioncomment-18111020

----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: 
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to