From c0164be4be7c5ffca6b799b81e03eff6559bbb7a Mon Sep 17 00:00:00 2001 From: Vladimir Shchur Date: Mon, 25 Nov 2024 09:05:09 -0800 Subject: [PATCH] [F#/Oxpecker] Improved "update" and "fortunes" benchmarks (#9357) --- frameworks/FSharp/oxpecker/src/App/App.fsproj | 10 ++--- frameworks/FSharp/oxpecker/src/App/Common.fs | 6 ++- frameworks/FSharp/oxpecker/src/App/Db.fs | 31 ++++++++------- frameworks/FSharp/oxpecker/src/App/Program.fs | 39 +++++++------------ .../FSharp/oxpecker/src/App/RenderHelpers.fs | 29 +++++++++----- 5 files changed, 60 insertions(+), 55 deletions(-) diff --git a/frameworks/FSharp/oxpecker/src/App/App.fsproj b/frameworks/FSharp/oxpecker/src/App/App.fsproj index e9aa702a5a1..48564a7779b 100644 --- a/frameworks/FSharp/oxpecker/src/App/App.fsproj +++ b/frameworks/FSharp/oxpecker/src/App/App.fsproj @@ -13,10 +13,10 @@ - - - - - + + + + + \ No newline at end of file diff --git a/frameworks/FSharp/oxpecker/src/App/Common.fs b/frameworks/FSharp/oxpecker/src/App/Common.fs index 47a63e2108e..8d21c46aa64 100644 --- a/frameworks/FSharp/oxpecker/src/App/Common.fs +++ b/frameworks/FSharp/oxpecker/src/App/Common.fs @@ -9,7 +9,7 @@ module Common = [] [] type JsonMessage = { - message : string + message: string } [] @@ -26,7 +26,9 @@ module Common = } [] - let ConnectionString = "Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;Maximum Pool Size=1024;NoResetOnClose=true;Enlist=false;Max Auto Prepare=3" + let ConnectionString = "Server=tfb-database;Database=hello_world;User Id=benchmarkdbuser;Password=benchmarkdbpass;Maximum Pool Size=1024;NoResetOnClose=true;Enlist=false" + [] + let MultiplexedConnectionString = ConnectionString + ";Max Auto Prepare=3;Multiplexing=true" let FortuneComparer = { new IComparer with diff --git a/frameworks/FSharp/oxpecker/src/App/Db.fs b/frameworks/FSharp/oxpecker/src/App/Db.fs index 2c471bada1d..65b3062a8e9 100644 --- a/frameworks/FSharp/oxpecker/src/App/Db.fs +++ b/frameworks/FSharp/oxpecker/src/App/Db.fs @@ -2,8 +2,7 @@ namespace App open System open System.Data -open System.Data.Common -open System.Text +open Microsoft.Extensions.ObjectPool open Npgsql @@ -15,6 +14,7 @@ module Db = use db = new NpgsqlConnection(ConnectionString) use cmd = db.CreateCommand(CommandText = "SELECT id, message FROM fortune") do! db.OpenAsync() + do! cmd.PrepareAsync() use! rdr = cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection) while! rdr.ReadAsync() do result.Add { id = rdr.GetInt32(0); message = rdr.GetString(1) } @@ -31,7 +31,7 @@ module Db = TypedValue = Random.Shared.Next(1, 10001) ) cmd.Parameters.Add(id) |> ignore - cmd + struct(cmd, id) let private readSingleRow (cmd: NpgsqlCommand) = task { @@ -43,41 +43,46 @@ module Db = let loadSingleRow () = task { use db = new NpgsqlConnection(ConnectionString) + let struct(cmd', _) = createReadCommand db + use cmd = cmd' do! db.OpenAsync() - use cmd = createReadCommand db + do! cmd.PrepareAsync() return! readSingleRow cmd } let private readMultipleRows (count: int) (conn: NpgsqlConnection) = let result = Array.zeroCreate count task { - use cmd = createReadCommand conn + let struct(cmd', idParam) = createReadCommand conn + use cmd = cmd' for i in 0..result.Length-1 do - cmd.Parameters["@Id"].Value <- Random.Shared.Next(1, 10001) let! row = readSingleRow cmd result[i] <- row + idParam.TypedValue <- Random.Shared.Next(1, 10001) return result } let loadMultipleRows (count: int) = task { - use db = new NpgsqlConnection(ConnectionString) + use db = new NpgsqlConnection(MultiplexedConnectionString) do! db.OpenAsync() return! readMultipleRows count db } let private maxBatch = 500 let private queries = Array.zeroCreate (maxBatch + 1) + let private stringBuilderPool = DefaultObjectPoolProvider().CreateStringBuilderPool() let private batchUpdateString batchSize = match queries[batchSize] with | null -> let lastIndex = batchSize - 1 - let sb = StringBuilder() + let sb = stringBuilderPool.Get() sb.Append("UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ") |> ignore for i in 0..lastIndex-1 do sb.AppendFormat("(@Id_{0}, @Rn_{0}), ", i) |> ignore sb.AppendFormat("(@Id_{0}, @Rn_{0}) ORDER BY 1) AS temp(id, randomNumber) WHERE temp.id = world.id", lastIndex) |> ignore let result = sb.ToString() + stringBuilderPool.Return(sb) queries[batchSize] <- result result | q -> @@ -95,15 +100,15 @@ module Db = for i in 0..results.Length-1 do let randomNumber = Random.Shared.Next(1, 10001) let struct(rnParamName, idParamName) = paramNames[i] - let random = NpgsqlParameter(ParameterName = rnParamName, DbType = DbType.Int32, TypedValue = randomNumber) - command.Parameters.Add(random) |> ignore - let id = NpgsqlParameter(ParameterName = idParamName, DbType = DbType.Int32, TypedValue = results[i].id) - command.Parameters.Add(id) |> ignore + command.Parameters.Add(NpgsqlParameter( + ParameterName = rnParamName, DbType = DbType.Int32, TypedValue = randomNumber)) |> ignore + command.Parameters.Add(NpgsqlParameter( + ParameterName = idParamName, DbType = DbType.Int32, TypedValue = results[i].id)) |> ignore results[i] <- { results[i] with randomnumber = randomNumber } let doMultipleUpdates (count: int) = task { - use conn = new NpgsqlConnection(ConnectionString) + use conn = new NpgsqlConnection(MultiplexedConnectionString) do! conn.OpenAsync() let! results = readMultipleRows count conn use cmd = conn.CreateCommand(CommandText = batchUpdateString count) diff --git a/frameworks/FSharp/oxpecker/src/App/Program.fs b/frameworks/FSharp/oxpecker/src/App/Program.fs index 895c54c9693..ce301a87b89 100644 --- a/frameworks/FSharp/oxpecker/src/App/Program.fs +++ b/frameworks/FSharp/oxpecker/src/App/Program.fs @@ -2,13 +2,21 @@ namespace App open System open Oxpecker -open System.Runtime.InteropServices [] -module HtmlViews = +module HttpHandlers = + open System.Text + open Microsoft.AspNetCore.Http + open SpanJson open Oxpecker.ViewEngine - let private head, tail = + let private extra = + { + id = 0 + message = "Additional fortune added at request time." + } + + let private fortunesHeadAndTail = (fun (content: HtmlElement) -> html() { head() { @@ -26,32 +34,11 @@ module HtmlViews = } :> HtmlElement ) |> RenderHelpers.prerender - let fortunes (fortunesData: ResizeArray) = - let fragment = __() - for fortune in CollectionsMarshal.AsSpan fortunesData do - tr() { - td() { raw <| string fortune.id } - td() { fortune.message } - } - |> fragment.AddChild - RenderHelpers.combine head tail fragment - -[] -module HttpHandlers = - open System.Text - open Microsoft.AspNetCore.Http - open SpanJson - - let private extra = - { - id = 0 - message = "Additional fortune added at request time." - } - let rec private renderFortunes (ctx: HttpContext) (data: ResizeArray) = data.Add extra data.Sort FortuneComparer - data |> HtmlViews.fortunes |> ctx.WriteHtmlViewChunked + RenderHelpers.CombinedElement(fortunesHeadAndTail, data) + |> ctx.WriteHtmlViewChunked let fortunes : EndpointHandler = fun ctx -> diff --git a/frameworks/FSharp/oxpecker/src/App/RenderHelpers.fs b/frameworks/FSharp/oxpecker/src/App/RenderHelpers.fs index 003fc09d8ce..7a94b2552ba 100644 --- a/frameworks/FSharp/oxpecker/src/App/RenderHelpers.fs +++ b/frameworks/FSharp/oxpecker/src/App/RenderHelpers.fs @@ -1,8 +1,27 @@ module RenderHelpers open System.Text + open App open Oxpecker.ViewEngine + type HeadAndTail = + { + Head: string + Tail: string + } + + [] + type CombinedElement(ht: HeadAndTail, fortunesData: ResizeArray) = + interface HtmlElement with + member this.Render(sb) = + sb.Append(ht.Head) |> ignore + for fortune in fortunesData do + (tr() { + td() { raw <| string fortune.id } + td() { fortune.message } + }).Render(sb) + sb.Append(ht.Tail) |> ignore + let prerender (view: HtmlElement -> HtmlElement) = let sb = StringBuilder() let mutable head = "" @@ -13,12 +32,4 @@ sb.Clear() |> ignore } let readyView = view fakeHole readyView.Render(sb) - (head, sb.ToString()) - - let inline combine (head: string) (tail: string) (hole: HtmlElement) = - { new HtmlElement with - member this.Render(sb) = - sb.Append(head) |> ignore - hole.Render(sb) - sb.Append(tail) |> ignore - } \ No newline at end of file + { Head = head; Tail = sb.ToString() } \ No newline at end of file