Browse Source

appMpower version 1.0 (#6614)

* appMpower version 1.0

* Instantiating json serializers only once

Co-authored-by: LLT21 <[email protected]>
LLT21 4 years ago
parent
commit
6196a47d2d
30 changed files with 1619 additions and 0 deletions
  1. 31 0
      frameworks/CSharp/appmpower/README.md
  2. 22 0
      frameworks/CSharp/appmpower/appmpower.dockerfile
  3. 30 0
      frameworks/CSharp/appmpower/benchmark_config.json
  4. 19 0
      frameworks/CSharp/appmpower/config.toml
  5. 17 0
      frameworks/CSharp/appmpower/odbcinst.ini
  6. 8 0
      frameworks/CSharp/appmpower/src/ConnectionStrings.cs
  7. 211 0
      frameworks/CSharp/appmpower/src/Db/PooledCommand.cs
  8. 200 0
      frameworks/CSharp/appmpower/src/Db/PooledConnection.cs
  9. 70 0
      frameworks/CSharp/appmpower/src/Db/PooledConnections.cs
  10. 22 0
      frameworks/CSharp/appmpower/src/Fortune.cs
  11. 46 0
      frameworks/CSharp/appmpower/src/FortunesView.cs
  12. 95 0
      frameworks/CSharp/appmpower/src/HttpApplication.cs
  13. 7 0
      frameworks/CSharp/appmpower/src/JsonMessage.cs
  14. 14 0
      frameworks/CSharp/appmpower/src/JsonMessageSerializer.cs
  15. 9 0
      frameworks/CSharp/appmpower/src/Kestrel/IJsonSerializer.cs
  16. 58 0
      frameworks/CSharp/appmpower/src/Kestrel/Json.cs
  17. 27 0
      frameworks/CSharp/appmpower/src/Kestrel/PlainText.cs
  18. 25 0
      frameworks/CSharp/appmpower/src/Kestrel/ServiceProvider.cs
  19. 58 0
      frameworks/CSharp/appmpower/src/Microsoft/AsciiString.cs
  20. 65 0
      frameworks/CSharp/appmpower/src/Microsoft/BatchUpdateString.cs
  21. 63 0
      frameworks/CSharp/appmpower/src/Microsoft/BufferExtensions.cs
  22. 143 0
      frameworks/CSharp/appmpower/src/Microsoft/BufferWriter.cs
  23. 59 0
      frameworks/CSharp/appmpower/src/Microsoft/StringBuilderCache.cs
  24. 23 0
      frameworks/CSharp/appmpower/src/Microsoft/WriterAdapter.cs
  25. 41 0
      frameworks/CSharp/appmpower/src/Program.cs
  26. 197 0
      frameworks/CSharp/appmpower/src/RawDb.cs
  27. 8 0
      frameworks/CSharp/appmpower/src/World.cs
  28. 15 0
      frameworks/CSharp/appmpower/src/WorldSerializer.cs
  29. 27 0
      frameworks/CSharp/appmpower/src/appMpower.csproj
  30. 9 0
      frameworks/CSharp/appmpower/src/nuget.config

+ 31 - 0
frameworks/CSharp/appmpower/README.md

@@ -0,0 +1,31 @@
+# [appMpower](https://github.com/LLT21/)(.Net) Benchmarking Test
+This includes tests for plaintext, json, db, queries, updates and fortune.
+
+[`appMpower`](https://github.com/LLT21/) is a nativily compiled (AOT) .NET implementation. The native compilation is done with reflection disabled; because the most used PostgreSQL .NET library is not reflection free, the PostgreSQL ODBC driver is used instead.
+
+## Infrastructure Software Versions
+
+**Language**
+
+* C# 7.0
+
+**Platforms**
+
+* .NET Core (Windows and Linux)
+
+**Web Servers**
+
+* [Kestrel](https://github.com/aspnet/KestrelHttpServer)
+
+**Web Stack**
+
+* ASP.NET Core
+
+## Paths & Source for Tests
+
+* [Plaintext](Benchmarks/Program.cs): "/plaintext"
+* [JSON Serialization](Benchmarks/Program.cs): "/json"
+* [Single query](Benchmarks/Program.cs): "/db"
+* [Multiple query](Benchmarks/Program.cs): "/queries"
+* [Updates](Benchmarks/Program.cs): "/updates"
+* [Fortune](Benchmarks/Program.cs): "/fortunes"

+ 22 - 0
frameworks/CSharp/appmpower/appmpower.dockerfile

@@ -0,0 +1,22 @@
+FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
+RUN apt-get update
+RUN apt-get -yqq install clang zlib1g-dev libkrb5-dev libtinfo5
+
+WORKDIR /app
+COPY src .
+RUN dotnet publish -c Release -o out -r linux-x64
+
+FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime
+
+RUN apt-get update
+RUN apt-get install -y unixodbc odbc-postgresql
+
+WORKDIR /etc/
+COPY odbcinst.ini .
+
+WORKDIR /app
+COPY --from=build /app/out ./
+
+EXPOSE 8080
+
+ENTRYPOINT ["./appMpower"]

+ 30 - 0
frameworks/CSharp/appmpower/benchmark_config.json

@@ -0,0 +1,30 @@
+{
+  "framework": "appmpower",
+  "tests": [
+    {
+      "default": {
+        "plaintext_url": "/plaintext",
+        "json_url": "/json",
+        "db_url": "/db",
+        "query_url": "/queries?c=",
+        "update_url": "/updates?c=",
+        "fortune_url": "/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Platform",
+        "database": "Postgres",
+        "framework": "ASP.NET Core",
+        "language": "C#",
+        "orm": "Raw",
+        "platform": ".NET",
+        "flavor": "CoreCLR",
+        "webserver": "Kestrel",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "appMpower",
+        "notes": "",
+        "versus": "aspcore"
+      }
+    }
+  ]
+}

+ 19 - 0
frameworks/CSharp/appmpower/config.toml

@@ -0,0 +1,19 @@
+[framework]
+name = "appmpower"
+
+[main]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?c="
+urls.update = "/updates?c="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Platform"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = ".NET"
+webserver = "kestrel"
+versus = "aspcore"

+ 17 - 0
frameworks/CSharp/appmpower/odbcinst.ini

@@ -0,0 +1,17 @@
+[ODBC]
+Trace=1
+Debug=1
+Pooling=No
+
+[ODBC Drivers]
+PostgreSQL = Installed
+
+;
+;  odbcinst.ini
+;
+[PostgreSQL]
+Description=ODBC for PostgreSQL
+; WARNING: The old psql odbc driver psqlodbc.so is now renamed psqlodbcw.so
+; in version 08.x. Note that the library can also be installed under an other
+; path than /usr/local/lib/ following your installation.
+Driver=/usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so

+ 8 - 0
frameworks/CSharp/appmpower/src/ConnectionStrings.cs

@@ -0,0 +1,8 @@
+namespace appMpower
+{
+   public static class ConnectionStrings
+   {
+      //public const string OdbcConnection = "Driver={PostgreSQL};Server=host.docker.internal;Port=5432;Database=hello_world;Uid=benchmarkdbuser;Pwd=benchmarkdbpass;UseServerSidePrepare=1;Pooling=false";
+      public const string OdbcConnection = "Driver={PostgreSQL};Server=tfb-database;Database=hello_world;Uid=benchmarkdbuser;Pwd=benchmarkdbpass;UseServerSidePrepare=1;Pooling=false";
+   }
+}

+ 211 - 0
frameworks/CSharp/appmpower/src/Db/PooledCommand.cs

@@ -0,0 +1,211 @@
+using System.Data;
+using System.Data.Common;
+using System.Data.Odbc;
+using System.Threading.Tasks;
+
+namespace appMpower.Db
+{
+   public class PooledCommand : IDbCommand
+   {
+      private OdbcCommand _odbcCommand;
+      private PooledConnection _pooledConnection;
+
+      public PooledCommand(PooledConnection pooledConnection)
+      {
+         _odbcCommand = (OdbcCommand)pooledConnection.CreateCommand();
+         _pooledConnection = pooledConnection;
+      }
+
+      public PooledCommand(string commandText, PooledConnection pooledConnection)
+      {
+         pooledConnection.GetCommand(commandText, this);
+      }
+
+      internal PooledCommand(OdbcCommand odbcCommand, PooledConnection pooledConnection)
+      {
+         _odbcCommand = odbcCommand;
+         _pooledConnection = pooledConnection;
+      }
+
+      internal OdbcCommand OdbcCommand
+      {
+         get
+         {
+            return _odbcCommand;
+         }
+         set
+         {
+            _odbcCommand = value;
+         }
+      }
+
+      internal PooledConnection PooledConnection
+      {
+         get
+         {
+            return _pooledConnection;
+         }
+         set
+         {
+            _pooledConnection = value;
+         }
+      }
+
+      public string CommandText
+      {
+         get
+         {
+            return _odbcCommand.CommandText;
+         }
+         set
+         {
+            _odbcCommand.CommandText = value;
+         }
+      }
+
+      public int CommandTimeout
+      {
+         get
+         {
+            return _odbcCommand.CommandTimeout;
+         }
+         set
+         {
+            _odbcCommand.CommandTimeout = value;
+         }
+      }
+      public CommandType CommandType
+      {
+         get
+         {
+            return _odbcCommand.CommandType;
+         }
+         set
+         {
+            _odbcCommand.CommandType = value;
+         }
+      }
+
+#nullable enable
+      public IDbConnection? Connection
+      {
+         get
+         {
+            return _odbcCommand.Connection;
+         }
+         set
+         {
+            _odbcCommand.Connection = (OdbcConnection?)value;
+         }
+      }
+#nullable disable
+
+
+      public IDataParameterCollection Parameters
+      {
+         get
+         {
+            return _odbcCommand.Parameters;
+         }
+      }
+
+#nullable enable
+      public IDbTransaction? Transaction
+      {
+         get
+         {
+            return _odbcCommand.Transaction;
+         }
+         set
+         {
+            _odbcCommand.Transaction = (OdbcTransaction?)value;
+         }
+      }
+#nullable disable
+
+      public UpdateRowSource UpdatedRowSource
+      {
+         get
+         {
+            return _odbcCommand.UpdatedRowSource;
+         }
+         set
+         {
+            _odbcCommand.UpdatedRowSource = value;
+         }
+      }
+      public void Cancel()
+      {
+         _odbcCommand.Cancel();
+      }
+
+      public IDbDataParameter CreateParameter()
+      {
+         return _odbcCommand.CreateParameter();
+      }
+
+      public IDbDataParameter CreateParameter(string name, DbType dbType, object value)
+      {
+         OdbcParameter odbcParameter = null;
+
+         if (this.Parameters.Contains(name))
+         {
+            odbcParameter = (OdbcParameter)this.Parameters[name];
+            odbcParameter.Value = value;
+         }
+         else
+         {
+            odbcParameter = _odbcCommand.CreateParameter();
+
+            odbcParameter.ParameterName = name;
+            odbcParameter.DbType = dbType;
+            odbcParameter.Value = value;
+            this.Parameters.Add(odbcParameter);
+         }
+
+         return odbcParameter;
+      }
+
+      public int ExecuteNonQuery()
+      {
+         return _odbcCommand.ExecuteNonQuery();
+      }
+
+      public IDataReader ExecuteReader()
+      {
+         return _odbcCommand.ExecuteReader();
+      }
+
+      public async Task<int> ExecuteNonQueryAsync()
+      {
+         return await _odbcCommand.ExecuteNonQueryAsync();
+      }
+
+      public async Task<DbDataReader> ExecuteReaderAsync(CommandBehavior behavior)
+      {
+         return await _odbcCommand.ExecuteReaderAsync(behavior);
+      }
+
+      public IDataReader ExecuteReader(CommandBehavior behavior)
+      {
+         return _odbcCommand.ExecuteReader(behavior);
+      }
+
+#nullable enable
+      public object? ExecuteScalar()
+      {
+         return _odbcCommand.ExecuteScalar();
+      }
+#nullable disable
+
+      public void Prepare()
+      {
+         _odbcCommand.Prepare();
+      }
+
+      public void Dispose()
+      {
+         _pooledConnection.ReleaseCommand(this);
+      }
+   }
+}

+ 200 - 0
frameworks/CSharp/appmpower/src/Db/PooledConnection.cs

@@ -0,0 +1,200 @@
+using System;
+using System.Collections.Concurrent;
+using System.Data;
+using System.Data.Odbc;
+using System.Threading.Tasks;
+
+namespace appMpower.Db
+{
+   public class PooledConnection : IDbConnection
+   {
+      private bool _isInUse = true;
+      private byte _number = 0;
+      private OdbcConnection _odbcConnection;
+      private ConcurrentDictionary<string, PooledCommand> _pooledCommands;
+
+      internal PooledConnection()
+      {
+      }
+
+      internal PooledConnection(OdbcConnection odbcConnection)
+      {
+         _odbcConnection = odbcConnection;
+         _pooledCommands = new ConcurrentDictionary<string, PooledCommand>();
+      }
+
+      internal ConcurrentDictionary<string, PooledCommand> PooledCommands
+      {
+         get
+         {
+            return _pooledCommands;
+         }
+         set
+         {
+            _pooledCommands = value;
+         }
+      }
+
+      public bool IsInUse
+      {
+         get
+         {
+            return _isInUse;
+         }
+         set
+         {
+            _isInUse = value;
+         }
+      }
+
+      public byte Number
+      {
+         get
+         {
+            return _number;
+         }
+         set
+         {
+            _number = value;
+         }
+      }
+
+      public OdbcConnection OdbcConnection
+      {
+         get
+         {
+            return _odbcConnection;
+         }
+         set
+         {
+            _odbcConnection = value;
+         }
+      }
+
+      public string ConnectionString
+      {
+         get
+         {
+            return _odbcConnection.ConnectionString;
+         }
+         set
+         {
+            _odbcConnection.ConnectionString = value;
+         }
+      }
+
+      public int ConnectionTimeout
+      {
+         get
+         {
+            return _odbcConnection.ConnectionTimeout;
+         }
+      }
+
+      public string Database
+      {
+         get
+         {
+            return _odbcConnection.Database;
+         }
+      }
+
+      public ConnectionState State
+      {
+         get
+         {
+            return _odbcConnection.State;
+         }
+      }
+
+      public IDbTransaction BeginTransaction()
+      {
+         return _odbcConnection.BeginTransaction();
+      }
+
+      public IDbTransaction BeginTransaction(IsolationLevel il)
+      {
+         return _odbcConnection.BeginTransaction(il);
+      }
+
+      public void ChangeDatabase(string databaseName)
+      {
+         _odbcConnection.ChangeDatabase(databaseName);
+      }
+
+      public void Close()
+      {
+         PooledConnections.ReleaseConnection(this);
+         _isInUse = false;
+      }
+
+      public IDbCommand CreateCommand()
+      {
+         return _odbcConnection.CreateCommand();
+      }
+
+      public OdbcCommand CreateOdbcCommand()
+      {
+         return _odbcConnection.CreateCommand();
+      }
+
+      public void Open()
+      {
+         if (_odbcConnection.State == ConnectionState.Closed)
+         {
+            _odbcConnection.Open();
+         }
+      }
+
+      public void Dispose()
+      {
+         if (_isInUse && _odbcConnection.State == ConnectionState.Open)
+         {
+            PooledConnections.ReleaseConnection(this);
+            _isInUse = false;
+         }
+      }
+
+      public async Task OpenAsync()
+      {
+         if (_odbcConnection.State == ConnectionState.Closed)
+         {
+            try
+            {
+               await _odbcConnection.OpenAsync();
+            }
+            catch (Exception exception)
+            {
+               Console.WriteLine(exception.Message);
+            }
+         }
+      }
+
+      internal PooledCommand GetCommand(string commandText, PooledCommand pooledCommand)
+      {
+         PooledCommand internalCommand;
+
+         if (_pooledCommands.TryRemove(commandText, out internalCommand))
+         {
+            pooledCommand.OdbcCommand = internalCommand.OdbcCommand;
+            pooledCommand.PooledConnection = internalCommand.PooledConnection;
+         }
+         else
+         {
+            pooledCommand.OdbcCommand = new OdbcCommand(commandText, this.OdbcConnection);
+            pooledCommand.PooledConnection = this;
+            _pooledCommands.TryAdd(commandText, pooledCommand);
+
+            //Console.WriteLine("prepare pool connection: " + this._number + " for command " + _pooledCommands.Count);
+            pooledCommand.OdbcCommand.Prepare();
+         }
+
+         return pooledCommand;
+      }
+
+      public void ReleaseCommand(PooledCommand pooledCommand)
+      {
+         _pooledCommands.TryAdd(pooledCommand.CommandText, pooledCommand);
+      }
+   }
+}

+ 70 - 0
frameworks/CSharp/appmpower/src/Db/PooledConnections.cs

@@ -0,0 +1,70 @@
+using System;
+using System.Collections.Concurrent;
+using System.Data.Odbc;
+using System.Threading.Tasks;
+
+namespace appMpower.Db
+{
+   public static class PooledConnections
+   {
+      private static int _maxLoops = 999;
+      private static byte _createdConnections = 0;
+      private static byte _maxConnections = Math.Min((byte)Environment.ProcessorCount, (byte)21);
+      private static ConcurrentStack<PooledConnection> _stack = new ConcurrentStack<PooledConnection>();
+
+      public static async Task<PooledConnection> GetConnection(string connectionString)
+      {
+         int i = 0;
+         PooledConnection pooledConnection;
+
+         if (_createdConnections < _maxConnections)
+         {
+            pooledConnection = new PooledConnection();
+            pooledConnection.OdbcConnection = new OdbcConnection(connectionString);
+            _createdConnections++;
+            pooledConnection.Number = _createdConnections;
+            pooledConnection.PooledCommands = new ConcurrentDictionary<string, PooledCommand>();
+            //Console.WriteLine("opened connection number: " + pooledConnection.Number);
+
+            return pooledConnection;
+         }
+         else
+         {
+            while (!_stack.TryPop(out pooledConnection) && i < _maxLoops)
+            {
+               if (i < 5) await Task.Delay(1);
+               else if (i < 10) await Task.Delay(2);
+               else if (i < 25) await Task.Delay(3);
+               else if (i < 50) await Task.Delay(4);
+               else if (i < 100) await Task.Delay(5);
+               else if (i < 500) await Task.Delay(10);
+               else await Task.Delay(20);
+
+               i++;
+               //Console.WriteLine("waiting: " + i);
+            }
+
+            if (i < _maxLoops)
+            {
+               //Console.WriteLine("opened connection number: " + pooledConnection.Number);
+               return pooledConnection;
+            }
+            else
+            {
+               throw new Exception("No connections are available");
+            }
+         }
+      }
+
+      public static void ReleaseConnection(PooledConnection pooledConnection)
+      {
+         PooledConnection stackedConnection = new PooledConnection();
+
+         stackedConnection.OdbcConnection = pooledConnection.OdbcConnection;
+         stackedConnection.Number = pooledConnection.Number;
+         stackedConnection.PooledCommands = pooledConnection.PooledCommands;
+
+         _stack.Push(stackedConnection);
+      }
+   }
+}

+ 22 - 0
frameworks/CSharp/appmpower/src/Fortune.cs

@@ -0,0 +1,22 @@
+using System;
+
+namespace appMpower
+{
+   public readonly struct Fortune : IComparable<Fortune>, IComparable
+   {
+      public Fortune(int id, string message)
+      {
+         Id = id;
+         Message = message;
+      }
+
+      public int Id { get; }
+
+      public string Message { get; }
+
+      public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used");
+
+      // Performance critical, using culture insensitive comparison
+      public int CompareTo(Fortune other) => string.CompareOrdinal(Message, other.Message);
+   }
+}

+ 46 - 0
frameworks/CSharp/appmpower/src/FortunesView.cs

@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using System.Web;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Primitives;
+using PlatformBenchmarks;
+
+namespace appMpower
+{
+   public static class FortunesView
+   {
+      private readonly static KeyValuePair<string, StringValues> _headerServer =
+         new KeyValuePair<string, StringValues>("Server", "k");
+      private readonly static KeyValuePair<string, StringValues> _headerContentType =
+         new KeyValuePair<string, StringValues>("Content-Type", "text/html; charset=UTF-8");
+
+      private readonly static AsciiString _fortunesTableStart = "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>";
+      private readonly static AsciiString _fortunesRowStart = "<tr><td>";
+      private readonly static AsciiString _fortunesColumn = "</td><td>";
+      private readonly static AsciiString _fortunesRowEnd = "</td></tr>";
+      private readonly static AsciiString _fortunesTableEnd = "</table></body></html>";
+
+      public static void Render(IHeaderDictionary headerDictionary, PipeWriter pipeWriter, List<Fortune> fortunes)
+      {
+         headerDictionary.Add(_headerServer);
+         headerDictionary.Add(_headerContentType);
+
+         var bufferWriter = new BufferWriter<WriterAdapter>(new WriterAdapter(pipeWriter), 1600);
+
+         bufferWriter.Write(_fortunesTableStart);
+
+         foreach (var fortune in fortunes)
+         {
+            bufferWriter.Write(_fortunesRowStart);
+            bufferWriter.WriteNumeric((uint)fortune.Id);
+            bufferWriter.Write(_fortunesColumn);
+            bufferWriter.WriteUtf8String(HttpUtility.HtmlEncode(fortune.Message));
+            bufferWriter.Write(_fortunesRowEnd);
+         }
+
+         bufferWriter.Write(_fortunesTableEnd);
+         headerDictionary.Add(new KeyValuePair<string, StringValues>("Content-Length", bufferWriter.Buffered.ToString()));
+         bufferWriter.Commit();
+      }
+   }
+}

+ 95 - 0
frameworks/CSharp/appmpower/src/HttpApplication.cs

@@ -0,0 +1,95 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using appMpower.Kestrel;
+using PlatformBenchmarks;
+
+namespace appMpower
+{
+   public class HttpApplication : IHttpApplication<IFeatureCollection>
+   {
+      private readonly static AsciiString _plainText = "Hello, World!";
+      private readonly static JsonMessageSerializer _jsonMessageSerializer = new JsonMessageSerializer();
+      private readonly static WorldSerializer _worldSerializer = new WorldSerializer();
+
+      public IFeatureCollection CreateContext(IFeatureCollection featureCollection)
+      {
+         return featureCollection;
+      }
+
+      public async Task ProcessRequestAsync(IFeatureCollection featureCollection)
+      {
+         var request = featureCollection as IHttpRequestFeature;
+         var httpResponse = featureCollection as IHttpResponseFeature;
+         var httpResponseBody = featureCollection as IHttpResponseBodyFeature;
+
+         PathString pathString = request.Path;
+
+         if (pathString.HasValue)
+         {
+            int pathStringLength = pathString.Value.Length;
+            string pathStringStart = pathString.Value.Substring(1, 1);
+
+            if (pathStringLength == 10 && pathStringStart == "p")
+            {
+               PlainText.Render(httpResponse.Headers, httpResponseBody.Writer, _plainText);
+               return;
+            }
+            else if (pathStringLength == 5 && pathStringStart == "j")
+            {
+               Json.RenderOne(httpResponse.Headers, httpResponseBody.Writer, new JsonMessage { message = "Hello, World!" }, _jsonMessageSerializer);
+               return;
+            }
+            else if (pathStringLength == 3 && pathStringStart == "d")
+            {
+               Json.RenderOne(httpResponse.Headers, httpResponseBody.Writer, await RawDb.LoadSingleQueryRow(), _worldSerializer);
+               return;
+            }
+            else if (pathStringLength == 8 && pathStringStart == "q")
+            {
+               int count = 1;
+
+               if (!Int32.TryParse(request.QueryString.Substring(request.QueryString.LastIndexOf("=") + 1), out count) || count < 1)
+               {
+                  count = 1;
+               }
+               else if (count > 500)
+               {
+                  count = 500;
+               }
+
+               //Json.RenderMany(httpResponse.Headers, httpResponseBody.Writer, await RawDb.LoadMultipleQueriesRows(count), _worldSerializer);
+               Json.RenderMany(httpResponse.Headers, httpResponseBody.Writer, await RawDb.ReadMultipleRows(count), _worldSerializer);
+               return;
+            }
+            else if (pathStringLength == 9 && pathStringStart == "f")
+            {
+               FortunesView.Render(httpResponse.Headers, httpResponseBody.Writer, await RawDb.LoadFortunesRows());
+               return;
+            }
+            else if (pathStringLength == 8 && pathStringStart == "u")
+            {
+               int count = 1;
+
+               if (!Int32.TryParse(request.QueryString.Substring(request.QueryString.LastIndexOf("=") + 1), out count) || count < 1)
+               {
+                  count = 1;
+               }
+               else if (count > 500)
+               {
+                  count = 500;
+               }
+
+               Json.RenderMany(httpResponse.Headers, httpResponseBody.Writer, await RawDb.LoadMultipleUpdatesRows(count), _worldSerializer);
+               return;
+            }
+         }
+      }
+
+      public void DisposeContext(IFeatureCollection featureCollection, Exception exception)
+      {
+      }
+   }
+}

+ 7 - 0
frameworks/CSharp/appmpower/src/JsonMessage.cs

@@ -0,0 +1,7 @@
+namespace appMpower
+{
+   public struct JsonMessage
+   {
+      public string message { get; set; }
+   }
+}

+ 14 - 0
frameworks/CSharp/appmpower/src/JsonMessageSerializer.cs

@@ -0,0 +1,14 @@
+using System.Text.Json;
+
+namespace appMpower
+{
+   public class JsonMessageSerializer : Kestrel.IJsonSerializer<JsonMessage>
+   {
+      public void Serialize(Utf8JsonWriter utf8JsonWriter, JsonMessage jsonMessage)
+      {
+         utf8JsonWriter.WriteStartObject();
+         utf8JsonWriter.WriteString("message", jsonMessage.message);
+         utf8JsonWriter.WriteEndObject();
+      }
+   }
+}

+ 9 - 0
frameworks/CSharp/appmpower/src/Kestrel/IJsonSerializer.cs

@@ -0,0 +1,9 @@
+using System.Text.Json;
+
+namespace appMpower.Kestrel
+{
+   public interface IJsonSerializer<T>
+   {
+      public void Serialize(Utf8JsonWriter utf8JsonWriter, T t);
+   }
+}

+ 58 - 0
frameworks/CSharp/appmpower/src/Kestrel/Json.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using System.Text.Json;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Primitives;
+
+namespace appMpower.Kestrel
+{
+   public static class Json
+   {
+      private readonly static KeyValuePair<string, StringValues> _headerServer =
+         new KeyValuePair<string, StringValues>("Server", "k");
+      private readonly static KeyValuePair<string, StringValues> _headerContentType =
+         new KeyValuePair<string, StringValues>("Content-Type", "application/json");
+
+      [ThreadStatic]
+      private static Utf8JsonWriter _utf8JsonWriter;
+
+      public static JsonWriterOptions _jsonWriterOptions = new JsonWriterOptions
+      {
+         SkipValidation = true
+      };
+
+      public static void RenderOne<T>(IHeaderDictionary headerDictionary, PipeWriter pipeWriter, T t, IJsonSerializer<T> jsonSerializer)
+      {
+         headerDictionary.Add(_headerServer);
+         headerDictionary.Add(_headerContentType);
+
+         Utf8JsonWriter utf8JsonWriter = _utf8JsonWriter ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
+         utf8JsonWriter.Reset(pipeWriter);
+
+         jsonSerializer.Serialize(utf8JsonWriter, t);
+         utf8JsonWriter.Flush();
+         headerDictionary.Add(new KeyValuePair<string, StringValues>("Content-Length", ((uint)utf8JsonWriter.BytesCommitted).ToString()));
+      }
+
+      public static void RenderMany<T>(IHeaderDictionary headerDictionary, PipeWriter pipeWriter, T[] tArray, IJsonSerializer<T> jsonSerializer)
+      {
+         headerDictionary.Add(_headerServer);
+         headerDictionary.Add(_headerContentType);
+
+         Utf8JsonWriter utf8JsonWriter = _utf8JsonWriter ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
+         utf8JsonWriter.Reset(pipeWriter);
+
+         utf8JsonWriter.WriteStartArray();
+
+         foreach (var t in tArray)
+         {
+            jsonSerializer.Serialize(utf8JsonWriter, t);
+         }
+
+         utf8JsonWriter.WriteEndArray();
+         utf8JsonWriter.Flush();
+         headerDictionary.Add(new KeyValuePair<string, StringValues>("Content-Length", ((uint)utf8JsonWriter.BytesCommitted).ToString()));
+      }
+   }
+}

+ 27 - 0
frameworks/CSharp/appmpower/src/Kestrel/PlainText.cs

@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using System.IO.Pipelines;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Primitives;
+using PlatformBenchmarks;
+
+namespace appMpower.Kestrel
+{
+   public static class PlainText
+   {
+      private readonly static KeyValuePair<string, StringValues> _headerServer =
+         new KeyValuePair<string, StringValues>("Server", new StringValues("k"));
+      private readonly static KeyValuePair<string, StringValues> _headerContentType =
+         new KeyValuePair<string, StringValues>("Content-Type", new StringValues("text/plain"));
+
+      public static void Render(IHeaderDictionary headerDictionary, PipeWriter pipeWriter, AsciiString utf8String)
+      {
+         headerDictionary.Add(_headerServer);
+         headerDictionary.Add(_headerContentType);
+         headerDictionary.Add(new KeyValuePair<string, StringValues>("Content-Length", utf8String.Length.ToString()));
+
+         var bufferWriter = new BufferWriter<WriterAdapter>(new WriterAdapter(pipeWriter), 208);
+         bufferWriter.Write(utf8String);
+         bufferWriter.Commit();
+      }
+   }
+}

+ 25 - 0
frameworks/CSharp/appmpower/src/Kestrel/ServiceProvider.cs

@@ -0,0 +1,25 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace appMpower.Kestrel
+{
+   public class ServiceProvider : ISupportRequiredService, IServiceProvider
+   {
+      public object GetRequiredService(Type serviceType)
+      {
+         return GetService(serviceType);
+      }
+
+      public object GetService(Type serviceType)
+      {
+         if (serviceType == typeof(ILoggerFactory))
+         {
+            return NullLoggerFactory.Instance;
+         }
+
+         return null;
+      }
+   }
+}

+ 58 - 0
frameworks/CSharp/appmpower/src/Microsoft/AsciiString.cs

@@ -0,0 +1,58 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text;
+
+namespace PlatformBenchmarks
+{
+   public readonly struct AsciiString : IEquatable<AsciiString>
+   {
+      private readonly byte[] _data;
+
+      public AsciiString(string s) => _data = Encoding.ASCII.GetBytes(s);
+
+      private AsciiString(byte[] b) => _data = b;
+
+      public int Length => _data.Length;
+
+      public ReadOnlySpan<byte> AsSpan() => _data;
+
+      public static implicit operator ReadOnlySpan<byte>(AsciiString str) => str._data;
+      public static implicit operator byte[](AsciiString str) => str._data;
+
+      public static implicit operator AsciiString(string str) => new AsciiString(str);
+
+      public override string ToString() => Encoding.ASCII.GetString(_data);
+      public static explicit operator string(AsciiString str) => str.ToString();
+
+      public bool Equals(AsciiString other) => ReferenceEquals(_data, other._data) || SequenceEqual(_data, other._data);
+      private bool SequenceEqual(byte[] data1, byte[] data2) => new Span<byte>(data1).SequenceEqual(data2);
+
+      public static bool operator ==(AsciiString a, AsciiString b) => a.Equals(b);
+      public static bool operator !=(AsciiString a, AsciiString b) => !a.Equals(b);
+      public override bool Equals(object other) => (other is AsciiString) && Equals((AsciiString)other);
+
+      public static AsciiString operator +(AsciiString a, AsciiString b)
+      {
+         var result = new byte[a.Length + b.Length];
+         a._data.CopyTo(result, 0);
+         b._data.CopyTo(result, a.Length);
+         return new AsciiString(result);
+      }
+
+      public override int GetHashCode()
+      {
+         // Copied from x64 version of string.GetLegacyNonRandomizedHashCode()
+         // https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/String.Comparison.cs
+         var data = _data;
+         int hash1 = 5381;
+         int hash2 = hash1;
+         foreach (int b in data)
+         {
+            hash1 = ((hash1 << 5) + hash1) ^ b;
+         }
+         return hash1 + (hash2 * 1566083941);
+      }
+   }
+}

+ 65 - 0
frameworks/CSharp/appmpower/src/Microsoft/BatchUpdateString.cs

@@ -0,0 +1,65 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System.Linq;
+
+namespace PlatformBenchmarks
+{
+   internal class BatchUpdateString
+   {
+      private const int MaxBatch = 500;
+
+      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();
+      internal static readonly string[] Jds = Enumerable.Range(0, MaxBatch).Select(i => $"j{i}").ToArray();
+
+      private static string[] _queries = new string[MaxBatch + 1];
+
+      public static string Query(int batchSize)
+      {
+         if (_queries[batchSize] != null)
+         {
+            return _queries[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.Append("UPDATE world SET randomNumber = CAST(temp.randomNumber AS INTEGER) FROM (VALUES ");
+         //Enumerable.Range(0, lastIndex).ToList().ForEach(i => sb.Append("(?, ?), "));
+         //sb.Append("(?, ?) ORDER BY 1) AS temp(id, randomNumber) WHERE CAST(temp.id AS INTEGER) = world.id");
+
+         sb.Append("UPDATE world SET randomNumber = CASE id ");
+
+         for (int i = 0; i < batchSize; i++)
+         {
+            sb.Append("WHEN ? THEN ? ");
+         }
+
+         //Enumerable.Range(0, batchSize).ToList().ForEach(i => sb.Append("WHEN ? THEN ? "));
+         sb.Append("ELSE randomnumber END WHERE id IN (");
+
+         for (int i = 0; i < lastIndex; i++)
+         {
+            sb.Append("?, ");
+         }
+
+         //Enumerable.Range(0, lastIndex).ToList().ForEach(i => sb.Append("?, "));
+         sb.Append("?)");
+
+         return _queries[batchSize] = StringBuilderCache.GetStringAndRelease(sb);
+      }
+   }
+}

+ 63 - 0
frameworks/CSharp/appmpower/src/Microsoft/BufferExtensions.cs

@@ -0,0 +1,63 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace PlatformBenchmarks
+{
+   // Same as KestrelHttpServer\src\Kestrel.Core\Internal\Http\PipelineExtensions.cs
+   // However methods accept T : struct, IBufferWriter<byte> rather than PipeWriter.
+   // This allows a struct wrapper to turn CountingBufferWriter into a non-shared generic,
+   // while still offering the WriteNumeric extension.
+
+   public static class BufferExtensions
+   {
+      private const int _maxULongByteLength = 20;
+
+      [ThreadStatic]
+      private static byte[] _numericBytesScratch;
+
+      internal static void WriteUtf8String<T>(ref this BufferWriter<T> buffer, string text)
+           where T : struct, IBufferWriter<byte>
+      {
+         var byteCount = Encoding.UTF8.GetByteCount(text);
+         buffer.Ensure(byteCount);
+         byteCount = Encoding.UTF8.GetBytes(text.AsSpan(), buffer.Span);
+         buffer.Advance(byteCount);
+      }
+      [MethodImpl(MethodImplOptions.NoInlining)]
+      internal static void WriteNumericMultiWrite<T>(ref this BufferWriter<T> buffer, uint number)
+           where T : IBufferWriter<byte>
+      {
+         const byte AsciiDigitStart = (byte)'0';
+
+         var value = number;
+         var position = _maxULongByteLength;
+         var byteBuffer = NumericBytesScratch;
+         do
+         {
+            // Consider using Math.DivRem() if available
+            var quotient = value / 10;
+            byteBuffer[--position] = (byte)(AsciiDigitStart + (value - quotient * 10)); // 0x30 = '0'
+            value = quotient;
+         }
+         while (value != 0);
+
+         var length = _maxULongByteLength - position;
+         buffer.Write(new ReadOnlySpan<byte>(byteBuffer, position, length));
+      }
+
+      private static byte[] NumericBytesScratch => _numericBytesScratch ?? CreateNumericBytesScratch();
+
+      [MethodImpl(MethodImplOptions.NoInlining)]
+      private static byte[] CreateNumericBytesScratch()
+      {
+         var bytes = new byte[_maxULongByteLength];
+         _numericBytesScratch = bytes;
+         return bytes;
+      }
+   }
+}

+ 143 - 0
frameworks/CSharp/appmpower/src/Microsoft/BufferWriter.cs

@@ -0,0 +1,143 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+
+namespace PlatformBenchmarks
+{
+   public ref struct BufferWriter<T> where T : IBufferWriter<byte>
+   {
+      private T _output;
+      private Span<byte> _span;
+      private int _buffered;
+
+      public BufferWriter(T output, int sizeHint)
+      {
+         _buffered = 0;
+         _output = output;
+         _span = output.GetSpan(sizeHint);
+      }
+
+      public Span<byte> Span => _span;
+
+      public T Output => _output;
+
+      public int Buffered => _buffered;
+
+      [MethodImpl(MethodImplOptions.AggressiveInlining)]
+      public void Commit()
+      {
+         var buffered = _buffered;
+         if (buffered > 0)
+         {
+            _buffered = 0;
+            _output.Advance(buffered);
+         }
+      }
+
+      [MethodImpl(MethodImplOptions.AggressiveInlining)]
+      public void Advance(int count)
+      {
+         _buffered += count;
+         _span = _span.Slice(count);
+      }
+
+      [MethodImpl(MethodImplOptions.AggressiveInlining)]
+      public void Write(ReadOnlySpan<byte> source)
+      {
+         if (_span.Length >= source.Length)
+         {
+            source.CopyTo(_span);
+            Advance(source.Length);
+         }
+         else
+         {
+            WriteMultiBuffer(source);
+         }
+      }
+
+      [MethodImpl(MethodImplOptions.AggressiveInlining)]
+      public void Ensure(int count = 1)
+      {
+         if (_span.Length < count)
+         {
+            EnsureMore(count);
+         }
+      }
+
+      [MethodImpl(MethodImplOptions.NoInlining)]
+      private void EnsureMore(int count = 0)
+      {
+         if (_buffered > 0)
+         {
+            Commit();
+         }
+
+         _span = _output.GetSpan(count);
+      }
+
+      private void WriteMultiBuffer(ReadOnlySpan<byte> source)
+      {
+         while (source.Length > 0)
+         {
+            if (_span.Length == 0)
+            {
+               EnsureMore();
+            }
+
+            var writable = Math.Min(source.Length, _span.Length);
+            source.Slice(0, writable).CopyTo(_span);
+            source = source.Slice(writable);
+            Advance(writable);
+         }
+      }
+
+      [MethodImpl(MethodImplOptions.AggressiveInlining)]
+      internal void WriteNumeric(uint number)
+      {
+         const byte AsciiDigitStart = (byte)'0';
+
+         var span = this.Span;
+
+         // Fast path, try copying to the available memory directly
+         var advanceBy = 0;
+         if (span.Length >= 3)
+         {
+            if (number < 10)
+            {
+               span[0] = (byte)(number + AsciiDigitStart);
+               advanceBy = 1;
+            }
+            else if (number < 100)
+            {
+               var tens = (byte)((number * 205u) >> 11); // div10, valid to 1028
+
+               span[0] = (byte)(tens + AsciiDigitStart);
+               span[1] = (byte)(number - (tens * 10) + AsciiDigitStart);
+               advanceBy = 2;
+            }
+            else if (number < 1000)
+            {
+               var digit0 = (byte)((number * 41u) >> 12); // div100, valid to 1098
+               var digits01 = (byte)((number * 205u) >> 11); // div10, valid to 1028
+
+               span[0] = (byte)(digit0 + AsciiDigitStart);
+               span[1] = (byte)(digits01 - (digit0 * 10) + AsciiDigitStart);
+               span[2] = (byte)(number - (digits01 * 10) + AsciiDigitStart);
+               advanceBy = 3;
+            }
+         }
+
+         if (advanceBy > 0)
+         {
+            Advance(advanceBy);
+         }
+         else
+         {
+            BufferExtensions.WriteNumericMultiWrite(ref this, number);
+         }
+      }
+   }
+}

+ 59 - 0
frameworks/CSharp/appmpower/src/Microsoft/StringBuilderCache.cs

@@ -0,0 +1,59 @@
+// Copyright (c) .NET Foundation. All rights reserved. 
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 
+
+using System;
+using System.Text;
+
+namespace PlatformBenchmarks
+{
+   internal static class StringBuilderCache
+   {
+      private const int DefaultCapacity = 1386;
+      private const int MaxBuilderSize = DefaultCapacity * 3;
+
+      [ThreadStatic]
+      private static StringBuilder t_cachedInstance;
+
+      /// <summary>Get a StringBuilder for the specified capacity.</summary>
+      /// <remarks>If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied.</remarks>
+      public static StringBuilder Acquire(int capacity = DefaultCapacity)
+      {
+         if (capacity <= MaxBuilderSize)
+         {
+            StringBuilder sb = t_cachedInstance;
+            if (capacity < DefaultCapacity)
+            {
+               capacity = DefaultCapacity;
+            }
+
+            if (sb != null)
+            {
+               // Avoid stringbuilder block fragmentation by getting a new StringBuilder
+               // when the requested size is larger than the current capacity
+               if (capacity <= sb.Capacity)
+               {
+                  t_cachedInstance = null;
+                  sb.Clear();
+                  return sb;
+               }
+            }
+         }
+         return new StringBuilder(capacity);
+      }
+
+      public static void Release(StringBuilder sb)
+      {
+         if (sb.Capacity <= MaxBuilderSize)
+         {
+            t_cachedInstance = sb;
+         }
+      }
+
+      public static string GetStringAndRelease(StringBuilder sb)
+      {
+         string result = sb.ToString();
+         Release(sb);
+         return result;
+      }
+   }
+}

+ 23 - 0
frameworks/CSharp/appmpower/src/Microsoft/WriterAdapter.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Buffers;
+using System.IO.Pipelines;
+
+namespace PlatformBenchmarks
+{
+   public struct WriterAdapter : IBufferWriter<byte>
+   {
+      public PipeWriter Writer;
+
+      public WriterAdapter(PipeWriter writer)
+          => Writer = writer;
+
+      public void Advance(int count)
+          => Writer.Advance(count);
+
+      public Memory<byte> GetMemory(int sizeHint = 0)
+          => Writer.GetMemory(sizeHint);
+
+      public Span<byte> GetSpan(int sizeHint = 0)
+          => Writer.GetSpan(sizeHint);
+   }
+}

+ 41 - 0
frameworks/CSharp/appmpower/src/Program.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+
+namespace appMpower
+{
+   class Program
+   {
+      static async Task Main(string[] args)
+      {
+         var socketTransportOptions = new SocketTransportOptions();
+         var socketTransportFactory = new SocketTransportFactory(Options.Create(socketTransportOptions), NullLoggerFactory.Instance);
+         var kestrelServerOptions = new KestrelServerOptions();
+
+         kestrelServerOptions.Listen(IPAddress.Any, 8080);
+         kestrelServerOptions.AddServerHeader = false;
+
+         using var kestrelServer = new KestrelServer(Options.Create(kestrelServerOptions), socketTransportFactory, NullLoggerFactory.Instance);
+
+         await kestrelServer.StartAsync(new HttpApplication(), CancellationToken.None);
+
+         Console.WriteLine("Listening on:");
+
+         foreach (var address in kestrelServer.Features.Get<IServerAddressesFeature>().Addresses)
+         {
+            Console.WriteLine(" - " + address);
+         }
+
+         Console.WriteLine("Process CTRL+C to quit");
+         var wh = new ManualResetEventSlim();
+         Console.CancelKeyPress += (sender, e) => wh.Set();
+         wh.Wait();
+      }
+   }
+}

+ 197 - 0
frameworks/CSharp/appmpower/src/RawDb.cs

@@ -0,0 +1,197 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Text;
+using System.Threading.Tasks;
+using appMpower.Db;
+
+namespace appMpower
+{
+   public static class RawDb
+   {
+      private const int MaxBatch = 500;
+      private static Random _random = new Random();
+      private static string[] _queriesMultipleRows = new string[MaxBatch + 1];
+
+      public static async Task<World> LoadSingleQueryRow()
+      {
+         using var pooledConnection = await PooledConnections.GetConnection(ConnectionStrings.OdbcConnection);
+         await pooledConnection.OpenAsync();
+
+         var (pooledCommand, _) = CreateReadCommand(pooledConnection);
+
+         using (pooledCommand)
+         {
+            return await ReadSingleRow(pooledCommand);
+         }
+      }
+
+      public static async Task<World[]> LoadMultipleQueriesRows(int count)
+      {
+         var worlds = new World[count];
+
+         using var pooledConnection = await PooledConnections.GetConnection(ConnectionStrings.OdbcConnection);
+         await pooledConnection.OpenAsync();
+
+         var (pooledCommand, dbDataParameter) = CreateReadCommand(pooledConnection);
+
+         using (pooledCommand)
+         {
+            for (int i = 0; i < count; i++)
+            {
+               worlds[i] = await ReadSingleRow(pooledCommand);
+               dbDataParameter.Value = _random.Next(1, 10001);
+            }
+         }
+
+         return worlds;
+      }
+
+      public static async Task<List<Fortune>> LoadFortunesRows()
+      {
+         var fortunes = new List<Fortune>();
+
+         using var pooledConnection = await PooledConnections.GetConnection(ConnectionStrings.OdbcConnection);
+         await pooledConnection.OpenAsync();
+
+         var pooledCommand = new PooledCommand("SELECT id, message FROM fortune", pooledConnection);
+
+         using (pooledCommand)
+         {
+            using var dataReader = await pooledCommand.ExecuteReaderAsync(CommandBehavior.SingleResult);
+
+            while (await dataReader.ReadAsync())
+            {
+               fortunes.Add(new Fortune
+               (
+                   id: dataReader.GetInt32(0),
+                   message: dataReader.GetString(1)
+               ));
+            }
+         }
+
+         fortunes.Add(new Fortune(id: 0, message: "Additional fortune added at request time."));
+         fortunes.Sort();
+
+         return fortunes;
+      }
+
+      public static async Task<World[]> LoadMultipleUpdatesRows(int count)
+      {
+         var worlds = new World[count];
+
+         using var pooledConnection = await PooledConnections.GetConnection(ConnectionStrings.OdbcConnection);
+         await pooledConnection.OpenAsync();
+
+         var (queryCommand, dbDataParameter) = CreateReadCommand(pooledConnection);
+
+         using (queryCommand)
+         {
+            for (int i = 0; i < count; i++)
+            {
+               worlds[i] = await ReadSingleRow(queryCommand);
+               dbDataParameter.Value = _random.Next(1, 10001);
+            }
+         }
+
+         var updateCommand = new PooledCommand(PlatformBenchmarks.BatchUpdateString.Query(count), pooledConnection);
+
+         using (updateCommand)
+         {
+            var ids = PlatformBenchmarks.BatchUpdateString.Ids;
+            var randoms = PlatformBenchmarks.BatchUpdateString.Randoms;
+            var jds = PlatformBenchmarks.BatchUpdateString.Jds;
+
+            for (int i = 0; i < count; i++)
+            {
+               var randomNumber = _random.Next(1, 10001);
+
+               updateCommand.CreateParameter(ids[i], DbType.Int32, worlds[i].Id);
+               updateCommand.CreateParameter(randoms[i], DbType.Int32, randomNumber);
+
+               worlds[i].RandomNumber = randomNumber;
+            }
+
+            for (int i = 0; i < count; i++)
+            {
+               updateCommand.CreateParameter(jds[i], DbType.Int32, worlds[i].Id);
+            }
+
+            await updateCommand.ExecuteNonQueryAsync();
+         }
+
+         return worlds;
+      }
+
+      private static (PooledCommand pooledCommand, IDbDataParameter dbDataParameter) CreateReadCommand(PooledConnection pooledConnection)
+      {
+         var pooledCommand = new PooledCommand("SELECT id, randomnumber FROM world WHERE id =?", pooledConnection);
+         var dbDataParameter = pooledCommand.CreateParameter("@Id", DbType.Int32, _random.Next(1, 10001));
+
+         return (pooledCommand, dbDataParameter);
+      }
+
+      private static async Task<World> ReadSingleRow(PooledCommand pooledCommand)
+      {
+         using var dataReader = await pooledCommand.ExecuteReaderAsync(CommandBehavior.SingleRow);
+         await dataReader.ReadAsync();
+
+         return new World
+         {
+            Id = dataReader.GetInt32(0),
+            RandomNumber = dataReader.GetInt32(1)
+         };
+      }
+
+      public static async Task<World[]> ReadMultipleRows(int count)
+      {
+         int j = 0;
+         var ids = PlatformBenchmarks.BatchUpdateString.Ids;
+         var worlds = new World[count];
+         string queryString;
+
+         if (_queriesMultipleRows[count] != null)
+         {
+            queryString = _queriesMultipleRows[count];
+         }
+         else
+         {
+            var stringBuilder = PlatformBenchmarks.StringBuilderCache.Acquire();
+
+            for (int i = 0; i < count; i++)
+            {
+               stringBuilder.Append("SELECT id, randomnumber FROM world WHERE id =?;");
+            }
+
+            queryString = _queriesMultipleRows[count] = PlatformBenchmarks.StringBuilderCache.GetStringAndRelease(stringBuilder);
+         }
+
+         using var pooledConnection = await PooledConnections.GetConnection(ConnectionStrings.OdbcConnection);
+         await pooledConnection.OpenAsync();
+
+         using var pooledCommand = new PooledCommand(queryString, pooledConnection);
+
+         for (int i = 0; i < count; i++)
+         {
+            pooledCommand.CreateParameter(ids[i], DbType.Int32, _random.Next(1, 10001));
+         }
+
+         using var dataReader = await pooledCommand.ExecuteReaderAsync(CommandBehavior.Default);
+
+         do
+         {
+            await dataReader.ReadAsync();
+
+            worlds[j] = new World
+            {
+               Id = dataReader.GetInt32(0),
+               RandomNumber = dataReader.GetInt32(1)
+            };
+
+            j++;
+         } while (await dataReader.NextResultAsync());
+
+         return worlds;
+      }
+   }
+}

+ 8 - 0
frameworks/CSharp/appmpower/src/World.cs

@@ -0,0 +1,8 @@
+namespace appMpower
+{
+   public struct World
+   {
+      public int Id { get; set; }
+      public int RandomNumber { get; set; }
+   }
+}

+ 15 - 0
frameworks/CSharp/appmpower/src/WorldSerializer.cs

@@ -0,0 +1,15 @@
+using System.Text.Json;
+
+namespace appMpower
+{
+   public class WorldSerializer : Kestrel.IJsonSerializer<World>
+   {
+      public void Serialize(Utf8JsonWriter utf8JsonWriter, World world)
+      {
+         utf8JsonWriter.WriteStartObject();
+         utf8JsonWriter.WriteNumber("id", world.Id);
+         utf8JsonWriter.WriteNumber("randomNumber", world.RandomNumber);
+         utf8JsonWriter.WriteEndObject();
+      }
+   }
+}

+ 27 - 0
frameworks/CSharp/appmpower/src/appMpower.csproj

@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net5.0</TargetFramework>
+    <OutputType>Exe</OutputType>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+
+    <TrimMode>link</TrimMode>
+
+    <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
+    <SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
+    <InvariantGlobalization>true</InvariantGlobalization>
+    <IlcOptimizationPreference>Speed</IlcOptimizationPreference>
+    <IlcDisableReflection>true</IlcDisableReflection>
+    <IlcGenerateStackTraceData>false</IlcGenerateStackTraceData>
+
+    <UseSystemResourceKeys>true</UseSystemResourceKeys>
+    <EventSourceSupport>false</EventSourceSupport>
+    <DebuggerSupport>false</DebuggerSupport>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="System.Data.Odbc" Version="5.0.0" />
+    <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="6.0.0-*" />
+  </ItemGroup>
+
+</Project>

+ 9 - 0
frameworks/CSharp/appmpower/src/nuget.config

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <packageSources>
+    <!--To inherit the global NuGet package sources remove the <clear/> line below -->
+    <clear />
+    <add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
+    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
+  </packageSources>
+</configuration>