From 4b87766347d3cdfba35fcd63a5a31658f8dd18be Mon Sep 17 00:00:00 2001 From: Carroll Vance Date: Fri, 5 Dec 2025 13:26:29 -0600 Subject: [PATCH 1/2] Reduce per request allocations --- src/Curl.jl | 24 +++++++++++------------- src/gRPC.jl | 10 +++++++--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Curl.jl b/src/Curl.jl index f00a337..9814b6f 100644 --- a/src/Curl.jl +++ b/src/Curl.jl @@ -138,21 +138,21 @@ end function grpc_timeout_header_val(timeout::Real) if round(Int, timeout) == timeout timeout_secs = round(Int64, timeout) - return "$(timeout_secs)S" + return "$(string(timeout_secs))S" end timeout *= 1000 if round(Int, timeout) == timeout timeout_millisecs = round(Int64, timeout) - return "$(timeout_millisecs)m" + return "$(string(timeout_millisecs))m" end timeout *= 1000 if round(Int, timeout) == timeout timeout_microsecs = round(Int64, timeout) - return "$(timeout_microsecs)u" + return "$(string(timeout_microsecs))u" end timeout *= 1000 timeout_nanosecs = round(Int64, timeout) - return "$(timeout_nanosecs)n" + return "$(string(timeout_nanosecs))n" end mutable struct gRPCRequest @@ -238,22 +238,19 @@ mutable struct gRPCRequest # Uncomment this for debugging purposes # curl_easy_setopt(easy_handle, CURLOPT_VERBOSE, UInt32(1)) - http_url = replace(url, "grpc://" => "http://") - http_url = replace(http_url, "grpcs://" => "https://") - - curl_easy_setopt(easy_handle, CURLOPT_URL, http_url) + curl_easy_setopt(easy_handle, CURLOPT_URL, url) curl_easy_setopt(easy_handle, CURLOPT_TIMEOUT, deadline) curl_easy_setopt(easy_handle, CURLOPT_PIPEWAIT, Clong(1)) curl_easy_setopt(easy_handle, CURLOPT_POST, Clong(1)) curl_easy_setopt(easy_handle, CURLOPT_CUSTOMREQUEST, "POST") - if startswith(http_url, "http://") + if startswith(url, "http://") curl_easy_setopt( easy_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE, ) - elseif startswith(http_url, "https://") + elseif startswith(url, "https://") curl_easy_setopt(easy_handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS) end @@ -275,7 +272,7 @@ mutable struct gRPCRequest easy_handle, grpc.multi, headers, - http_url, + url, request, 0, response, @@ -797,8 +794,9 @@ function check_multi_info(grpc::gRPCCURL) # The actual cleanup/notification happens here cleanup_request(grpc, req) - # Remove from the list of requests associated - grpc.requests = filter(x -> x !== req, grpc.requests) + # Remove from the list of requests associated (in-place, no allocation) + idx = findfirst(x -> x === req, grpc.requests) + !isnothing(idx) && deleteat!(grpc.requests, idx) else @error("curl_multi_info_read: unknown message", message, maxlog = 1_000) end diff --git a/src/gRPC.jl b/src/gRPC.jl index e14e98b..17ae345 100644 --- a/src/gRPC.jl +++ b/src/gRPC.jl @@ -88,11 +88,15 @@ end function url(client::gRPCServiceClient) protocol = if client.secure - "grpcs" + "https" else - "grpc" + "http" end - "$protocol://$(client.host):$(client.port)$(client.path)" + + # "$protocol://$(client.host):$(client.port)$(client.path)" + buffer = IOBuffer() + write(buffer, protocol, "://", client.host, ":", string(client.port), client.path) + String(take!(buffer)) end From e289687117613b74f8aee71b966410bb621fbd39 Mon Sep 17 00:00:00 2001 From: Carroll Vance Date: Fri, 5 Dec 2025 13:27:05 -0600 Subject: [PATCH 2/2] Update benchmark table format + docs --- README.md | 40 +++++++++++------------ docs/src/index.md | 20 ++++++------ utils/gRPCClientUtils.jl/src/Benchmark.jl | 14 +++----- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 53158f8..2cfcfd9 100644 --- a/README.md +++ b/README.md @@ -22,16 +22,16 @@ By default Julia 1.12 starts with just one thread. The closer to `@async` we get However, it is unlikely Julia will be used this way in the real world. ``` -╭──────────────────────────────────┬─────────┬────────┬─────────────┬──────────┬────────────┬──────────────┬─────────┬──────┬──────╮ -│ Benchmark │ N │ Memory │ Allocations │ Duration │ Throughput │ Avg duration │ Std-dev │ Min │ Max │ -│ │ calls │ MiB │ │ s │ calls/s │ μs │ μs │ μs │ μs │ -├──────────────────────────────────┼─────────┼────────┼─────────────┼──────────┼────────────┼──────────────┼─────────┼──────┼──────┤ -│ workload_smol │ 94000 │ 3.74 │ 85110 │ 5.01 │ 18756 │ 53 │ 4.2 │ 47 │ 71 │ -│ workload_32_224_224_uint8 │ 2800 │ 63.78 │ 9230 │ 5.11 │ 548 │ 1826 │ 378.6 │ 1598 │ 2657 │ -│ workload_streaming_request │ 2566000 │ 0.61 │ 6615 │ 4.99 │ 514001 │ 2 │ 0.61 │ 1 │ 16 │ -│ workload_streaming_response │ 985000 │ 13.0 │ 27721 │ 5.0 │ 197101 │ 5 │ 0.48 │ 4 │ 7 │ -│ workload_streaming_bidirectional │ 2568000 │ 1.98 │ 25503 │ 4.99 │ 514539 │ 2 │ 0.5 │ 1 │ 12 │ -╰──────────────────────────────────┴─────────┴────────┴─────────────┴──────────┴────────────┴──────────────┴─────────┴──────┴──────╯ +╭──────────────────────────────────┬─────────────┬────────────────┬────────────┬──────────────┬─────────┬──────┬──────╮ +│ Benchmark │ Avg Memory │ Avg Allocs │ Throughput │ Avg duration │ Std-dev │ Min │ Max │ +│ │ KiB/message │ allocs/message │ calls/s │ μs │ μs │ μs │ μs │ +├──────────────────────────────────┼─────────────┼────────────────┼────────────┼──────────────┼─────────┼──────┼──────┤ +│ workload_smol │ 2.95 │ 72.5 │ 18424 │ 54 │ 3.54 │ 48 │ 66 │ +│ workload_32_224_224_uint8 │ 637.0 │ 79.1 │ 548 │ 1826 │ 405.48 │ 1602 │ 2730 │ +│ workload_streaming_request │ 0.61 │ 6.6 │ 508983 │ 2 │ 0.67 │ 1 │ 15 │ +│ workload_streaming_response │ 12.99 │ 27.6 │ 194689 │ 5 │ 0.52 │ 4 │ 9 │ +│ workload_streaming_bidirectional │ 1.98 │ 25.5 │ 490718 │ 2 │ 0.59 │ 1 │ 13 │ +╰──────────────────────────────────┴─────────────┴────────────────┴────────────┴──────────────┴─────────┴──────┴──────╯ ``` ### Real World: `julia -t auto` @@ -39,16 +39,16 @@ However, it is unlikely Julia will be used this way in the real world. Using more threads isn't great for async IO, but this is likely how most people will be using `gRPCClient.jl`. ``` -╭──────────────────────────────────┬─────────┬────────┬─────────────┬──────────┬────────────┬──────────────┬─────────┬──────┬──────╮ -│ Benchmark │ N │ Memory │ Allocations │ Duration │ Throughput │ Avg duration │ Std-dev │ Min │ Max │ -│ │ calls │ MiB │ │ s │ calls/s │ μs │ μs │ μs │ μs │ -├──────────────────────────────────┼─────────┼────────┼─────────────┼──────────┼────────────┼──────────────┼─────────┼──────┼──────┤ -│ workload_smol │ 91000 │ 3.75 │ 85123 │ 5.03 │ 18079 │ 55 │ 3.96 │ 48 │ 67 │ -│ workload_32_224_224_uint8 │ 2900 │ 63.78 │ 9188 │ 5.01 │ 579 │ 1728 │ 97.86 │ 1614 │ 1899 │ -│ workload_streaming_request │ 1841000 │ 0.89 │ 6482 │ 4.99 │ 368669 │ 3 │ 1.35 │ 2 │ 21 │ -│ workload_streaming_response │ 330000 │ 13.0 │ 27838 │ 5.02 │ 65771 │ 15 │ 5.2 │ 6 │ 37 │ -│ workload_streaming_bidirectional │ 405000 │ 1.48 │ 25672 │ 5.0 │ 80948 │ 12 │ 8.52 │ 3 │ 62 │ -╰──────────────────────────────────┴─────────┴────────┴─────────────┴──────────┴────────────┴──────────────┴─────────┴──────┴──────╯ +╭──────────────────────────────────┬─────────────┬────────────────┬────────────┬──────────────┬─────────┬──────┬──────╮ +│ Benchmark │ Avg Memory │ Avg Allocs │ Throughput │ Avg duration │ Std-dev │ Min │ Max │ +│ │ KiB/message │ allocs/message │ calls/s │ μs │ μs │ μs │ μs │ +├──────────────────────────────────┼─────────────┼────────────────┼────────────┼──────────────┼─────────┼──────┼──────┤ +│ workload_smol │ 2.95 │ 72.5 │ 18014 │ 56 │ 3.08 │ 50 │ 64 │ +│ workload_32_224_224_uint8 │ 637.0 │ 79.7 │ 567 │ 1762 │ 99.07 │ 1628 │ 1911 │ +│ workload_streaming_request │ 0.86 │ 6.5 │ 341851 │ 3 │ 1.68 │ 2 │ 30 │ +│ workload_streaming_response │ 13.0 │ 27.7 │ 64515 │ 16 │ 5.12 │ 6 │ 33 │ +│ workload_streaming_bidirectional │ 1.41 │ 25.6 │ 102072 │ 10 │ 6.23 │ 4 │ 52 │ +╰──────────────────────────────────┴─────────────┴────────────────┴────────────┴──────────────┴─────────┴──────┴──────╯ ``` ## Acknowledgement diff --git a/docs/src/index.md b/docs/src/index.md index df777a9..de8af06 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -156,16 +156,16 @@ benchmark_table() ``` ``` -╭──────────────────────────────────┬─────────┬────────┬─────────────┬──────────┬────────────┬──────────────┬─────────┬──────┬──────╮ -│ Benchmark │ N │ Memory │ Allocations │ Duration │ Throughput │ Avg duration │ Std-dev │ Min │ Max │ -│ │ calls │ MiB │ │ s │ calls/s │ μs │ μs │ μs │ μs │ -├──────────────────────────────────┼─────────┼────────┼─────────────┼──────────┼────────────┼──────────────┼─────────┼──────┼──────┤ -│ workload_smol │ 94000 │ 3.74 │ 85110 │ 5.01 │ 18756 │ 53 │ 4.2 │ 47 │ 71 │ -│ workload_32_224_224_uint8 │ 2800 │ 63.78 │ 9230 │ 5.11 │ 548 │ 1826 │ 378.6 │ 1598 │ 2657 │ -│ workload_streaming_request │ 2566000 │ 0.61 │ 6615 │ 4.99 │ 514001 │ 2 │ 0.61 │ 1 │ 16 │ -│ workload_streaming_response │ 985000 │ 13.0 │ 27721 │ 5.0 │ 197101 │ 5 │ 0.48 │ 4 │ 7 │ -│ workload_streaming_bidirectional │ 2568000 │ 1.98 │ 25503 │ 4.99 │ 514539 │ 2 │ 0.5 │ 1 │ 12 │ -╰──────────────────────────────────┴─────────┴────────┴─────────────┴──────────┴────────────┴──────────────┴─────────┴──────┴──────╯ +╭──────────────────────────────────┬─────────────┬────────────────┬────────────┬──────────────┬─────────┬──────┬──────╮ +│ Benchmark │ Avg Memory │ Avg Allocs │ Throughput │ Avg duration │ Std-dev │ Min │ Max │ +│ │ KiB/message │ allocs/message │ calls/s │ μs │ μs │ μs │ μs │ +├──────────────────────────────────┼─────────────┼────────────────┼────────────┼──────────────┼─────────┼──────┼──────┤ +│ workload_smol │ 2.95 │ 72.5 │ 18014 │ 56 │ 3.08 │ 50 │ 64 │ +│ workload_32_224_224_uint8 │ 637.0 │ 79.7 │ 567 │ 1762 │ 99.07 │ 1628 │ 1911 │ +│ workload_streaming_request │ 0.86 │ 6.5 │ 341851 │ 3 │ 1.68 │ 2 │ 30 │ +│ workload_streaming_response │ 13.0 │ 27.7 │ 64515 │ 16 │ 5.12 │ 6 │ 33 │ +│ workload_streaming_bidirectional │ 1.41 │ 25.6 │ 102072 │ 10 │ 6.23 │ 4 │ 52 │ +╰──────────────────────────────────┴─────────────┴────────────────┴────────────┴──────────────┴─────────┴──────┴──────╯ ``` ### Stress Workloads diff --git a/utils/gRPCClientUtils.jl/src/Benchmark.jl b/utils/gRPCClientUtils.jl/src/Benchmark.jl index bc20c28..a45c439 100644 --- a/utils/gRPCClientUtils.jl/src/Benchmark.jl +++ b/utils/gRPCClientUtils.jl/src/Benchmark.jl @@ -15,10 +15,8 @@ function perform_benchmark(f, N) return [ f, - N_sample, - mem, - b.allocs, - round(timing, digits = 2), + 1000*mem / N, # Avg Memory + round(b.allocs / N, digits = 1), # Avg Allocs round(Int, N_sample/timing), # Throughput round(Int, mean(timings_us) / N), # Avg duration round(std(timings_us) / N, digits = 2), @@ -33,17 +31,15 @@ function benchmark_table() column_labels = [ [ "Benchmark", - "N", - "Memory", - "Allocations", - "Duration", + "Avg Memory", + "Avg Allocs", "Throughput", "Avg duration", "Std-dev", "Min", "Max", ], - ["", "calls", "MiB", "", "s", "calls/s", "μs", "μs", "μs", "μs"], + ["", "KiB/message", "allocs/message", "calls/s", "μs", "μs", "μs", "μs"], ] all_benchmarks = [ (workload_smol, 1_000),