Skip to content
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

private/protocol/json/jsonutil: Use json.Decoder to decrease memory allocation #2115

Merged
merged 2 commits into from
Sep 7, 2018

Conversation

atsushi-ishibashi
Copy link
Contributor

@atsushi-ishibashi atsushi-ishibashi commented Aug 23, 2018

closed: #2114

https://gist.github.com/atsushi-ishibashi/2d7d13cf1df9a84251ed175816a04b14
The below shows that the new UnmarshalJSON decrease memory capacity by 50%.

//current
BenchmarkUnmarshalJSON-4    	 1000000	      2122 ns/op	    2928 B/op	      18 allocs/op
//new one
BenchmarkUnmarshalJSON2-4   	 1000000	      1654 ns/op	    1480 B/op	      17 allocs/op

Copy link
Contributor

@jasdel jasdel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this change together @atsushi-ishibashi Thanks for finding this optimization. We'll run this through our integration tests, but I don't immediately see any reason why we shouldn't be able to accept this change.

Copy link
Contributor

@jasdel jasdel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed an issue with checking for io.EOF from the Decode. I don't think these lines are needed.


if err := json.Unmarshal(b, &out); err != nil {
if err := json.NewDecoder(stream).Decode(&out); err != nil {
if err == io.EOF {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you find a case where the io.EOF was being returned, and it wasn't a parsing error? I wouldn't of expected Decode to return an EOF error if it successfully read the content.

I think the if err == io.EOF { block can be removed.

Copy link
Contributor Author

@atsushi-ishibashi atsushi-ishibashi Sep 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be compatible with the below in the previous code.

if len(b) == 0 {
	return nil
}

If there isn't if err == io.EOF {, the new UnmarshalJSON returns error with 0 bytes stream.

But I cloudn't confirm where the scope is the same:thinking:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atsushi-ishibashi - Oof, I really don't like relying on the io.EOF as it requires internal knowledge on how Decode works. However, I think we can accept this change with one minor tweak

err := json.NewDecoder(stream).Decode(&out)
if err == io.EOF {
    return nil
} else if err != nil {
    return err
}

It may be a good idea of not creating a new decoder every time either. We can let UnmarshalJSON handle the creation of the stream and pass that into unmarshalJSON. Let me know what you think of that!

// or maybe make this a method on an object? That way decoder doesn't need to be passed
// down
func unmarshalJSON(decoder json.Decoder, v interface{}, stream io.Reader) error {
    err := decoder.Decode(stream)
    // same logic as above
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like relying on the io.EOF as it requires internal knowledge on how Decode works.

Completely agree👍I'll modify the way of error handling following with your suggestion.

It may be a good idea of not creating a new decoder every time either.

I don't agree with passing decoder as an argument because it will change interface. Currently UnmarshalJSON is package's public fuction so one possible choice is that creating decoder as package variable in init fuction or variable initialization. But we have to consider that there is a way to reassign io.Reader to Docoder. As far as I examined in a few minutes, it doesn't seem to exist:thinking:
When I misunderstand your suggestion, please correct me!

Copy link
Contributor

@xibz xibz Sep 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm! Looks like UnmarshalJSON will ever only be called once! Forget I said anything :D

@jasdel jasdel added the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Sep 4, 2018

if err := json.Unmarshal(b, &out); err != nil {
if err := json.NewDecoder(stream).Decode(&out); err != nil {
if err == io.EOF {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atsushi-ishibashi - Oof, I really don't like relying on the io.EOF as it requires internal knowledge on how Decode works. However, I think we can accept this change with one minor tweak

err := json.NewDecoder(stream).Decode(&out)
if err == io.EOF {
    return nil
} else if err != nil {
    return err
}

It may be a good idea of not creating a new decoder every time either. We can let UnmarshalJSON handle the creation of the stream and pass that into unmarshalJSON. Let me know what you think of that!

// or maybe make this a method on an object? That way decoder doesn't need to be passed
// down
func unmarshalJSON(decoder json.Decoder, v interface{}, stream io.Reader) error {
    err := decoder.Decode(stream)
    // same logic as above
}

@jasdel jasdel changed the title Use decoder to decrease memory allocation private/protocol/json/jsonutil: Use json.Decoder to decrease memory allocation Sep 7, 2018
@jasdel jasdel merged commit f309516 into aws:master Sep 7, 2018
jasdel added a commit that referenced this pull request Sep 7, 2018
Adds PR #2115 to the pending change log.
@awstools awstools mentioned this pull request Sep 7, 2018
usrenmae pushed a commit to usrenmae/aws-sdk-go that referenced this pull request Sep 23, 2018
…llocation (aws#2115)

Updates the SDK's JSON protocol marshaler to use `json.Decoder` instead of `ioutil.ReadAll`. This reduces the memory unmarshaling JSON payloads by about 50%.

Fix aws#2114
usrenmae pushed a commit to usrenmae/aws-sdk-go that referenced this pull request Sep 23, 2018
Adds PR aws#2115 to the pending change log.
jasdel pushed a commit that referenced this pull request Sep 25, 2018
…ry allocation (#2141)

Update RESTJSON protocol unmarshaler to use json.Decoder instead of ioutil.ReadAll to reduce allocations.

Related to: #2115
jasdel pushed a commit that referenced this pull request Sep 25, 2018
… allocation (#2142)

Update JSONPRC protocol unmarshaler to use json.Decoder instead of ioutil.ReadAll to reduce allocations.

Related to: #2115
xibz pushed a commit to xibz/aws-sdk-go that referenced this pull request Oct 15, 2018
…llocation (aws#2115)

Updates the SDK's JSON protocol marshaler to use `json.Decoder` instead of `ioutil.ReadAll`. This reduces the memory unmarshaling JSON payloads by about 50%.

Fix aws#2114
xibz pushed a commit to xibz/aws-sdk-go that referenced this pull request Oct 15, 2018
Adds PR aws#2115 to the pending change log.
xibz pushed a commit to xibz/aws-sdk-go that referenced this pull request Oct 15, 2018
…ry allocation (aws#2141)

Update RESTJSON protocol unmarshaler to use json.Decoder instead of ioutil.ReadAll to reduce allocations.

Related to: aws#2115
xibz pushed a commit to xibz/aws-sdk-go that referenced this pull request Oct 15, 2018
… allocation (aws#2142)

Update JSONPRC protocol unmarshaler to use json.Decoder instead of ioutil.ReadAll to reduce allocations.

Related to: aws#2115
@diehlaws diehlaws removed the response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. label Nov 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Utilize memory allocation when json decode
4 participants