Skip to content
This repository has been archived by the owner on Dec 18, 2018. It is now read-only.

Faster IFeatureCollection.Get<TFeature> #2290

Merged
merged 2 commits into from Feb 23, 2018

Conversation

benaadams
Copy link
Contributor

@benaadams benaadams commented Feb 3, 2018

Go via Generic rather than wrapping with (TFeature)Get(typeof(TFeature)) as it scales better

pre

                 Method |     Mean |     Error |    StdDev |         Op/s | Allocated |
----------------------- |---------:|----------:|----------:|-------------:|----------:|
     GetViaTypeOf_First | 12.56 ns | 0.1568 ns | 0.2347 ns | 79,608,881.2 |       0 B |
    GetViaGeneric_First | 22.93 ns | 0.1275 ns | 0.1908 ns | 43,613,651.3 |       0 B |
      GetViaTypeOf_Last | 75.97 ns | 0.1121 ns | 0.1678 ns | 13,163,676.0 |       0 B |
     GetViaGeneric_Last | 85.30 ns | 0.2586 ns | 0.3870 ns | 11,723,594.4 |       0 B |
    GetViaTypeOf_Custom | 77.44 ns | 0.2239 ns | 0.3351 ns | 12,912,708.4 |       0 B |
   GetViaGeneric_Custom | 86.67 ns | 0.3066 ns | 0.4590 ns | 11,538,511.2 |       0 B |
  GetViaTypeOf_NotFound | 77.56 ns | 0.2899 ns | 0.4339 ns | 12,893,115.2 |       0 B |
 GetViaGeneric_NotFound | 86.23 ns | 0.2085 ns | 0.3121 ns | 11,597,065.2 |       0 B |

post

                 Method |     Mean |     Error |    StdDev |         Op/s | Allocated |
----------------------- |---------:|----------:|----------:|-------------:|----------:|
     GetViaTypeOf_First | 11.85 ns | 0.0565 ns | 0.0846 ns | 84,418,379.7 |       0 B |
    GetViaGeneric_First | 21.11 ns | 0.4077 ns | 0.6102 ns | 47,376,257.7 |       0 B |
      GetViaTypeOf_Last | 74.42 ns | 0.3818 ns | 0.5714 ns | 13,437,263.4 |       0 B |
     GetViaGeneric_Last | 40.41 ns | 4.6554 ns | 6.9680 ns | 24,748,132.8 |       0 B |
    GetViaTypeOf_Custom | 76.88 ns | 0.3606 ns | 0.5397 ns | 13,006,456.8 |       0 B |
   GetViaGeneric_Custom | 41.89 ns | 0.1967 ns | 0.2943 ns | 23,872,788.5 |       0 B |
  GetViaTypeOf_NotFound | 77.13 ns | 0.1864 ns | 0.2790 ns | 12,965,485.2 |       0 B |
 GetViaGeneric_NotFound | 41.31 ns | 0.1276 ns | 0.1910 ns | 24,204,614.8 |       0 B |

#2012 rebased

Resolves #2303

@BrennanConroy
Copy link
Member

@pakrym

@benaadams
Copy link
Contributor Author

PR also shaves a significant amount off OnReset()

52 secs of plaintext
Pre
image

Post
image

@benaadams
Copy link
Contributor Author

CS0103: The name 'PipeFactory' does not exist in the current context

C:\projects\kestrelhttpserver\test\Kestrel.Core.Tests\HttpProtocolFeatureCollectionTests.cs(34,24): error CS0103: The name 'PipeFactory' does not exist in the current context [C:\projects\kestrelhttpserver\test\Kestrel.Core.Tests\Kestrel.Core.Tests.csproj]
C:\projects\kestrelhttpserver\test\Kestrel.Core.Tests\HttpProtocolFeatureCollectionTests.cs(34,24): error CS0103: The name 'PipeFactory' does not exist in the current context [C:\projects\kestrelhttpserver\test\Kestrel.Core.Tests\Kestrel.Core.Tests.csproj]
C:\projects\kestrelhttpserver\test\Kestrel.Core.Tests\HttpProtocolFeatureCollectionTests.cs(34,24): error CS0103: The name 'PipeFactory' does not exist in the current context [C:\projects\kestrelhttpserver\test\Kestrel.Core.Tests\Kestrel.Core.Tests.csproj]
    0 Warning(s)
    3 Error(s)
