-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Protocol (un)marshallers make many allocations #377
Comments
#404 added a nice improvement to the performance of marshalling JSONRPC, JSONUTIL, and RESTJSON protocols. With a performance improvement of about 60%. Simple structures didn't see as much impact, but more complex input structs with nested types saw larger improvements.
|
@jasdel The interface style looks good to me. I have one other idea. Would it be possible to hang the payload tags off of the blank identifier instead of using the unexported
We would still need to use |
Is there any work in progress on the XML front? The EC2 DescribeImages call seems like a good test case ;-) |
Updated in #722. We're currently investigating how the marshalers can be improved. Hopefully this will lead to replacing the memory duplication and reflection type walking with code generation. |
Any upgrade on the status of this issue? We're currently running against the same problem as issue #1300 . |
Thanks for the feedback @jonaskint. We started the work with this task via #1554 targeting RESTXML and RESTJSON marshaling first. We discovered the SDK's protocol tests were not comprehensive and allowed a bug to leak to master. We reverted the change due to an error in the code generation and marshaling of the payloads. This PR represents the pending work on the RESTJSON and RESTXML marshalers. We've not yet started work on improving the unmarshalers. For the s3 manager issue #1554 won't fix that. I think s3manager needs to be refactored significantly to improve its memory usage and footprint. This work is still in our backlog. |
Merging #722 and this issue together to unify the discussion and work on this issue. Both can be solved by improved SDK (un)marshaling support. |
Hello, thanks for the hard work! Any updates on this? I am seeing ~9x memory growth using WriteAtBuffer from a Lambda. Here I am downloading a file that is # Alloc = 1 MiB TotalAlloc = 1 MiB Sys = 3 MiB NumGC = 0
buffer := aws.NewWriteAtBuffer([]byte{})
n, err := downloader.Download(buffer, &s3.GetObjectInput{
Bucket: aws.String(s3Bucket),
Key: aws.String(s3Path),
})
# Alloc = 250 MiB TotalAlloc = 25973 MiB Sys = 438 MiB NumGC = 346 Note the |
@teastburn have you tried to preallocated the buffer used by the |
@jasdel thanks for the quick response! I did also try that after looking at the WriteAtBuffer code. # Alloc = 1 MiB TotalAlloc = 1 MiB Sys = 5 MiB NumGC = 0
b := make([]byte, 50000000)
# Alloc = 49 MiB TotalAlloc = 49 MiB Sys = 55 MiB NumGC = 1
buffer := aws.NewWriteAtBuffer(b)
# Alloc = 48 MiB TotalAlloc = 49 MiB Sys = 55 MiB NumGC = 1
n, err := downloader.Download(buffer, &s3.GetObjectInput{
Bucket: aws.String(s3Bucket),
Key: aws.String(s3Path),
})
# Alloc = 153 MiB TotalAlloc = 5522 MiB Sys = 525 MiB NumGC = 55 Interestingly, the Lambda did not run out of memory ( I'm using go version |
I switched to using
which uses a constant amount of memory if used with an |
Thanks for the update @teastburn. Is your application using |
@jasdel do you have a recommendation there? I am wrapping it in a bufio.NewReader so that I can easily read by lines via a Scanner. One semi annoying issue with my solution is the caller to my struct receiver function still has to call Close() on the body via a struct method. The good thing, however, is that even loading a 256mb file takes a max memory of 50mb. // Allows us to loop through s3 file line by line
type ByteReader interface {
io.Reader
ReadBytes(byte) ([]byte, error)
}
// Mockable interface
type S3FileGettable interface {
Get(string, string) (ByteReader, error)
io.Closer
}
type S3Getter struct {
body io.ReadCloser
}
func (s3g *S3Getter) Close() error {
return s3g.body.Close()
}
// Must call S3Getter.Close() after done with ByteReader
func (s3g *S3Getter) Get(s3Bucket, s3Path string) (ByteReader, error) {
// ...
svc := s3.New(session.Must(session.NewSession(s3Cfg)))
resp, err := svc.GetObject(&s3.GetObjectInput{
Bucket: aws.String(s3Bucket),
Key: aws.String(s3Path),
})
// ...
s3g.body = resp.Body
reader := bufio.NewReader(resp.Body)
return reader, nil
} |
Using the buff reader like your are is a good way to get the ByteReader interface. I suggest instead of the close method on Something like the following might work better for closing the body. type ByteReadCloser interface {
ByteReader
io.Closer
}
type S3FileGetter struct {
Client *s3.S3 // Add client shared across all object gets.
}
func (s3g *S3Getter) Get(s3Bucket, s3Path string) (ByteReadCloser, error) {
// ...
resp, err := s3g.Client.GetObject(&s3.GetObjectInput{
Bucket: aws.String(s3Bucket),
Key: aws.String(s3Path),
})
// ...
return NewBufferedReadCloser(resp.Body), nil
}
type BufferedReadCloser struct {
*bufio.Reader
io.Closer
}
func NewBufferedReadCloser(reader io.ReadCloser) *BufferedReadCloser {
return &BufferedReadCloser{
Reader: bufio.NewReader(reader),
Closer: reader,
}
}
func (b *ByteReadCloser) Close() error {
return b.Closer.Close()
} Then be used similar to the following. Also, try to only initialize the S3 client once per region/account to ensure that duplicate resources aren't created within the applications. objGetter := S3Getter{Client: s3.New(session.Must(session.NewSession(s3Cfg)))}
// Get each object, and process it
reader, err := objGetter.Get(myBucket, myKey)
// handle error
defer reader.Close()
// process reader content. |
@jasdel That's great feedback. Thanks for the help! |
Its possible to provide another buffer strategy that is using sync.Pool for download, just wrote the following post for more info: https://medium.com/@levyeran/high-memory-allocations-and-gc-cycles-while-downloading-large-s3-objects-using-the-aws-sdk-for-go-e776a136c5d0 |
We have noticed this issue has not received attention in 1 year. We will close this issue for now. If you think this is in error, please feel free to comment and reopen the issue. |
The protocol marshallers make numerous allocations when marshaling and unmarshalling structs. This is much more noticeable for larger API operations such as ElasticTranscoder's CreateJob can does about 90k bytes in 4k allocations. Whereas
encoding/json
marshal on the same structure only does 4k bytes in 7 allocations.#376 updates
internal/protocol/json
to perform a more efficient check if a field is exported or not. Reducing the allocations caused bystrings.ToLower
. Several of the other protocol's marshallers performstrings.ToLower
check, and probably can be updated also.Improving the SDK's marshallers to be more efficient with allocations would help to increase performance and reduce memory usage.
The text was updated successfully, but these errors were encountered: