From e8cca18dce05a9e2516c073ec58ddc3b5cfe13b7 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Tue, 3 Sep 2024 20:52:03 +0100 Subject: [PATCH 1/4] event/event: reuse bufio.Reader Should avoid an expensive allocation at each loop. --- event/event.go | 16 +++++++++------- event/event_types.go | 4 +++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/event/event.go b/event/event.go index acea600..23f3d12 100644 --- a/event/event.go +++ b/event/event.go @@ -39,7 +39,10 @@ func NewClient(socket string) (*EventClient, error) { if err != nil { return nil, fmt.Errorf("error while connecting to socket: %w", err) } - return &EventClient{conn: conn}, err + return &EventClient{ + conn: conn, + reader: bufio.NewReaderSize(conn, bufSize), + }, err } // Close the underlying connection. @@ -56,7 +59,7 @@ func (c *EventClient) Close() error { func (c *EventClient) Receive(ctx context.Context) ([]ReceivedData, error) { buf := make([]byte, bufSize) - n, err := readWithContext(ctx, c.conn, buf) + n, err := c.readWithContext(ctx, buf) if err != nil { return nil, fmt.Errorf("error while reading from socket: %w", err) } @@ -96,15 +99,14 @@ func (c *EventClient) Subscribe(ctx context.Context, ev EventHandler, events ... } } -func readWithContext(ctx context.Context, conn net.Conn, buf []byte) (int, error) { +func (c *EventClient) readWithContext(ctx context.Context, buf []byte) (int, error) { done := make(chan struct{}) var n int var err error // Start a goroutine to perform the read go func() { - reader := bufio.NewReader(conn) - n, err = reader.Read(buf) + n, err = c.reader.Read(buf) close(done) }() @@ -113,9 +115,9 @@ func readWithContext(ctx context.Context, conn net.Conn, buf []byte) (int, error return n, err case <-ctx.Done(): // Set a short deadline to unblock the Read() - conn.SetReadDeadline(time.Now()) + c.conn.SetReadDeadline(time.Now()) // Reset read deadline - defer conn.SetReadDeadline(time.Time{}) + defer c.conn.SetReadDeadline(time.Time{}) // Make sure that the goroutine is done to avoid leaks <-done return 0, ctx.Err() diff --git a/event/event_types.go b/event/event_types.go index aa6254f..cc461e3 100644 --- a/event/event_types.go +++ b/event/event_types.go @@ -1,13 +1,15 @@ package event import ( + "bufio" "context" "net" ) // EventClient is the event struct from hyprland-go. type EventClient struct { - conn net.Conn + conn net.Conn + reader *bufio.Reader } // Event Client interface, right now only used for testing. From 34968a92abdbfa7165205c6752172209687afe3c Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Tue, 3 Sep 2024 21:17:00 +0100 Subject: [PATCH 2/4] event/event_test: add BenchmarkReceive --- event/event_test.go | 93 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/event/event_test.go b/event/event_test.go index 2f2126c..9599440 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -1,8 +1,11 @@ package event import ( + "bufio" "context" "errors" + "math/rand" + "net" "os" "testing" "time" @@ -11,6 +14,8 @@ import ( "github.com/thiagokokada/hyprland-go/internal/assert" ) +const socketPath = "/tmp/bench_unix_socket.sock" + type FakeEventClient struct { EventClient } @@ -245,3 +250,91 @@ func (h *FakeEventHandler) Screencast(s Screencast) { assert.Equal(h.t, s.Owner, "0") assert.Equal(h.t, s.Sharing, true) } + +func BenchmarkReceive(b *testing.B) { + go RandomStringServer() + + // Make sure the socket exist + for i := 0; i < 10; i++ { + time.Sleep(100 * time.Millisecond) + if _, err := os.Stat(socketPath); err != nil { + break + } + } + + c := assert.Must1(NewClient(socketPath)) + defer c.Close() + + ctx := context.Background() + + // Reset setup time + b.ResetTimer() + for i := 0; i < b.N; i++ { + c.Receive(ctx) + } +} + +// This function needs to be as fast as possible, otherwise this is the +// bottleneck +// https://stackoverflow.com/a/31832326 +const ( + letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = rand.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return b +} + +func RandomStringServer() { + // Remove the previous socket file if it exists + if err := os.RemoveAll(socketPath); err != nil { + panic(err) + } + + listener, err := net.Listen("unix", socketPath) + if err != nil { + panic(err) + } + defer listener.Close() + + for { + conn, err := listener.Accept() + if err != nil { + panic(err) + } + writer := bufio.NewWriter(conn) + + go func(c net.Conn) { + defer c.Close() + + for { + prefix := []byte(">>>") + randomData := RandomBytes(16) + message := append(prefix, randomData...) + + // Send the message to the client + _, err := writer.Write(message) + if err != nil { + return + } + } + }(conn) + } +} From 04eaeaac360923811e61db34ea8284c5727fc771 Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Tue, 3 Sep 2024 22:47:41 +0100 Subject: [PATCH 3/4] Revert "event/event: reuse bufio.Reader" This reverts commit e8cca18dce05a9e2516c073ec58ddc3b5cfe13b7. --- event/event.go | 16 +++++++--------- event/event_types.go | 4 +--- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/event/event.go b/event/event.go index 23f3d12..acea600 100644 --- a/event/event.go +++ b/event/event.go @@ -39,10 +39,7 @@ func NewClient(socket string) (*EventClient, error) { if err != nil { return nil, fmt.Errorf("error while connecting to socket: %w", err) } - return &EventClient{ - conn: conn, - reader: bufio.NewReaderSize(conn, bufSize), - }, err + return &EventClient{conn: conn}, err } // Close the underlying connection. @@ -59,7 +56,7 @@ func (c *EventClient) Close() error { func (c *EventClient) Receive(ctx context.Context) ([]ReceivedData, error) { buf := make([]byte, bufSize) - n, err := c.readWithContext(ctx, buf) + n, err := readWithContext(ctx, c.conn, buf) if err != nil { return nil, fmt.Errorf("error while reading from socket: %w", err) } @@ -99,14 +96,15 @@ func (c *EventClient) Subscribe(ctx context.Context, ev EventHandler, events ... } } -func (c *EventClient) readWithContext(ctx context.Context, buf []byte) (int, error) { +func readWithContext(ctx context.Context, conn net.Conn, buf []byte) (int, error) { done := make(chan struct{}) var n int var err error // Start a goroutine to perform the read go func() { - n, err = c.reader.Read(buf) + reader := bufio.NewReader(conn) + n, err = reader.Read(buf) close(done) }() @@ -115,9 +113,9 @@ func (c *EventClient) readWithContext(ctx context.Context, buf []byte) (int, err return n, err case <-ctx.Done(): // Set a short deadline to unblock the Read() - c.conn.SetReadDeadline(time.Now()) + conn.SetReadDeadline(time.Now()) // Reset read deadline - defer c.conn.SetReadDeadline(time.Time{}) + defer conn.SetReadDeadline(time.Time{}) // Make sure that the goroutine is done to avoid leaks <-done return 0, ctx.Err() diff --git a/event/event_types.go b/event/event_types.go index cc461e3..aa6254f 100644 --- a/event/event_types.go +++ b/event/event_types.go @@ -1,15 +1,13 @@ package event import ( - "bufio" "context" "net" ) // EventClient is the event struct from hyprland-go. type EventClient struct { - conn net.Conn - reader *bufio.Reader + conn net.Conn } // Event Client interface, right now only used for testing. From 62310e74b050fb2d7c6d4eb6907f0ff651f1513b Mon Sep 17 00:00:00 2001 From: Thiago Kenji Okada Date: Tue, 3 Sep 2024 22:49:32 +0100 Subject: [PATCH 4/4] Revert "event: use bufio.NewReader" This reverts commit e2b70b5aa15ed531c1d14f6e5b37d084c031fb6c. --- event/event.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/event/event.go b/event/event.go index acea600..3820306 100644 --- a/event/event.go +++ b/event/event.go @@ -1,7 +1,6 @@ package event import ( - "bufio" "context" "fmt" "net" @@ -103,8 +102,7 @@ func readWithContext(ctx context.Context, conn net.Conn, buf []byte) (int, error // Start a goroutine to perform the read go func() { - reader := bufio.NewReader(conn) - n, err = reader.Read(buf) + n, err = conn.Read(buf) close(done) }()