diff --git a/http_perf_simple.fsx b/http_perf_simple.fsx
new file mode 100644
index 000000000..57f746994
--- /dev/null
+++ b/http_perf_simple.fsx
@@ -0,0 +1,80 @@
+// HTTP String Processing Performance Test - Simple Version
+#r "src/FSharp.Data.Http/bin/Release/netstandard2.0/FSharp.Data.Http.dll"
+
+open System.Diagnostics
+open System.Text
+open FSharp.Data
+
+// Simple form data test
+let testFormDataParsing () =
+ let formData = [
+ ("username", "testuser123")
+ ("password", "secretpassword")
+ ("email", "user@example.com")
+ ("firstName", "John")
+ ("lastName", "Doe")
+ ]
+
+ printfn "Testing form data encoding performance..."
+ let sw = Stopwatch.StartNew()
+ for i in 1..10000 do
+ let body = FormValues formData
+ ignore body
+ sw.Stop()
+
+ printfn "Form data encoding (10,000 operations): %dms (~%.4fms per operation)"
+ sw.ElapsedMilliseconds (float sw.ElapsedMilliseconds / 10000.0)
+
+// Basic string building test to show the concept
+let testStringBuilding () =
+ printfn "Testing string building optimizations concept..."
+
+ // Simulate before optimization (string concatenation)
+ let testStringConcat () =
+ let mutable result = "param1=value1"
+ for i in 2..100 do
+ result <- result + "¶m" + string i + "=value" + string i
+ result
+
+ // Simulate after optimization (StringBuilder)
+ let testStringBuilder () =
+ let sb = StringBuilder("param1=value1")
+ for i in 2..100 do
+ sb.Append("¶m") |> ignore
+ sb.Append(string i) |> ignore
+ sb.Append("=value") |> ignore
+ sb.Append(string i) |> ignore
+ sb.ToString()
+
+ // Time string concatenation approach
+ let sw1 = Stopwatch.StartNew()
+ for i in 1..1000 do
+ ignore (testStringConcat())
+ sw1.Stop()
+
+ // Time StringBuilder approach
+ let sw2 = Stopwatch.StartNew()
+ for i in 1..1000 do
+ ignore (testStringBuilder())
+ sw2.Stop()
+
+ printfn "String concatenation (1,000 ops): %dms (~%.4fms per op)" sw1.ElapsedMilliseconds (float sw1.ElapsedMilliseconds / 1000.0)
+ printfn "StringBuilder (1,000 ops): %dms (~%.4fms per op)" sw2.ElapsedMilliseconds (float sw2.ElapsedMilliseconds / 1000.0)
+ printfn "StringBuilder improvement: %.1fx faster" (float sw1.ElapsedMilliseconds / float sw2.ElapsedMilliseconds)
+
+printfn "=== HTTP String Processing Performance Analysis ==="
+printfn ""
+testFormDataParsing()
+printfn ""
+testStringBuilding()
+printfn ""
+printfn "HTTP Performance Optimizations Implemented:"
+printfn "1. Cookie processing - StringBuilder instead of string concatenation"
+printfn "2. URL query string building - StringBuilder for parameter encoding"
+printfn "3. Form data encoding - StringBuilder for key=value&key=value patterns"
+printfn "4. XML root wrapping - StringBuilder for content pattern"
+printfn ""
+printfn "Expected Benefits:"
+printfn "- Reduced memory allocations in HTTP request processing"
+printfn "- Better performance for high-throughput HTTP scenarios"
+printfn "- Lower GC pressure during form submissions and API calls"
\ No newline at end of file
diff --git a/http_perf_test.fsx b/http_perf_test.fsx
new file mode 100644
index 000000000..5a94d4c79
--- /dev/null
+++ b/http_perf_test.fsx
@@ -0,0 +1,64 @@
+// HTTP String Processing Performance Test
+#r "src/FSharp.Data.Http/bin/Release/netstandard2.0/FSharp.Data.Http.dll"
+#r "src/FSharp.Data.Runtime.Utilities/bin/Release/netstandard2.0/FSharp.Data.Runtime.Utilities.dll"
+
+open System.Diagnostics
+open FSharp.Data
+
+// Test 1: HTTP request with query parameters (tests internal query string building)
+let testQueryStringBuilding () =
+ let queryParams = [
+ ("page", "1")
+ ("limit", "100")
+ ("filter", "active")
+ ("sort", "name")
+ ("include", "details")
+ ("format", "json")
+ ]
+
+ let sw = Stopwatch.StartNew()
+ for i in 1..1000 do // Reduced iterations since this makes actual requests
+ try
+ let result = Http.RequestString("https://httpbin.org/get", query = queryParams)
+ ignore result
+ with
+ | _ -> () // Ignore network errors
+ sw.Stop()
+
+ printfn "HTTP requests with query params (1,000 operations): %dms (~%.3fms per operation)"
+ sw.ElapsedMilliseconds (float sw.ElapsedMilliseconds / 1000.0)
+
+// Test 2: Form data encoding
+let testFormDataEncoding () =
+ let formData = [
+ ("username", "testuser123")
+ ("password", "secretpassword")
+ ("email", "user@example.com")
+ ("firstName", "John")
+ ("lastName", "Doe")
+ ("phoneNumber", "+1-555-123-4567")
+ ]
+
+ let sw = Stopwatch.StartNew()
+ for i in 1..10000 do
+ let body = FormValues formData
+ ignore body
+ sw.Stop()
+
+ printfn "Form data encoding (10,000 operations): %dms (~%.3fms per operation)"
+ sw.ElapsedMilliseconds (float sw.ElapsedMilliseconds / 10000.0)
+
+printfn "=== HTTP String Processing Performance Test ==="
+testQueryStringBuilding()
+testFormDataEncoding()
+printfn ""
+printfn "Performance optimizations implemented:"
+printfn "1. URL query string building - Replaced string concatenation with StringBuilder"
+printfn "2. Form data encoding - Replaced string concatenation with StringBuilder"
+printfn "3. Cookie processing - Replaced string concatenation with StringBuilder"
+printfn "4. XML root element wrapping - Replaced string concatenation with StringBuilder"
+printfn ""
+printfn "Expected benefits:"
+printfn "- Reduced memory allocations during HTTP operations"
+printfn "- Better performance for applications with high HTTP throughput"
+printfn "- Improved GC pressure in HTTP-intensive scenarios"
\ No newline at end of file
diff --git a/src/FSharp.Data.Http/Http.fs b/src/FSharp.Data.Http/Http.fs
index e394ad507..366500beb 100644
--- a/src/FSharp.Data.Http/Http.fs
+++ b/src/FSharp.Data.Http/Http.fs
@@ -1882,7 +1882,13 @@ module internal CookieHandling =
while i < cookiesWithWrongSplit.Length - 1
&& isInvalidCookie cookiesWithWrongSplit.[i + 1] do
- currentCookie <- currentCookie + "," + cookiesWithWrongSplit.[i + 1]
+ let sb =
+ StringBuilder(currentCookie.Length + 1 + cookiesWithWrongSplit.[i + 1].Length)
+
+ sb.Append(currentCookie) |> ignore
+ sb.Append(",") |> ignore
+ sb.Append(cookiesWithWrongSplit.[i + 1]) |> ignore
+ currentCookie <- sb.ToString()
i <- i + 1
@@ -2008,9 +2014,22 @@ type Http private () =
match query with
| [] -> url
| query ->
- url
- + if url.Contains "?" then "&" else "?"
- + String.concat "&" [ for k, v in query -> Uri.EscapeDataString k + "=" + Uri.EscapeDataString v ]
+ let sb = StringBuilder(url.Length + 32) // Estimate initial capacity
+ sb.Append(url) |> ignore
+ sb.Append(if url.Contains "?" then "&" else "?") |> ignore
+
+ let mutable isFirst = true
+
+ for k, v in query do
+ if not isFirst then
+ sb.Append("&") |> ignore
+
+ sb.Append(Uri.EscapeDataString k) |> ignore
+ sb.Append("=") |> ignore
+ sb.Append(Uri.EscapeDataString v) |> ignore
+ isFirst <- false
+
+ sb.ToString()
static member private InnerRequest
(
@@ -2077,9 +2096,19 @@ type Http private () =
| BinaryUpload bytes -> HttpContentTypes.Binary, (fun _ -> new MemoryStream(bytes) :> _)
| FormValues values ->
let bytes (e: Encoding) =
- [ for k, v in values -> Http.EncodeFormData k + "=" + Http.EncodeFormData v ]
- |> String.concat "&"
- |> e.GetBytes
+ let sb = StringBuilder()
+ let mutable isFirst = true
+
+ for k, v in values do
+ if not isFirst then
+ sb.Append("&") |> ignore
+
+ sb.Append(Http.EncodeFormData k) |> ignore
+ sb.Append("=") |> ignore
+ sb.Append(Http.EncodeFormData v) |> ignore
+ isFirst <- false
+
+ sb.ToString() |> e.GetBytes
HttpContentTypes.FormValues, (fun e -> new MemoryStream(bytes e) :> _)
| Multipart(boundary, parts) -> HttpContentTypes.Multipart(boundary), writeMultipart boundary parts
diff --git a/src/FSharp.Data.Xml.Core/XmlRuntime.fs b/src/FSharp.Data.Xml.Core/XmlRuntime.fs
index bc7fb3844..9c5f82756 100644
--- a/src/FSharp.Data.Xml.Core/XmlRuntime.fs
+++ b/src/FSharp.Data.Xml.Core/XmlRuntime.fs
@@ -6,6 +6,7 @@ namespace FSharp.Data.Runtime.BaseTypes
open System.ComponentModel
open System.IO
+open System.Text
open System.Xml.Linq
#nowarn "10001"
@@ -74,7 +75,12 @@ type XmlElement =
|> Seq.map (fun value -> { XElement = value })
|> Seq.toArray
with _ when text.TrimStart().StartsWith "<" ->
- XDocument.Parse("" + text + "", LoadOptions.PreserveWhitespace).Root.Elements()
+ let sb = StringBuilder(text.Length + 13) // 13 = length of ""
+ sb.Append("") |> ignore
+ sb.Append(text) |> ignore
+ sb.Append("") |> ignore
+
+ XDocument.Parse(sb.ToString(), LoadOptions.PreserveWhitespace).Root.Elements()
|> Seq.map (fun value -> { XElement = value })
|> Seq.toArray