Skip to content

Commit da234b9

Browse files
authored
Improve performance of output cache (#48328)
* optimize Output Cache; no API changes yet - all internal: - unify OutputCacheEntry and FormatterEntry - leased buffers for headers, tags, etc (dispose on way out) - use ReadOnlySequence<byte> instead of List<byte[]> with recyclable segments - avoid copying the payload data once fectched - serialization tweak: use common headers (not yet listed) finish porting tests; all good migrate writes to IBufferWriter<byte>, using recyclable array buffer writer (similar to MemoryStream, but: faster) use pooled buffers when buffering output-cache payloads RROSS should be able to recycle buffers as needed implement header name/value lookup buffer add bench project note memory overhead in bench full bench suite add benchmark result - don't store tags in the cache payload - don't store segment details in the cache payload whitespace include test that uses body-writer rather than stream tidy up benchmarks remove pipe impl; we don't need it (proven in bench) add a few more known headers; don't store request-id don't write empty segments fix buffer cleanup paths revert changes to shared SegmentWriteStream update numbers use fixed size stackalloc Update src/Middleware/OutputCaching/src/OutputCacheEntryFormatter.cs Co-authored-by: Günther Foidl <[email protected]> - remove global SkipLocalsInit - add accessibility on all new members - in length checks, use - over + to avoid overflow nits relocate perf to microbenchmarks use standard project structure for micro-benchmarks use "actual" instead of "bytes" to avoid a "pop"/"ld" in the "release" case avoid a memcopy by writing directly to the buffer bytes (and increasing DRY) Update src/Middleware/OutputCaching/src/OutputCacheEntry.cs Co-authored-by: Brennan <[email protected]> Update src/Middleware/OutputCaching/test/OutputCacheEntryFormatterTests.cs Co-authored-by: Brennan <[email protected]> fix PR nits from #48450 fix sln (dead project) Update src/Middleware/OutputCaching/src/OutputCacheEntry.cs Co-authored-by: Brennan <[email protected]> DRY nit moar nits use leased buffer for tags when calling SetAsync Update src/Middleware/OutputCaching/src/OutputCacheEntryFormatter.cs Co-authored-by: Brennan <[email protected]> use [LoggerMessage] for logging merge submodule delete bump optimize Output Cache; no API changes yet - all internal: - unify OutputCacheEntry and FormatterEntry - leased buffers for headers, tags, etc (dispose on way out) - use ReadOnlySequence<byte> instead of List<byte[]> with recyclable segments - avoid copying the payload data once fectched - serialization tweak: use common headers (not yet listed) sln fix dammit * use `_field` naming in `FormatterBinaryReader.cs` * move log methods to dedicated file * simplify cached response cleanup * add comment re buffer size
1 parent c6cc85c commit da234b9

29 files changed

+1997
-480
lines changed

AspNetCore.sln

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,6 +1772,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinimalFormSample", "src\An
17721772
EndProject
17731773
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcFormSample", "src\Mvc\samples\MvcFormSample\MvcFormSample.csproj", "{055F86AA-FB37-40CC-B39E-C29CE7547BB7}"
17741774
EndProject
1775+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.OutputCaching.Microbenchmarks", "src\Middleware\OutputCaching\perf\Microbenchmarks\Microsoft.AspNetCore.OutputCaching.Microbenchmarks.csproj", "{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}"
1776+
EndProject
17751777
Global
17761778
GlobalSection(SolutionConfigurationPlatforms) = preSolution
17771779
Debug|Any CPU = Debug|Any CPU
@@ -10645,6 +10647,22 @@ Global
1064510647
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|x64.Build.0 = Release|Any CPU
1064610648
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|x86.ActiveCfg = Release|Any CPU
1064710649
{055F86AA-FB37-40CC-B39E-C29CE7547BB7}.Release|x86.Build.0 = Release|Any CPU
10650+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
10651+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
10652+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Debug|arm64.ActiveCfg = Debug|Any CPU
10653+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Debug|arm64.Build.0 = Debug|Any CPU
10654+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Debug|x64.ActiveCfg = Debug|Any CPU
10655+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Debug|x64.Build.0 = Debug|Any CPU
10656+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Debug|x86.ActiveCfg = Debug|Any CPU
10657+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Debug|x86.Build.0 = Debug|Any CPU
10658+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
10659+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Release|Any CPU.Build.0 = Release|Any CPU
10660+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Release|arm64.ActiveCfg = Release|Any CPU
10661+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Release|arm64.Build.0 = Release|Any CPU
10662+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Release|x64.ActiveCfg = Release|Any CPU
10663+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Release|x64.Build.0 = Release|Any CPU
10664+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Release|x86.ActiveCfg = Release|Any CPU
10665+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6}.Release|x86.Build.0 = Release|Any CPU
1064810666
EndGlobalSection
1064910667
GlobalSection(SolutionProperties) = preSolution
1065010668
HideSolutionNode = FALSE
@@ -11520,6 +11538,7 @@ Global
1152011538
{87D58D50-20D1-4091-88C5-8D88DCCC2DE3} = {6126DCE4-9692-4EE2-B240-C65743572995}
1152111539
{F7BCD3AD-31E2-4223-B215-851C3D0AB78A} = {B55A5DE1-5AF3-4B18-AF04-C1735B071DA6}
1152211540
{055F86AA-FB37-40CC-B39E-C29CE7547BB7} = {B8825E86-B8EA-4666-B681-C443D027C95D}
11541+
{137AD17B-066F-4ED4-80FA-8D21C7B76CA6} = {AA5ABFBC-177C-421E-B743-005E0FD1248B}
1152311542
EndGlobalSection
1152411543
GlobalSection(ExtensibilityGlobals) = postSolution
1152511544
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

