{"action":"created","issue":{"url":"https://api.github.com/repos/dotnet/corefx/issues/7302","repository_url":"https://api.github.com/repos/dotnet/corefx","labels_url":"https://api.github.com/repos/dotnet/corefx/issues/7302/labels{/name}","comments_url":"https://api.github.com/repos/dotnet/corefx/issues/7302/comments","events_url":"https://api.github.com/repos/dotnet/corefx/issues/7302/events","html_url":"https://github.com/dotnet/corefx/pull/7302","id":144055727,"number":7302,"title":"Improve memory usage of HttpClient's StreamToStreamCopy","user":{"login":"stephentoub","id":2642209,"avatar_url":"https://avatars.githubusercontent.com/u/2642209?v=3","gravatar_id":"","url":"https://api.github.com/users/stephentoub","html_url":"https://github.com/stephentoub","followers_url":"https://api.github.com/users/stephentoub/followers","following_url":"https://api.github.com/users/stephentoub/following{/other_user}","gists_url":"https://api.github.com/users/stephentoub/gists{/gist_id}","starred_url":"https://api.github.com/users/stephentoub/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stephentoub/subscriptions","organizations_url":"https://api.github.com/users/stephentoub/orgs","repos_url":"https://api.github.com/users/stephentoub/repos","events_url":"https://api.github.com/users/stephentoub/events{/privacy}","received_events_url":"https://api.github.com/users/stephentoub/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/dotnet/corefx/labels/2%20-%20In%20Progress","name":"2 - In Progress","color":"CCCCCC"},{"url":"https://api.github.com/repos/dotnet/corefx/labels/cla-already-signed","name":"cla-already-signed","color":"009800"},{"url":"https://api.github.com/repos/dotnet/corefx/labels/Needs%20extra%20testing%20before%20merge","name":"Needs extra testing before merge","color":"e11d21"},{"url":"https://api.github.com/repos/dotnet/corefx/labels/System.Net","name":"System.Net","color":"d4c5f9"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":9,"created_at":"2016-03-28T19:10:21Z","updated_at":"2016-03-29T23:00:31Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/dotnet/corefx/pulls/7302","html_url":"https://github.com/dotnet/corefx/pull/7302","diff_url":"https://github.com/dotnet/corefx/pull/7302.diff","patch_url":"https://github.com/dotnet/corefx/pull/7302.patch"},"body":"Copying between streams represents one of the larger sources of memory allocations and copying in HttpClient. HttpClient has its own stream-to-stream copy implementation, but there are ways we can significantly improve it based on typical usage within HttpClient.  \r\n\r\nThis commit does the following:\r\n- Previously it would special case when both the source and destination stream were MemoryStreams, but even in that case it would allocate a temporary buffer and do a Read/Write loop to copy between them, and even then it's relatively rare to copy from a MemoryStream to a MemoryStream.  Instead, if the source stream is a MemoryStream and we can get its buffer, we can do a single write to the destination (regardless of what kind of stream it is), avoiding the need for multiple read/writes and for the temporary buffer.  If we can't get its buffer but its length is known to be less than or equal to the requested buffer size and it's positioned at the beginning, we can simply ToArray the source and still do a single write to the target.\r\n- If the destination is the special LimitMemoryStream, used for buffering output, and if that LimitMemoryStream was pre-sized based on the response having a Content-Length, then rather than allocating a temporary buffer and repeatedly copying from the source stream into that buffer and copying from the buffer into the memory stream, we can instead give the destination's buffer directly to be read into by the source, avoiding the temporary buffer and the extra copies.  This is a very common case.\r\n- If the source does not need to be disposed after the copy, rather than doing our own copy loop, we can instead ask the source to use its CopyToAsync implementation to copy to the destination stream.  Worst case this will be exactly what we were previously doing, but best case the source has its own CopyToAsync override that's more efficient.\r\n- When constructing a LimitMemoryStream, if the Content-Length isn't known, we were explicitly passing a 0 capacity to the base, which causes it to allocate a new zero-length array; I've added a ctor to avoid that.\r\n\r\nAs a small example, with this short test:\r\n```C#\r\nvar uri = new Uri(\"http://httpbin.org/get\");\r\nvar c = new HttpClient();\r\nfor (int i = 0; i < 100; i++)\r\n    c.GetAsync(uri).GetAwaiter().GetResult().Dispose();\r\n```\r\nbefore the change it allocated 1,411,714 bytes, and after the change, it allocated 1,003,702, for an ~30% reduction.  That drop is almost entirely in avoiding needing byte[]s:\r\n\r\nI'd appreciate some extra scrutiny on this.\r\ncc: @davidsh, @cipop, @ericeil, @kapilash, @bartonjs, @ianhays"},"comment":{"url":"https://api.github.com/repos/dotnet/corefx/issues/comments/203147214","html_url":"https://github.com/dotnet/corefx/pull/7302#issuecomment-203147214","issue_url":"https://api.github.com/repos/dotnet/corefx/issues/7302","id":203147214,"user":{"login":"jamesqo","id":9159214,"avatar_url":"https://avatars.githubusercontent.com/u/9159214?v=3","gravatar_id":"","url":"https://api.github.com/users/jamesqo","html_url":"https://github.com/jamesqo","followers_url":"https://api.github.com/users/jamesqo/followers","following_url":"https://api.github.com/users/jamesqo/following{/other_user}","gists_url":"https://api.github.com/users/jamesqo/gists{/gist_id}","starred_url":"https://api.github.com/users/jamesqo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jamesqo/subscriptions","organizations_url":"https://api.github.com/users/jamesqo/orgs","repos_url":"https://api.github.com/users/jamesqo/repos","events_url":"https://api.github.com/users/jamesqo/events{/privacy}","received_events_url":"https://api.github.com/users/jamesqo/received_events","type":"User","site_admin":false},"created_at":"2016-03-29T23:00:31Z","updated_at":"2016-03-29T23:00:31Z","body":"@stephentoub, I'm assuming the changes here supplant the ones I made in #5093?"}}