-
Notifications
You must be signed in to change notification settings - Fork 3.6k
add mutable configuration to optimize ReadMessage performance #843
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Most of the users use And for most of the users, it's a little complex to optimize the |
|
Hello! Does this mean that every connection will bring an additional constant memory overhead equal to the largest message read from the connection? Given that memory consumption of Gorilla WebSocket lib is one of the most difficult things to solve by itself at this point it's hard to justify additional overheads IMO. As you pointed by using NextReader it seems already possible to achieve a similar effect and avoid attaching additional slice of bytes to the connection. |
Yes. But performs much better. In the beginning, I use
Yes, |
@FZambia Here's also a simple benchmark for 1m-websocket-connections on low hardware such as server running on 4 cpu cores and costing only 2g mem, for more details please refer to the code and script: |
|
If worry about holding large buffer, can easily reset the buffer just before each calling for // Of course we can get the buffer from some pool
conn.SetMutableBuffer(make([]byte, InitReadBufferSize))
for {
if len(conn.MutableBuffer()) > MaxReadBufferSize {
// Maybe put the old conn.MutableBuffer() to some pool first
// Of course we can get the buffer from some pool
conn.SetMutableBuffer(make([]byte, InitReadBufferSize))
}
mt, msg, err := conn.ReadMessage()
if err {
//...
return
}
}That's still much easier than using |
|
This comment proposes an alternative to the PR. The benefits of this alternative compared to the PR are listed below. If buffer reuse is a problem that needs to be solved, then this alternative should be considered. Here's how to reuse a buffer using the package as is: This code is easy to write and understand, but bundling it up in a helper method might be nice: The helper method is: This proposal has the following benefits over the PR:
ReadMessage, ReadJSON, and my proposed ReadMessageTo should be helper functions instead of helper methods. Because we already have ReadMessage and ReadJSON methods, ReadMessageTo should be a method for consistency. Here's how to limit the size of the buffer: |
|
@mylo88
If users need to call the new method |
|
Thank you for the pull request! We will put it in the queue to review as soon as we have completed the project transition at the end of July! |
The performance of bytes.Buffer vs []byte is not a concern. Any extra runtime cost for using a bytes.Buffer is negligible compared to the cost of a single read to the network stack.
The number of lines that a developer writes to enable buffer reuse in the PR is shorter than what I propose, but lines of code is not all that should be considered. The overall complexity if an API should also be considered. The developer cost of understanding an API can outweigh the cost of writing a couple of extra lines of code. The PR adds a configuration option and a method that interact to fundamentally change how ReadMessage works. Action at a distance is complex. The PR adds significantly to the API surface area (four methods and one new configuration field). Even if these elements are easy to understand in isolation, the increased surface area adds to the cognitive load required to understand the package. (The reason that I said that ReadJSON, ReadMessage and ReadMessageTo should be standalone helper functions instead of methods is that this design reduces the API surface area for Conn. The reduced surface area makes Conn easier to understand.) When doing research this comment, I noticed that the PR does not add a Mutable field to Dialer. If the lack of symmetry between client and server is not intentional, then the omission shows how action at a distance can be complex. |
Since they implement similar things, why not use a short version?
I do agree with this point, and that's also the reason why do most developers use In other frameworks, or just I think there's no need to explain too much about this PR's code, but just 1 or 2 simple examples will be fine to show how to optimize performance and mem cost.
The configuration is easy if users read the comments, and no need to change other logic code if not mind the large buffer problem. I think it's not correct to say that
i didn't intend to add the |
|
There are at least two reasons why developers use ReadMessage:
I agree that the addition of the mutable switch does not break existing applications. My complaint is with the complexity exposed to the application developer. The switch fundamentally change the expectations for the return value from ReadMessage and this action at a distance is inherently complex. I am not disputing that the API in this PR can sufficiently documented. My complaint is that the API is complex compared to an alternative and that the API surface area hurts the overall usability of the package. Dialer and Upgrader are the respective client and server configurations for a connection. It's weird to add a field to one of these types when the configuration can apply to both client and server connections. If this PR does proceed, then the term "Mutable" in the exported API should be replaced by "ReuseReadMessageBuffer" or something similar. The term "Mutable" does not carry enough context. Is there evidence that a developer identified the buffer allocation in ReadMessage as a performance problem, but that developer was incapable of writing the code in my first comment? Without evidence, no change should be made to the API including my proposal. Additions to an API should carry their weight. |
|
Let's see gofiber: app := fiber.New(fiber.Config{
Immutable: true, // default is
})And If be afraid of complexity, it will be hard to go ahead.
For most of the users, performance is not a problem even if they use py or php.
Plz refer to |
|
Not only melody, but also other well-known frameworks which have lots of stars, should need higher performance but still use Do you know why they still use How to process ws message is just like I said, it's the most popular way: |
|
I agree that this PR adds a lot to the API surface while the only way to avoid unexpected memory growth is to use all those getters/setters. Simply setting mutable option to true is the hidden bomb which potentially may result into more issues. I can easily imagine 2x memory usage growth due to using the option. Proper usage of proposed API without memory issue requires too much knowledge about internals and all these getter/setter manipulations with per-connection buffer are unobvious. Having this type of API in Gorilla WebSocket may eventually be addressed as a problem in the library. At the same time this package already provides API to achieve the desired performance speedup using |
|
As long as more users can get benefits, it doesn't matter whether the pr is merged or not. I'll leave this pr opening for discussion until you guys make the final decision, then please feel free to close it. |
|
@jaitaiwan This PR should be closed. The comment by FZambia is a good summary of the problems with the PR. |

Summary of Changes
ReadMessageperformanceExplain
The common processes for the ws message is:
The current version,
ReadMessagecallsReadAll, which uses new buffer to read data every time.But we usually drop the buffer after step 2, and it wastes too much.
ReadAll's append is also slow when the message is bigger than 512:https://github.com/golang/go/blob/master/src/io/io.go#L702
https://github.com/golang/go/blob/master/src/io/io.go#L715
package tests passed.
I also add benchmarks for the new feature and got better performance compared with old usage: