Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Avoid unnecessary byte[] allocations in HttpContent#2493

Merged
davidsh merged 1 commit into
dotnet:masterfrom
justinvp:http_preamble
Aug 25, 2015
Merged

Avoid unnecessary byte[] allocations in HttpContent#2493
davidsh merged 1 commit into
dotnet:masterfrom
justinvp:http_preamble

Conversation

@justinvp

Copy link
Copy Markdown
Contributor

HttpContent.ReadAsStringAsync() has a bunch of calls to Encoding.GetPreamble() as part of its encoding detection. GetPreamble() creates a new byte[] each time it is called. These byte[] allocations can be avoided.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The only place you're constructing these, you're passing in SomeEncoding and SomeEncoding.GetPreamble(). You could simplify those call sites by just making the constructor be:

public EncodingPreamblePair(Encoding encoding)
{
    Encoding = encoding;
    Preamble = encoding.GetPreamble();
}

or you could add a constructor that does that:

public EncodingPreamblePair(Encoding encoding) : this(encoding, encoding.GetPreamble())
{
}

public EncodingPreamblePair(Encoding encoding, byte[] preamble)
{
    Encoding = encoding;
    Preamble = preamble;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah, yes, thanks! (I didn't notice the possible simplification because I was originally using KeyValuePair<K,V>). Fixed.

@davidsh

davidsh commented Jul 24, 2015

Copy link
Copy Markdown
Contributor

cc: @CIPop @SidharthNabar @pgavlin

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not convinced this should be a struct. Since we're only ever constructing a handful of these things, but we're iterating through the array that stores them potentially a lot, seems like it could be better to keep the size of the data being enumerated at one instead of two references.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What'd you have in mind? Two arrays (one for the encodings and one for the byte[] preambles, that share the same indexes)? Or just one array of byte[] preambles with known indexes to get the associated encoding?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

struct is fine in this case, it's actually better than class as it avoids an additional indirection. struct would have been an issue if it was large and something like List<> would have been used instead of an array.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll play around with a couple alternative approaches and measure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mikedn, you're probably right, but I'm not sure what's wrong with my microbenchmarking then. Here's my example:

using System;
using System.Diagnostics;
using System.Linq;

static class Test
{
    sealed class WrapperClass
    {
        public object Object1;
        public object Object2;
    }

    struct WrapperStruct
    {
        public object Object1;
        public object Object2;
    }

    static void Main()
    {
        var classArr = Enumerable.Range(0, 10).Select(_ => new WrapperClass { Object1 = new object(), Object2 = new object() }).ToArray();
        var structArr = Enumerable.Range(0, 10).Select(_ => new WrapperStruct { Object1 = new object(), Object2 = new object() }).ToArray();
        var sw = new Stopwatch();
        const int Iters = 100000000;

        while (true)
        {
            sw.Restart();
            for (int i = 0; i < Iters; i++)
            {
                foreach (var item in structArr)
                {
                    object obj1 = item.Object1;
                    object obj2 = item.Object2;
                }
            }
            Console.WriteLine("Struct : " + sw.ElapsedMilliseconds);

            sw.Restart();
            for (int i = 0; i < Iters; i++)
            {
                foreach (var item in classArr)
                {
                    object obj1 = item.Object1;
                    object obj2 = item.Object2;
                }
            }
            Console.WriteLine("Class  : " + sw.ElapsedMilliseconds);

            Console.WriteLine();
        }
    }
}

With VS2015 RTM, on my machine the 32-bit JIT results in the class being 3x the speed of the struct version, and the 64-bit JIT results in it being 1.25x faster. Do you see different results?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JIT fail. In the class case it partially eliminates some the code inside the loop, in the struct case it insists on copying the struct fields to obj1 and obj2. If you change both loop bodies to

if (item.Object1 == item.Object2)
    Console.WriteLine("eq");

you'll get similar results for both struct and class. The class case is still a tiny bit faster due to other JIT issues but the difference is small enough that avoiding some heap allocations seems preferable.

That's with RyuJIT, I haven't tested with the x86 JIT. Is this code ever supposed to run on x86? As is now CoreCLR doesn't quite support x86.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this code ever supposed to run on x86? As is now CoreCLR doesn't quite support x86.

CoreCLR is meant to support 32-bit, at least on Windows, and in fact we currently run most of our tests on Windows on 32-bit (though hopefully they'll soon run 64-bit by default). Plus many of these libraries are shared with .NET Native.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Well, I checked the x86 results and it's funny, both struct and class generate good code yet the struct case is slower (1000ms vs 800ms). That's until you invert the order of the tests and then the struct case is slightly faster (730ms vs 770ms). Oh well, I guess we'll never get a realistic result by using such benchmarks. Anyone offers to flip a coin? 😄

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I flipped one 😄 The coin suggested sticking with struct.

@justinvp

Copy link
Copy Markdown
Contributor Author

Updated with an alternative approach that I landed on after trying a few alternatives. It's a little more code, but minimal allocations, and a lot faster than my initial commit:

  • TryDetectEncoding is ~1.3-1.4x faster than looping through pairs.
  • GetPreambleLength is ~5-5.8x faster than looping through pairs and checking pair.Encoding.Equals(encoding) and ~3.5-3.6x faster than checking pair.Encoding.CodePage == encoding.CodePage.

PTAL

(Note: I intend to squash all commits before merging).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With the way this has been restructured, is this else if necessary? I'm wondering if you can just move the body that sets bomLength up into the try/catch earlier, e.g.

try
{
    encoding = Encoding.GetEncoding(innerThis.Headers.ContentType.CharSet);
    bomLength = GetPreambleLength(data, dataLength, encoding);
}

To me that makes all of this logic more clear:

  • If there was a header for encoding, we try to get the encoding and its associated preamble length.
  • If that wasn't successful, either because there wasn't a header or because we didn't recognize the encoding, we try to detect it from the data and get its associated BOM length
  • Finally, if that wasn't successful, we use a default encoding and BOM length.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(I'd probably also add a comment where bomLength is set to 0, highlighting that DefaultStringEncoding is UTF8, but we already checked to see if it had a UTF8 BOM, so the bomLength is 0.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed. The logic is much clearer now, thanks for the feedback.

With the latest changes, the detection/fallback part looks like the following:

// If no content encoding is listed in the ContentType HTTP header, or no Content-Type header present,
// then check for a BOM in the data to figure out the encoding.
if (encoding == null)
{
    TryDetectEncoding(data, dataLength, ref encoding, ref bomLength);
}

// Use the default encoding if we couldn't detect one.
if (encoding == null)
{
    encoding = DefaultStringEncoding;

    // DefaultStringEncoding is UTF8, but we already checked to see if it had a UTF8 BOM,
    // so the bomLength is 0.
    bomLength = 0;
}

I'm mulling over changing TryDetectEncoding to actually return true/false, in which case the above could be:

// If no content encoding is listed in the ContentType HTTP header, or no Content-Type header present,
// then check for a BOM in the data to figure out the encoding.
if (encoding == null)
{
    if (!TryDetectEncoding(data, dataLength, ref encoding, ref bomLength))
    {
        // Use the default encoding if we couldn't detect one.
        encoding = DefaultStringEncoding;

        // DefaultStringEncoding is UTF8, but we already checked to see if it had a UTF8 BOM,
        // so the bomLength is 0.
        bomLength = 0;
    }
}

Do you have a preference?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Or, move the DefaultStringEncoding fallback directly inside TryDetectEncoding and keep it void (in which case I'd probably rename it just DetectEncoding).

// If no content encoding is listed in the ContentType HTTP header, or no Content-Type header present,
// then check for a BOM in the data to figure out the encoding with fallback to DefaultStringEncoding.
if (encoding == null)
{
    DetectEncoding(data, dataLength, out encoding, out bomLength);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do you have a preference?

My preference would be the Try version, as it makes it more clear to me at least what policy is being applied. If you did switch to the DetectEncoding version, my preference would be for it to return the encoding rather than using a ref, and to use out rather ref for the bomLength. Just my personal preference.

@stephentoub

Copy link
Copy Markdown
Member

LGTM

@justinvp

Copy link
Copy Markdown
Contributor Author

I have an even faster version of the encoding detection (~2-2.6x faster). I'll update the PR tomorrow with the improvements (sorry for the additional churn).

@justinvp

Copy link
Copy Markdown
Contributor Author

I added a new commit with the improvements. GetPreambleLength is ~1.2-1.5x faster than the previous commit and TryDetectEncoding is ~1.2-5.4x faster (x64 RyuJIT), depending on the data and encoding.

@davidsh

davidsh commented Aug 24, 2015

Copy link
Copy Markdown
Contributor

@stephentoub Can you please review the latest commits to this? It changed since your previous LGTM. I'm preparing final testing of this. Thx.

@stephentoub

Copy link
Copy Markdown
Member

LGTM. My only question is, due to the hardcoding of various constants here, should we add some more Debug.Asserts, potentially in a cctor that's only under an #if DEBUG, that validate that the constants we're using continue to match the data in the Encoding objects they represent? It's extremely unlikely / impossible I guess that these would change, so it's not required, just a thought.

@davidsh

davidsh commented Aug 24, 2015

Copy link
Copy Markdown
Contributor

@justinvp Can you comment on this? Thx.

@justinvp

Copy link
Copy Markdown
Contributor Author

Alright, I added asserts for the encoding constants. PR updated and squashed into a single commit.

@justinvp

Copy link
Copy Markdown
Contributor Author

Looks like the CI build is failing for unrelated reasons:

ERROR: Failed to run DSL Script
java.util.concurrent.ExecutionException: org.codehaus.groovy.runtime.InvokerInvocationException: com.cloudbees.plugins.flow.JobExecutionFailureException
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:188)
    at java_util_concurrent_Future$get.call(Unknown Source)
    at com.cloudbees.plugins.flow.FlowDelegate$_parallel_closure6.doCall(FlowDSL.groovy:440)
    at sun.reflect.GeneratedMethodAccessor578.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:903)
    at groovy.lang.Closure.call(Closure.java:415)
    at groovy.lang.Closure.call(Closure.java:428)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1379)
    at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1351)
    at org.codehaus.groovy.runtime.dgm$170.invoke(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:271)
    at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:53)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
    at com.cloudbees.plugins.flow.FlowDelegate.parallel(FlowDSL.groovy:438)
    at sun.reflect.GeneratedMethodAccessor666.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1079)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:903)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:723)
    at com.cloudbees.plugins.flow.FlowDelegate.invokeMethod(FlowDSL.groovy)
    at hudson.util.spring.ClosureScript.invokeMethod(ClosureScript.java:83)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:72)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145)
    at Script1.run(Script1.groovy:1)
    at Script1$run.call(Unknown Source)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
    at Script1$run.call(Unknown Source)
    at com.cloudbees.plugins.flow.FlowDSL.executeFlowScript(FlowDSL.groovy:84)
    at com.cloudbees.plugins.flow.FlowRun$BuildWithWorkspaceRunnerImpl.doRun(FlowRun.java:180)
    at hudson.model.AbstractBuild$AbstractBuildExecution.run(AbstractBuild.java:537)
    at hudson.model.Run.execute(Run.java:1741)
    at hudson.model.Run.run(Run.java:1679)
    at com.cloudbees.plugins.flow.FlowRun.run(FlowRun.java:153)
    at hudson.model.ResourceController.execute(ResourceController.java:98)
    at hudson.model.Executor.run(Executor.java:381)