src/Caching/StackExchangeRedis/src/Microsoft.Extensions.Caching.StackExchangeRedis.csproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,20 @@
1616
<Reference Include="Microsoft.AspNetCore.OutputCaching" Condition="'$(TargetFramework)' != '$(DefaultNetFxTargetFramework)' and '$(TargetFramework)' != 'netstandard2.0'" />
1717
<Reference Include="Microsoft.Extensions.Options" />
1818
<Reference Include="StackExchange.Redis" />
19-
<InternalsVisibleTo Include="Microsoft.Extensions.Caching.StackExchangeRedis.Tests"/>
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<InternalsVisibleTo Include="Microsoft.Extensions.Caching.StackExchangeRedis.Tests" />
2023
</ItemGroup>
2124

2225
<ItemGroup>
2326
<Compile Include="$(SharedSourceRoot)ThrowHelpers\ArgumentNullThrowHelper.cs" LinkBase="Shared" />
2427
<Compile Include="$(SharedSourceRoot)ThrowHelpers\ObjectDisposedThrowHelper.cs" LinkBase="Shared" />
2528
<Compile Include="$(SharedSourceRoot)CallerArgument\CallerArgumentExpressionAttribute.cs" LinkBase="Shared" />
29+
</ItemGroup>
2630

31+
<ItemGroup>
2732
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Shipped.txt" />
2833
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Unshipped.txt" />
2934
</ItemGroup>
30-
3135
</Project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if NET7_0_OR_GREATER // IOutputCacheStore only exists from net7
5+
6+
using System;
7+
using Microsoft.Extensions.Logging;
8+
using StackExchange.Redis;
9+
10+
namespace Microsoft.Extensions.Caching.StackExchangeRedis;
11+
12+
internal partial class RedisOutputCacheStore
13+
{
14+
[LoggerMessage(1, LogLevel.Warning, "Transient error occurred executing redis output-cache GC loop.", EventName = "RedisOutputCacheGCTransientError")]
15+
internal static partial void RedisOutputCacheGCTransientFault(ILogger logger, Exception exception);
16+
17+
[LoggerMessage(2, LogLevel.Error, "Fatal error occurred executing redis output-cache GC loop.", EventName = "RedisOutputCacheGCFatalError")]
18+
internal static partial void RedisOutputCacheGCFatalError(ILogger logger, Exception exception);
19+
}
20+
#endif

src/Caching/StackExchangeRedis/src/RedisOutputCacheStore.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
namespace Microsoft.Extensions.Caching.StackExchangeRedis;
2323

24-
internal class RedisOutputCacheStore : IOutputCacheStore, IOutputCacheBufferStore, IDisposable
24+
internal partial class RedisOutputCacheStore : IOutputCacheStore, IOutputCacheBufferStore, IDisposable
2525
{
2626
private readonly RedisCacheOptions _options;
2727
private readonly ILogger _logger;
@@ -114,14 +114,14 @@ private async Task RunGarbageCollectionLoopAsync()
114114
catch (Exception ex)
115115
{
116116
// this sweep failed; log it
117-
_logger.LogDebug(ex, "Transient error occurred executing redis output-cache GC loop");
117+
RedisOutputCacheGCTransientFault(_logger, ex);
118118
}
119119
}
120120
}
121121
catch (Exception ex)
122122
{
123123
// the entire loop is dead
124-
_logger.LogDebug(ex, "Fatal error occurred executing redis output-cache GC loop");
124+
RedisOutputCacheGCFatalError(_logger, ex);
125125
}
126126
}
127127

src/Caching/StackExchangeRedis/src/StackExchangeRedisCacheServiceCollectionExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public static IServiceCollection AddStackExchangeRedisOutputCache(this IServiceC
5151

5252
services.Configure(setupAction);
5353
// replace here (Add vs TryAdd) is intentional and part of test conditions
54+
// long-form name qualification is because of the #if conditional; we'd need a matchin #if around
55+
// a using directive, which is messy
5456
services.AddSingleton<AspNetCore.OutputCaching.IOutputCacheStore, RedisOutputCacheStoreImpl>();
5557

5658
return services;

src/Middleware/OutputCaching/OutputCaching.slnf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"projects": [
55
"src\\Caching\\StackExchangeRedis\\src\\Microsoft.Extensions.Caching.StackExchangeRedis.csproj",
66
"src\\Caching\\StackExchangeRedis\\test\\Microsoft.Extensions.Caching.StackExchangeRedis.Tests.csproj",
7+
"src\\Middleware\\OutputCaching\\perf\\Microbenchmarks\\Microsoft.AspNetCore.OutputCaching.Microbenchmarks.csproj",
78
"src\\Middleware\\OutputCaching\\samples\\OutputCachingSample\\OutputCachingSample.csproj",
89
"src\\Middleware\\OutputCaching\\src\\Microsoft.AspNetCore.OutputCaching.csproj",
910
"src\\Middleware\\OutputCaching\\test\\Microsoft.AspNetCore.OutputCaching.Tests.csproj"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark]

0 commit comments

Comments
 (0)