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.
```
Этот коммит содержится в:
Agniva De Sarker
2023-03-13 22:32:12 +05:30
коммит произвёл GitHub
родитель 10ae28e320
Коммит 2f1d964735
2 изменённых файлов: 34 добавлений и 5 удалений

Просмотреть файл

@@ -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)
}
}