Time Elapsed 00:01:51.28

@davidfowl
Copy link
Member

I broke your change? 😄

@benaadams
Copy link
Contributor Author

I broke your change?

Was trying to work out how it was possible, then realized it was my new test breaking 😄

@benaadams
Copy link
Contributor Author

Set and Get branches

Pre
image

Post
image

@benaadams
Copy link
Contributor Author

benaadams commented Feb 11, 2018

Full Get usage

Pre
image

Post

image

@benaadams
Copy link
Contributor Author

OSX UnitTests/netcoreapp2.0

Failed   NonListenerPipeConnectionsAreLoggedAndIgnored
Error Message:
 System.IO.IOException : Unable to transfer data on the transport connection: Connection reset by peer.
---- System.Net.Sockets.SocketException : Connection reset by peer
Stack Trace:
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.IO.StreamReader.<ReadBufferAsync>d__65.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult()
   at System.IO.StreamReader.<ReadToEndAsyncInternal>d__61.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Testing.HttpClientSlim.<ReadResponse>d__4.MoveNext() in C:\b\w\33bdfc1cae7b2a38\modules\Testing\src\Microsoft.AspNetCore.Testing\HttpClientSlim.cs:line 66
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Testing.HttpClientSlim.<GetStringAsync>d__1.MoveNext() in C:\b\w\33bdfc1cae7b2a38\modules\Testing\src\Microsoft.AspNetCore.Testing\HttpClientSlim.cs:line 35
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.ListenerPrimaryTests.<AssertResponseEventually>d__3.MoveNext() in /_/test/Kestrel.Transport.Libuv.Tests/ListenerPrimaryTests.cs:line 281
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests.ListenerPrimaryTests.<NonListenerPipeConnectionsAreLoggedAndIgnored>d__1.MoveNext() in /_/test/Kestrel.Transport.Libuv.Tests/ListenerPrimaryTests.cs:line 139
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
----- Inner Stack Trace -----

OSX UnitTests/netcoreapp2.1 pass

Copy link
Member

@halter73 halter73 left a comment

Choose a reason for hiding this comment

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

Any idea why GetViaGeneric_First is still twice as fast as GetViaGeneric_Last? Shouldn't the JIT remove all the extra branches so it optimizes to return (ReifiedType)_specificFeature?

}

TFeature IFeatureCollection.Get<TFeature>()
protected void ResetIHttpUpgradeFeature()
Copy link
Member

Choose a reason for hiding this comment

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

Nit: missing newline

Copy link
Member

Choose a reason for hiding this comment

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

Theoretically, isn't this exactly what Set<IHttpUpgradeFeature>(this) should optimize to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added newline

@benaadams
Copy link
Contributor Author

Any idea why GetViaGeneric_First is still twice as fast as GetViaGeneric_Last? Shouldn't the JIT remove all the extra branches so it optimizes to return (ReifiedType)_specificFeature?

The interfaces are object types so it produces a shared generic and doesn't branch eliminate; have raised issue https://github.com/dotnet/coreclr/issues/16266 requesting ability to mark a method and have it be generated as non-shared (though may be a large Jit change).

If the method inlined (size aside), it would eliminate all the branches; but alas its an interface method and you can't inline through an interface.

Additionally an explicit interface method counts as virtual method https://github.com/dotnet/coreclr/issues/16198 so will only devirtualize on a sealed type where the explicit type is known/exposed at Jit time; so this can never devirtualize and inline (currently anyway):

((IFeatureCollection)this).Set<IHttpUpgradeFeature>(this);

Theoretically, isn't this exactly what Set<IHttpUpgradeFeature>(this) should optimize to?

To work around it you could define protected FastFeatureSet<TFeature>(TFeature instance) on HttpProtocol with an aggressive inline and hope it inlines into both two single feature resets (on Http1Connection and Http2Connection) and into the interface generic void IFeatureCollection.Set<TFeature>(TFeature instance), or not define it explicitly public Set<TFeature>(TFeature instance); else it introduces an extra method call and moves the potential interface inline one step further away; and crossgen isn't as happy with inlining shared generics with this number of type references (give error like "complex type handling"); the runtime Jit is happier to do it though.

It also seems a lot of work and hoops to jump through; as well as Jit beahviour to rely on just to avoid defining two simple Reset methods?

@benaadams
Copy link
Contributor Author

halter73 requested changes

Were there other changes?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants