lizining1231 opened a new pull request, #3727:
URL: https://github.com/apache/dubbo-go/pull/3727
### Description
Fixes #3726 link #3718
### Description
The optional `marshalAppender` extension added there covered the IDL path
(`protoBinaryCodec`) and server-side non-IDL responses
(`tripleServerCodecSession`), but not the client-side non-IDL request path
(Java interop and generic calls), which goes through `protoWrapperCodec`. Every
unary call therefore encoded its arguments with `innerCodec.Marshal`, allocated
a separate buffer for the outer `TripleRequestWrapper`, and copied that buffer
into the transport.
This change brings `protoWrapperCodec` onto the same extension: the outer
wrapper is encoded directly into the caller-provided (pooled) buffer. It is
purely additive, codecs without the extension keep their exact previous
behaviour.
### Changes
`protocol/triple/triple_protocol/codec.go`:
- Add `MarshalAppend` to `protoWrapperCodec`, implementing the optional
`marshalAppender` interface. It delegates to
`proto.MarshalOptions{}.MarshalAppend`, so its output is byte-identical to
`Marshal`.
- Extract a shared `requestWrapper` helper so `Marshal` and `MarshalAppend`
build the same `TripleRequestWrapper` and can never drift apart.
- Inner payloads still come from `innerCodec.Marshal`, so the win is
confined to the outer wrapper's allocation and copy.
### Test
| Test | Description |
|---|---|
| `TestMarshalPerfTypeGuard` | Scope guard: the wrapper must implement
`marshalAppender` for both hessian2 and msgpack, plus a reverse guard that
hides the extension and forces the slow path |
| `TestMarshalPerfClientWrapperFastPath` | Both send wires reach
`MarshalAppend` exactly once (probe whose `Marshal` always fails), and the fast
path emits byte-identical output to the slow path across single / multi / POJO
/ bare / void / nil argument shapes |
| `TestMarshalPerfClientWrapperConcurrentSend` | 32 goroutines x 100 sends
sharing one `bufferPool`, race-free under `-race` |
| `TestMarshalPerfClientWrapperLargeBufferDropped` | Buffers grown beyond
the 8MiB recycle cap are dropped by the pool (asserting the pool's cap, not
byte equality) and leave no residue for the next small message; both wires |
| `TestNonIDLUnary_PublicEntry_MarshalAppendEffective` | Non-IDL unary calls
end to end through `NewClient` / `CallUnary` on {gRPC, Triple} x {hessian2,
msgpack} all reach the fast path |
| `BenchmarkWrapperCodecFastPath` / `SlowPath` | A/B benchmarks: hessian2
and msgpack inner codecs x 128B / 1KiB / 16KiB / 1MiB x both wires |
### Validation
#### pprof CPU Profile diff
<img width="1910" height="872" alt="image"
src="https://github.com/user-attachments/assets/9084ab5d-ee00-43db-8f11-5344489da732"
/>
#### Marshal Fast-Path Benchmark Results
**go test bench -bench=WrapperCodec**
**allocs/op reduced from 13 to 11**
| Message | Before | After |
| ------- | ------ | ----- |
| 128B | 12 | 10 |
| 1KiB | 13 | 11 |
| 16KiB | 13 | 11 |
| 1MiB | 13 | 11 |
Before, every op paid for the outer wrapper's own slice on top of the inner
codec's allocations. The fast path writes that wrapper straight into the pooled
buffer, so both allocations disappear.
**B/op reduced by up to -49.9% (msgpack inner codec)**
| Message | Before | After | Reduction |
| ------- | ------- | ------- | --------- |
| 128B | 1901 | 1649 | -13.26% |
| 1KiB | 4414 | 3186 | -27.82% |
| 16KiB | 38598 | 20098 | -47.93% |
| 1MiB | 2115543 | 1059381 | -49.92% |
The saving is exactly one payload-sized wrapper buffer, so B/op no longer
carries a second copy of the message and the gap widens with payload size. Note
that the outer wrapper always contains the inner-encoded bytes, so this path is
bounded at -50%; removing the inner copy needs append support from the inner
codecs and is out of scope here.
The same A/B with a hessian2 inner codec:
| Message | Before | After | Reduction |
| ------- | ------- | ------- | --------- |
| 128B | 884 | 627 | -29.07% |
| 1KiB | 3777 | 2539 | -32.78% |
| 16KiB | 82649 | 63997 | -22.57% |
| 1MiB | 7813072 | 6763493 | -13.43% |
hessian2's own encoder allocates far more, which dilutes the ratio; the
absolute saving is still one payload-sized wrapper buffer.
Reproduce:
```bash
# pprof
go test -run '^$' -bench
'^BenchmarkWrapperCodecSlowPath$/msgpack/1MiB/triple$' -benchtime=30000x \
-cpuprofile cpu_slow.out ./protocol/triple/triple_protocol/
go test -run '^$' -bench
'^BenchmarkWrapperCodecFastPath$/msgpack/1MiB/triple$' -benchtime=30000x \
-cpuprofile cpu_fast.out ./protocol/triple/triple_protocol/
go tool pprof -top -diff_base cpu_slow.out cpu_fast.out
# benchmark
go test -run '^$' -bench WrapperCodec -benchmem -count=3
./protocol/triple/triple_protocol/
```
### Checklist
- [x] I confirm the target branch is `develop`
- [x] I have run `make fmt` to format my code
- [x] I have run `make test` to run local tests
- [x] I have added tests that prove my fix is effective or that my feature
works
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]