Skip to content

Perf | Reduce allocations when sending large strings#4072

Open
edwardneal wants to merge 1 commit intodotnet:mainfrom
edwardneal:perf/reduce-string-copies
Open

Perf | Reduce allocations when sending large strings#4072
edwardneal wants to merge 1 commit intodotnet:mainfrom
edwardneal:perf/reduce-string-copies

Conversation

@edwardneal
Copy link
Contributor

Description

At present, SqlClient calculates the number of bytes in a character string (and performs the actual conversions) by calling string.ToCharArray and Encoding.GetByteCount and Encoding.GetBytes. netfx also lacks some of the newer Encoding methods which allow GetByteCount and GetBytes to operate over a specific range of characters in the string.

These two points mean that when sending a 50MB string using netcore, we perform 150MB of allocations. This PR resolves this. It introduces two new netfx-only extension methods in EncodingExtensions which match the netcore method signatures, so we can remove some conditional compilation in TdsParser and use the more performant paths.

This highlighted a second pattern: calling string.ToCharArray(offset, length), then calling GetByteCount or GetBytes on the result. I replaced three instances of this pattern with the more efficient GetByteCount/GetBytes(string, offset, length) call.

One noteworthy instance in the second pattern is in GetEncodingCharLength. It looks like someone else had thought of this before, but it was undone - I don't see anything in the commit history to indicate why.

Benchmarking highlights an across-the-board reduction in memory usage by 66%. Execution time is also cut - about 3-5% for <5MB strings, and about 20-30% for 10MB strings and above.

Issues

None.

Testing

Automated tests should continue to pass. Benchmark results are below.

Benchmarks
Method Job StringSizeMB Mean Error StdDev Median Ratio RatioSD Completed Work Items Lock Contentions Gen0 Gen1 Gen2 Allocated Alloc Ratio
SendLargeVarchar SqlClient-7.0.0-main 1 11.567 ms 0.3831 ms 1.1174 ms 11.535 ms 1.01 0.14 - - 625.0000 625.0000 625.0000 3.01 MB 1.00
SendLargeVarchar SqlClient-7.0.0-perf 1 7.365 ms 0.1853 ms 0.5166 ms 7.255 ms 0.64 0.08 - - 234.3750 234.3750 234.3750 1.01 MB 0.33
SendLargeVarcharAsync SqlClient-7.0.0-main 1 8.301 ms 0.1652 ms 0.2806 ms 8.249 ms 1.00 0.05 3.2031 - 640.6250 640.6250 640.6250 3.01 MB 1.00
SendLargeVarcharAsync SqlClient-7.0.0-perf 1 8.018 ms 0.2120 ms 0.6183 ms 8.020 ms 0.97 0.08 3.3750 - 187.5000 187.5000 187.5000 1.01 MB 0.34
SendLargeVarchar SqlClient-7.0.0-main 5 39.442 ms 1.9343 ms 5.6730 ms 38.935 ms 1.02 0.21 - - 285.7143 285.7143 285.7143 15.01 MB 1.00
SendLargeVarchar SqlClient-7.0.0-perf 5 36.804 ms 1.5362 ms 4.5295 ms 36.967 ms 0.95 0.19 - - - - - 5.01 MB 0.33
SendLargeVarcharAsync SqlClient-7.0.0-main 5 42.262 ms 1.2411 ms 3.6203 ms 41.407 ms 1.01 0.12 18.8571 - 285.7143 285.7143 285.7143 15.02 MB 1.00
SendLargeVarcharAsync SqlClient-7.0.0-perf 5 36.945 ms 1.4749 ms 4.3258 ms 36.898 ms 0.88 0.13 20.2500 - 125.0000 125.0000 125.0000 5.02 MB 0.33
SendLargeVarchar SqlClient-7.0.0-main 10 91.648 ms 3.7073 ms 10.8144 ms 91.117 ms 1.01 0.17 - - 333.3333 333.3333 333.3333 30.01 MB 1.00
SendLargeVarchar SqlClient-7.0.0-perf 10 62.857 ms 2.3273 ms 6.8622 ms 62.176 ms 0.70 0.11 - - - - - 10.01 MB 0.33
SendLargeVarcharAsync SqlClient-7.0.0-main 10 78.835 ms 2.3091 ms 6.5130 ms 78.092 ms 1.01 0.11 43.2857 - 285.7143 285.7143 285.7143 30.03 MB 1.00
SendLargeVarcharAsync SqlClient-7.0.0-perf 10 61.616 ms 1.8269 ms 5.3867 ms 61.493 ms 0.79 0.09 41.8571 - - - - 10.03 MB 0.33
SendLargeVarchar SqlClient-7.0.0-main 25 168.564 ms 3.6049 ms 10.4585 ms 166.763 ms 1.00 0.09 - - 333.3333 333.3333 333.3333 75.01 MB 1.00
SendLargeVarchar SqlClient-7.0.0-perf 25 122.108 ms 2.2688 ms 2.1222 ms 121.483 ms 0.73 0.05 - - - - - 25.01 MB 0.33
SendLargeVarcharAsync SqlClient-7.0.0-main 25 159.280 ms 3.1621 ms 7.8158 ms 157.352 ms 1.00 0.07 100.6667 - 333.3333 333.3333 333.3333 75.05 MB 1.00
SendLargeVarcharAsync SqlClient-7.0.0-perf 25 125.581 ms 3.1097 ms 8.9721 ms 124.814 ms 0.79 0.07 97.6667 - - - - 25.05 MB 0.33
SendLargeVarchar SqlClient-7.0.0-main 50 575.489 ms 126.1522 ms 367.9919 ms 345.035 ms 1.37 1.14 - - - - - 150.01 MB 1.00
SendLargeVarchar SqlClient-7.0.0-perf 50 508.659 ms 129.0977 ms 374.5358 ms 248.768 ms 1.21 1.12 - - - - - 50.01 MB 0.33
SendLargeVarcharAsync SqlClient-7.0.0-main 50 283.127 ms 5.5997 ms 12.8662 ms 280.335 ms 1.00 0.06 205.5000 - - - - 150.1 MB 1.00
SendLargeVarcharAsync SqlClient-7.0.0-perf 50 235.014 ms 5.6108 ms 15.6408 ms 231.605 ms 0.83 0.07 192.0000 - - - - 50.09 MB 0.33

Shim GetByteCount and GetBytes methods, and make sure these are used more widely.
Improves performance on netfx and netcore.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: To triage

Development

Successfully merging this pull request may close these issues.

1 participant