Browse Source

Benchmark sync (#6036)

* Benchmark sync

* Better batch sql

* Reuse commands
Ben Adams 4 years ago
parent
commit
0bd276a5fb

+ 4 - 4
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs

@@ -112,7 +112,7 @@ namespace PlatformBenchmarks
 
             if (state == State.StartLine)
             {
-#if NETCOREAPP5_0 || NET5_0
+#if NET5_0
                 if (Parser.ParseRequestLine(new ParsingAdapter(this), ref reader))
                 {
                     state = State.Headers;
@@ -197,7 +197,7 @@ namespace PlatformBenchmarks
 
             if (state == State.StartLine)
             {
-#if NETCOREAPP5_0 || NET5_0
+#if NET5_0
                 if (Parser.ParseRequestLine(new ParsingAdapter(this), ref reader))
                 {
                     state = State.Headers;
@@ -246,7 +246,7 @@ namespace PlatformBenchmarks
         }
 #endif
 
-#if NETCOREAPP5_0 || NET5_0
+#if NET5_0
 
         public void OnStaticIndexedHeader(int index)
         {
@@ -311,7 +311,7 @@ namespace PlatformBenchmarks
             public ParsingAdapter(BenchmarkApplication requestHandler)
                 => RequestHandler = requestHandler;
 
-#if NETCOREAPP5_0 || NET5_0
+#if NET5_0
             public void OnStaticIndexedHeader(int index) 
                 => RequestHandler.OnStaticIndexedHeader(index);
 

+ 1 - 1
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkApplication.cs

@@ -63,7 +63,7 @@ namespace PlatformBenchmarks
         private RequestType _requestType;
         private int _queries;
 
-#if NETCOREAPP5_0 || NET5_0
+#if NET5_0
         public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine)
         {
             _requestType = versionAndMethod.Method == HttpMethod.Get ? GetRequestType(startLine.Slice(targetPath.Offset, targetPath.Length), ref _queries) : RequestType.NotRecognized;

+ 7 - 2
frameworks/CSharp/aspnetcore/PlatformBenchmarks/BenchmarkConfigurationHelpers.cs

@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Configuration;
 using System.IO.Pipelines;
+using System.Runtime.InteropServices;
 
 namespace PlatformBenchmarks
 {
@@ -23,10 +24,14 @@ namespace PlatformBenchmarks
                     options.IOQueueCount = threadCount;
                 }
 
-#if NETCOREAPP5_0 || NET5_0
+#if NET5_0
                 options.WaitForDataBeforeAllocatingBuffer = false;
+                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+                {
+                    options.UnsafePreferInlineScheduling = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS") == "1";
+                }
 
-                Console.WriteLine($"Options: WaitForData={options.WaitForDataBeforeAllocatingBuffer}, IOQueue={options.IOQueueCount}");
+                Console.WriteLine($"Options: WaitForData={options.WaitForDataBeforeAllocatingBuffer}, PreferInlineScheduling={options.UnsafePreferInlineScheduling}, IOQueue={options.IOQueueCount}");
 #endif
             });
 

+ 14 - 12
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/BatchUpdateString.cs

@@ -11,8 +11,8 @@ namespace PlatformBenchmarks
 
         public static DatabaseServer DatabaseServer;
 
-        internal static readonly string[] Ids = Enumerable.Range(0, MaxBatch).Select(i => $"@Id_{i}").ToArray();
-        internal static readonly string[] Randoms = Enumerable.Range(0, MaxBatch).Select(i => $"@Random_{i}").ToArray();
+        internal static readonly string[] Ids = Enumerable.Range(0, MaxBatch).Select(i => $"@I{i}").ToArray();
+        internal static readonly string[] Randoms = Enumerable.Range(0, MaxBatch).Select(i => $"@R{i}").ToArray();
 
         private static string[] _queries = new string[MaxBatch + 1];
 
@@ -23,20 +23,22 @@ namespace PlatformBenchmarks
                 return _queries[batchSize];
             }
 
+            return CreateBatch(batchSize);
+        }
+
+        private static string CreateBatch(int batchSize)
+        {
             var lastIndex = batchSize - 1;
 
             var sb = StringBuilderCache.Acquire();
 
-            if (DatabaseServer == DatabaseServer.PostgreSql)
-            {
-                sb.Append("UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ");
-                Enumerable.Range(0, lastIndex).ToList().ForEach(i => sb.Append($"(@Id_{i}, @Random_{i}), "));
-                sb.Append($"(@Id_{lastIndex}, @Random_{lastIndex}) ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id");
-            }
-            else
-            {
-                Enumerable.Range(0, batchSize).ToList().ForEach(i => sb.Append($"UPDATE world SET randomnumber = @Random_{i} WHERE id = @Id_{i};"));
-            }
+            sb.AppendLine("UPDATE world SET randomNumber = CASE id");
+            Enumerable.Range(0, batchSize).ToList().ForEach(i => sb.AppendLine($"when @I{i} then @R{i}"));
+            sb.AppendLine("else randomnumber");
+            sb.AppendLine("end");
+            sb.Append("where id in (");
+            Enumerable.Range(0, batchSize).ToList().ForEach(i => sb.AppendLine($"@I{i}{(lastIndex == i ? "" : ",")} "));
+            sb.Append(")");
 
             return _queries[batchSize] = StringBuilderCache.GetStringAndRelease(sb);
         }

+ 91 - 23
frameworks/CSharp/aspnetcore/PlatformBenchmarks/Data/RawDb.cs

@@ -2,6 +2,7 @@
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.CompilerServices;
@@ -13,6 +14,9 @@ namespace PlatformBenchmarks
 {
     public class RawDb
     {
+        private static readonly ConcurrentQueue<SqlReadCommand> _sqlReadCommands = new ConcurrentQueue<SqlReadCommand>();
+        private static readonly ConcurrentQueue<SqlFortuneCommand> _sqlFortuneCommands = new ConcurrentQueue<SqlFortuneCommand>();
+        private static readonly object[] _cacheKeys = Enumerable.Range(0, 10001).Select((i) => new CacheKey(i)).ToArray();
         private readonly ConcurrentRandom _random;
         private readonly string _connectionString;
         private readonly MemoryCache _cache = new MemoryCache(
@@ -33,9 +37,10 @@ namespace PlatformBenchmarks
             {
                 await db.OpenAsync();
 
-                var (cmd, _) = CreateReadCommand(db);
-                using (cmd)
+                using (var cmd = CreateReadCommand())
                 {
+                    cmd.Connection = db;
+                    cmd.Parameter.TypedValue = _random.Next(0, 10000) + 1;
                     return await ReadSingleRow(cmd);
                 }
             }
@@ -49,13 +54,14 @@ namespace PlatformBenchmarks
             {
                 await db.OpenAsync();
 
-                var (cmd, idParameter) = CreateReadCommand(db);
-                using (cmd)
+                using (var cmd = CreateReadCommand())
                 {
+                    cmd.Connection = db;
+                    var param = cmd.Parameter;
                     for (int i = 0; i < result.Length; i++)
                     {
+                        param.TypedValue = _random.Next(0, 10000) + 1;
                         result[i] = await ReadSingleRow(cmd);
-                        idParameter.TypedValue = _random.Next(1, 10001);
                     }
                 }
             }
@@ -93,9 +99,9 @@ namespace PlatformBenchmarks
                 {
                     await db.OpenAsync();
 
-                    var (cmd, idParameter) = rawdb.CreateReadCommand(db);
-                    using (cmd)
+                    using (var cmd = CreateReadCommand())
                     {
+                        cmd.Connection = db;
                         Func<ICacheEntry, Task<CachedWorld>> create = async (entry) => 
                         {
                             return await rawdb.ReadSingleRow(cmd);
@@ -104,7 +110,8 @@ namespace PlatformBenchmarks
                         var cacheKeys = _cacheKeys;
                         var key = cacheKeys[id];
 
-                        idParameter.TypedValue = id;
+                        var param = cmd.Parameter;
+                        param.TypedValue = id;
 
                         for (; i < result.Length; i++)
                         {
@@ -112,7 +119,7 @@ namespace PlatformBenchmarks
                             result[i] = data;
 
                             id = rawdb._random.Next(1, 10001);
-                            idParameter.TypedValue = id;
+                            param.TypedValue = id;
                             key = cacheKeys[id];
                         }
                     }
@@ -128,14 +135,14 @@ namespace PlatformBenchmarks
             {
                 await db.OpenAsync();
 
-                var (cmd, idParameter) = CreateReadCommand(db);
-                using (cmd)
+                using (var cmd = CreateReadCommand())
                 {
+                    cmd.Connection = db;
                     var cacheKeys = _cacheKeys;
                     var cache = _cache;
                     for (var i = 1; i < 10001; i++)
                     {
-                        idParameter.TypedValue = i;
+                        cmd.Parameter.TypedValue = i;
                         cache.Set<CachedWorld>(cacheKeys[i], await ReadSingleRow(cmd));
                     }
                 }
@@ -152,13 +159,14 @@ namespace PlatformBenchmarks
             {
                 await db.OpenAsync();
 
-                var (queryCmd, queryParameter) = CreateReadCommand(db);
-                using (queryCmd)
+                using (var cmd = CreateReadCommand())
                 {
+                    cmd.Connection = db;
+                    var queryParameter = cmd.Parameter;
                     for (int i = 0; i < results.Length; i++)
                     {
-                        results[i] = await ReadSingleRow(queryCmd);
-                        queryParameter.TypedValue = _random.Next(1, 10001);
+                        queryParameter.TypedValue = _random.Next(0, 10000) + 1;
+                        results[i] = await ReadSingleRow(cmd);
                     }
                 }
 
@@ -192,7 +200,9 @@ namespace PlatformBenchmarks
             {
                 await db.OpenAsync();
 
-                using (var cmd = new NpgsqlCommand("SELECT id, message FROM fortune", db))
+                using var cmd = CreateFortuneCommand();
+                cmd.Connection = db;
+
                 using (var rdr = await cmd.ExecuteReaderAsync())
                 {
                     while (await rdr.ReadAsync())
@@ -212,14 +222,24 @@ namespace PlatformBenchmarks
             return result;
         }
 
-        private (NpgsqlCommand readCmd, NpgsqlParameter<int> idParameter) CreateReadCommand(NpgsqlConnection connection)
+        private static SqlReadCommand CreateReadCommand()
         {
-            var cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = @Id", connection);
-            var parameter = new NpgsqlParameter<int>(parameterName: "@Id", value: _random.Next(1, 10001));
+            if (!_sqlReadCommands.TryDequeue(out var cmd))
+            {
+                cmd = new SqlReadCommand();
+            }
 
-            cmd.Parameters.Add(parameter);
+            return cmd;
+        }
+
+        private static SqlFortuneCommand CreateFortuneCommand()
+        {
+            if (!_sqlFortuneCommands.TryDequeue(out var cmd))
+            {
+                cmd = new SqlFortuneCommand();
+            }
 
-            return (cmd, parameter);
+            return cmd;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -237,7 +257,55 @@ namespace PlatformBenchmarks
             }
         }
 
-        private static readonly object[] _cacheKeys = Enumerable.Range(0, 10001).Select((i) => new CacheKey(i)).ToArray();
+        internal class SqlFortuneCommand : IDisposable
+        {
+            private readonly NpgsqlCommand _cmd;
+
+            public NpgsqlConnection Connection
+            {
+                set { _cmd.Connection = value; }
+            }
+
+            public SqlFortuneCommand()
+            {
+                _cmd = new NpgsqlCommand("SELECT id, message FROM fortune");
+            }
+
+            public Task<NpgsqlDataReader> ExecuteReaderAsync() => _cmd.ExecuteReaderAsync();
+
+            public void Dispose()
+            {
+                _cmd.Connection = null;
+                _sqlFortuneCommands.Enqueue(this);
+            }
+        }
+
+        internal class SqlReadCommand : IDisposable
+        {
+            private readonly NpgsqlCommand _cmd;
+            private readonly NpgsqlParameter<int> _parameter;
+
+            public NpgsqlParameter<int> Parameter => _parameter;
+            public NpgsqlConnection Connection
+            {
+                set { _cmd.Connection = value; }
+            }
+
+            public SqlReadCommand()
+            {
+                _cmd = new NpgsqlCommand("SELECT id, randomnumber FROM world WHERE id = @Id");
+                _parameter = new NpgsqlParameter<int>(parameterName: "@Id", value: 0);
+                _cmd.Parameters.Add(_parameter);
+            }
+
+            public static implicit operator NpgsqlCommand(SqlReadCommand c) => c._cmd;
+
+            public void Dispose()
+            {
+                _cmd.Connection = null;
+                _sqlReadCommands.Enqueue(this);
+            }
+        }
 
         public sealed class CacheKey : IEquatable<CacheKey>
         {

+ 1 - 0
frameworks/CSharp/aspnetcore/aspcore-pgo.dockerfile

@@ -5,6 +5,7 @@ RUN dotnet publish -c Release -o out
 
 FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime
 ENV ASPNETCORE_URLS http://+:8080
+ENV DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS 1
 WORKDIR /app
 COPY --from=build /app/out ./
 COPY Benchmarks/appsettings.json ./appsettings.json

+ 1 - 0
frameworks/CSharp/aspnetcore/aspcore.dockerfile

@@ -5,6 +5,7 @@ RUN dotnet publish -c Release -o out
 
 FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime
 ENV ASPNETCORE_URLS http://+:8080
+ENV DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS 1
 WORKDIR /app
 COPY --from=build /app/out ./
 COPY Benchmarks/appsettings.json ./appsettings.json