-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
encoding/json: Encoder internally buffers full output #7872
Comments
Comment 2 by nicolashillegeer: > Yeah, suppose it could use a bufio.Writer instead of a bytes.Buffer. This could indeed save a useless copy, as also noted in this issue: https://golang.org/issue/5683 However, there are some small things. For example, I'm using an Encoder to encode straight into a go.net websocket. Due to the current implementation, the Encoder issues a Write once the entire message is completed. With a bufio.Writer, there could be several writes. The go.net WebSocket implementation takes every write as an entire message (as far as I can see from perusing the source). This would make my json possibly arrive in arbitrary pieces. Note that what I'm doing is not necessarily the appropriate usage of go.net WebSockets, it has websocket.JSON.Receive/Send methods to send and receive entire messages at a time. And I should be using that. But those methods use json.Marshal/json.Unmarshal under the hood, which cause buffer churn when used a lot (my use case is relatively high-throughput). Ideally the go.net WebSockets package would use Encoder/Decoder with a bytes.Buffer under the hood for the JSON.(Send|Receive) calls, reusing the buffer every time (either using a sync.Pool for the buffers or storing them in the websocket connection itself, since I believe those don't support concurrent writing anyway). I would not mourn too much if my current usage of go.net WebSockets stops working. |
I was rather shocked when I discovered this, actually. If my understanding is correct this means that JSON cannot be streamed to a file without it consuming large amounts of memory.
I highly agree with this approach. |
@slimsag, I don't understand. Each call to encoding/json.Encoder.Encode produces one Write. Even if Encode wrote more eagerly to its Writer, you still have the entire argument to Encode (the whatever interface{}) in memory. So it's at worst a linear factor of memory. This isn't the difference of being able to stream or not. Realistically if you want to stream JSON, you'd do something like:
... and that pattern works fine even today. Changing the internal buffering (this bug) has nothing to do with making it possible to programmatically generate the data to stream. That would a much more invasive API change, and not obviously better than the loop above. |
…o.Writer (addresses golang#7872)
This should satisfy the existing requirements (truncation on error), and limited buffering to enable streaming structures of unbounded/unknown size: https://github.com/ydnar/go/compare/jsonio |
@bradfitz I can see now that I misunderstood what this bug is about, my apologies! From the title "Encoder internally buffers full output" I had incorrectly thought all calls to Encode were buffered. I have re-read the issue understand now that only the data written by a single call to Encode is buffered, and so streaming works fine. |
CL https://golang.org/cl/13818 mentions this issue. |
I took a stab at this: https://golang.org/cl/13818 I don't think I'm really happy with the outcome though. Review / comments welcome. PS: This is my first attempt to contribute. |
Based on what @rsc wrote in https://golang.org/cl/13818, what we want to do here is to provide an opt-in mechanism for an Encoder to generate output incrementally. The corresponding change for a Decoder is #11046. |
Too late. Didn't happen for Go 1.9. |
Would an API like
A similar API change could be made for |
@rsc What do you think of Jason’s approach? |
@ianlancetaylor for further triage/thoughts. |
Related to #20169. |
Apart from the issues discussed in #20169 I don't see a reason to set a number of bytes to accumulate. I would just have an option along the lines of Note that the |
Do you have opinions about enabling
vs
Do any backward-compatibility concerns arise from changing |
There's no chaining today; please don't add any. |
I have seen multiple developers guarding against partial JSON by using a buffer pool. I myself wasn't aware Go did this. Perhaps the documentation could be updated to mention that on error, no partial encoding is written. Other languages have made us really defensive programmers, and It's really nice to see the forethought that Go has put into it's implementations (while not becoming overly abstracted and superfluous). |
Note for anyone who thinks they don't have to defend against partial writes with JSON responses: if you set |
Change https://golang.org/cl/135595 mentions this issue: |
@rsc: Can you elaborate on why allowing chaining isn't preferred? It can be nice for scoping down variables into only the scope where they're needed:
or
vs.
Chaining can also preserve vertical space in exchange for horizontal space, which most displays have more of anyway. I agree it can be abused and taken to an unhelpful extreme, but for simple/common cases it seems worthwhile, backward-compatible, and fairly straightforward to implement. |
I believe one of the complaints is that method chaining adds cognitive complexity by combining multiple differing transforms, actions, or side effects on the same line. In your example, you can also use block scoping. {
e := json.NewEncoder(r)
e.SetIndent("", " ")
if err := e.Encode(i); err != nil {
// uh oh
}
}
// e is undefined However, this is not normally an issue as both the compiler and tooling should yell at you. |
@xeoncross That's a reasonable stance. If that's too much cognitive overhead for you (or your code reviewer), you can always just not chain the methods -- the existing behavior would work the same whether or not If the stdlib enabled chaining, users who want it could use it in the scenarios where it makes sense to them. Users who don't want to use it, don't have to. 🤷♂ |
I've been looking closely at https://golang.org/cl/135595, and found some drawbacks. It has one confirmed bug (easily fixed), and one likely bug (unconfirmed), and introduces a drastic increase in allocs, and slows throughput. I think all of these problems are easily fixed, and I'd be happy to contribute a CL, but the existing CL has some outstanding discussion points (not least of which lead to #27735), but seems to have died. Would an additional CL be welcome at this point? Or should the existing CL be updated with the necessary fixes (which I can elaborate on)? |
Hi all, we kicked off a discussion for a possible "encoding/json/v2" package that addresses the spirit of this proposal. |
The text was updated successfully, but these errors were encountered: