mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-11 23:19:31 +00:00
perf(client): pool req-body buffer + manual http.Request with cached URL
Two changes that together cut allocs/call from 15 to 13 (client-internal bench) and per-call CPU from 600ns to 455ns (-24%) on the no-HTTP path: 1. Codec gets an optional BodyEncoder extension (MarshalTo io.Writer). When present, encodeJSONBody stream-encodes the request directly into a pooled *bytes.Buffer instead of allocating a [2-step] Marshal+Reader pair. DefaultCodec implements it via goccy/go-json.NewEncoder. 2. *Bot caches the parsed base URL on construction. buildRequest skips net/http.NewRequestWithContext for the common case and constructs *http.Request manually — clones the URL by value, sets the method path, and populates ContentLength + GetBody from the body's concrete type so RetryDoer's body-replay across attempts still works. Cross-library bench (sendMessage round-trip vs httptest.Server): -2 allocs/call (104 -> 102), bytes -1.2%, time within noise (real HTTP plumbing dominates). The CPU win is visible on synthetic stub-doer benches and translates to lower GC pressure on sustained-throughput workloads. Slow-path fallback preserved for codecs that don't implement BodyEncoder and for *Bot instances where url.Parse on the configured base failed — they take the original NewRequestWithContext path.
This commit is contained in:
+21
-1
@@ -1,7 +1,11 @@
|
||||
// Package client provides HTTP client primitives for the Telegram Bot API.
|
||||
package client
|
||||
|
||||
import "github.com/goccy/go-json"
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
// Codec encodes/decodes JSON payloads exchanged with the Telegram Bot API.
|
||||
// The default implementation wraps goccy/go-json. Users may plug in
|
||||
@@ -12,6 +16,15 @@ type Codec interface {
|
||||
Unmarshal(data []byte, v any) error
|
||||
}
|
||||
|
||||
// BodyEncoder is an optional Codec extension that encodes directly into
|
||||
// an io.Writer, skipping the intermediate []byte that Marshal returns.
|
||||
// Call uses this when present to avoid allocating the marshal result and
|
||||
// the bytes.Reader that wraps it; codecs without it fall through to
|
||||
// Marshal + bytes.NewReader.
|
||||
type BodyEncoder interface {
|
||||
MarshalTo(w io.Writer, v any) error
|
||||
}
|
||||
|
||||
// DefaultCodec wraps goccy/go-json. It is the zero-value safe default.
|
||||
type DefaultCodec struct{}
|
||||
|
||||
@@ -20,3 +33,10 @@ func (DefaultCodec) Marshal(v any) ([]byte, error) { return json.Marshal(v) }
|
||||
|
||||
// Unmarshal calls json.Unmarshal.
|
||||
func (DefaultCodec) Unmarshal(data []byte, v any) error { return json.Unmarshal(data, v) }
|
||||
|
||||
// MarshalTo encodes v into w via goccy/go-json's streaming encoder. The
|
||||
// trailing newline that Encoder appends is valid JSON whitespace and is
|
||||
// accepted by Telegram's parser.
|
||||
func (DefaultCodec) MarshalTo(w io.Writer, v any) error {
|
||||
return json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user