Optimize precomputing of websocket event (#22465)
fmt.Sprintf was badly optimized and in cases of heavy traffic,
it was in the hot path and caused a lot of allocations.
We sacrifice readability for performance here.
```
10:46:40-~/mattermost/mattermost-server/model] benchstat base.txt new.txt
goos: linux
goarch: amd64
pkg: github.com/mattermost/mattermost-server/v6/model
cpu: Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
│ base.txt │ new.txt │
│ sec/op │ sec/op vs base │
EncodeJSON-8 2.061µ ± 1% 1.588µ ± 2% -22.93% (p=0.000 n=10)
│ base.txt │ new.txt │
│ B/op │ B/op vs base │
EncodeJSON-8 545.0 ± 0% 472.0 ± 0% -13.39% (p=0.000 n=10)
│ base.txt │ new.txt │
│ allocs/op │ allocs/op vs base │
EncodeJSON-8 6.000 ± 0% 3.000 ± 0% -50.00% (p=0.000 n=10)
```
```release-note
Writes to websocket now take 13% less memory
and also 22% faster per message.
```
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
10ae28e320
Коммит
2f1d964735
@@ -5,8 +5,8 @@ package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -281,7 +281,7 @@ func (ev *WebSocketEvent) EventType() string {
|
||||
|
||||
func (ev *WebSocketEvent) ToJSON() ([]byte, error) {
|
||||
if ev.precomputedJSON != nil {
|
||||
return []byte(fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, ev.precomputedJSON.Event, ev.precomputedJSON.Data, ev.precomputedJSON.Broadcast, ev.GetSequence())), nil
|
||||
return ev.precomputedJSONBuf(), nil
|
||||
}
|
||||
return json.Marshal(webSocketEventJSON{
|
||||
ev.event,
|
||||
@@ -294,9 +294,7 @@ func (ev *WebSocketEvent) ToJSON() ([]byte, error) {
|
||||
// Encode encodes the event to the given encoder.
|
||||
func (ev *WebSocketEvent) Encode(enc *json.Encoder) error {
|
||||
if ev.precomputedJSON != nil {
|
||||
return enc.Encode(json.RawMessage(
|
||||
fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, ev.precomputedJSON.Event, ev.precomputedJSON.Data, ev.precomputedJSON.Broadcast, ev.sequence),
|
||||
))
|
||||
return enc.Encode(json.RawMessage(ev.precomputedJSONBuf()))
|
||||
}
|
||||
|
||||
return enc.Encode(webSocketEventJSON{
|
||||
@@ -307,6 +305,20 @@ func (ev *WebSocketEvent) Encode(enc *json.Encoder) error {
|
||||
})
|
||||
}
|
||||
|
||||
// We write optimal code here sacrificing readability for
|
||||
// performance.
|
||||
func (ev *WebSocketEvent) precomputedJSONBuf() []byte {
|
||||
return []byte(`{"event": ` +
|
||||
string(ev.precomputedJSON.Event) +
|
||||
`, "data": ` +
|
||||
string(ev.precomputedJSON.Data) +
|
||||
`, "broadcast": ` +
|
||||
string(ev.precomputedJSON.Broadcast) +
|
||||
`, "seq": ` +
|
||||
strconv.Itoa(int(ev.sequence)) +
|
||||
`}`)
|
||||
}
|
||||
|
||||
func WebSocketEventFromJSON(data io.Reader) (*WebSocketEvent, error) {
|
||||
var ev WebSocketEvent
|
||||
var o webSocketEventJSON
|
||||
|
||||
@@ -5,6 +5,8 @@ package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -235,3 +237,18 @@ func TestWebSocketEventDeepCopy(t *testing.T) {
|
||||
})
|
||||
require.NotEqual(t, ev.data, evCopy.data)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
func BenchmarkEncodeJSON(b *testing.B) {
|
||||
message := NewWebSocketEvent(WebsocketEventUserAdded, "", "channelID", "", nil, "")
|
||||
message.Add("user_id", "userID")
|
||||
message.Add("team_id", "teamID")
|
||||
|
||||
ev := message.PrecomputeJSON()
|
||||
|
||||
enc := json.NewEncoder(io.Discard)
|
||||
for i := 0; i < b.N; i++ {
|
||||
err = ev.Encode(enc)
|
||||
}
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user