Caused by: org.codehaus.groovy.runtime.InvokerInvocationException: com.cloudbees.plugins.flow.JobExecutionFailureException
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:97)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:903)
    at groovy.lang.Closure.call(Closure.java:415)
    at groovy.lang.Closure.call(Closure.java:409)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
Caused by: com.cloudbees.plugins.flow.JobExecutionFailureException
    at sun.reflect.GeneratedConstructorAccessor394.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
    at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:77)
    at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:102)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:186)
    at com.cloudbees.plugins.flow.FlowDelegate.fail(FlowDSL.groovy:171)
    at com.cloudbees.plugins.flow.FlowDelegate.statusCheck(FlowDSL.groovy:205)
    at com.cloudbees.plugins.flow.FlowDelegate.build(FlowDSL.groovy:210)
    at sun.reflect.GeneratedMethodAccessor584.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1079)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:903)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:723)
    at com.cloudbees.plugins.flow.FlowDelegate.invokeMethod(FlowDSL.groovy)
    at hudson.util.spring.ClosureScript.invokeMethod(ClosureScript.java:83)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeOnDelegationObjects(ClosureMetaClass.java:407)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:346)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:903)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:145)
    at Script1$_run_closure1.doCall(Script1.groovy:17)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:903)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:46)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:133)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
    at Script1$_run_closure1.doCall(Script1.groovy)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:903)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:39)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:54)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:112)
    at com.cloudbees.plugins.flow.FlowDelegate$_parallel_closure5_closure7.doCall(FlowDSL.groovy:427)
    at sun.reflect.GeneratedMethodAccessor552.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
    at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:272)
    at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:903)
    at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:66)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:141)
    at com.cloudbees.plugins.flow.FlowDelegate$_parallel_closure5_closure7.doCall(FlowDSL.groovy)
    at sun.reflect.GeneratedMethodAccessor549.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:90)
    ... 9 more

HttpContent.ReadAsStringAsync() has a bunch of calls to
Encoding.GetPreamble() as part of its encoding detection.
GetPreamble() creates a new byte[] each time it is called.
These byte[] allocations can be avoided. Also, as part of this,
cleaned up and improved the performance of the encoding detection.
@stephentoub

Copy link
Copy Markdown
Member

@dotnet-bot test this please

@davidsh

davidsh commented Aug 25, 2015

Copy link
Copy Markdown
Contributor

LGTM

@stephentoub

Copy link
Copy Markdown
Member

LGTM2

davidsh added a commit that referenced this pull request Aug 25, 2015
Avoid unnecessary byte[] allocations in HttpContent
@davidsh
davidsh merged commit 16abb16 into dotnet:master Aug 25, 2015
@justinvp
justinvp deleted the http_preamble branch October 1, 2015 22:06
@karelz karelz modified the milestone: 1.0.0-rtm Dec 3, 2016
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
Avoid unnecessary byte[] allocations in HttpContent

Commit migrated from dotnet/corefx@16abb16
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants