Reduce string allocations#1639
Conversation
|
This PR introduces some readbility issues in places. Replacing case statements with large if/else blocks is not really desireable. In other cases if the code is going to be changed it should go further and make use of new built in methods. For example, changing: to: would be better as: |
At the moment, C#/.NET is not able to use I'll try to come up with something that makes this more concise.
OOPS! Missed those ones. I'll do another pass. |
|
I reverted back to I have a little devil screaming in the back of my mind to create |
| { | ||
| // Attempt to execute the current command. | ||
| switch (command.ToLower()) | ||
| switch (command) |
There was a problem hiding this comment.
The case statement could be kept clean and readable by just changing the switch to: switch(command.ToLowerInvariant())
| if (args?.Length > 0) | ||
| { | ||
| switch(args[0].ToLower()) | ||
| if (string.Equals(args[0], FFMPEG_VP8_CODEC, StringComparison.OrdinalIgnoreCase) || |
There was a problem hiding this comment.
The original swtich could be left in place, and still get the benefit of the suggestion, by doing:
switch (args[0].ToLowerInvariant())
{
case FFMPEG_VP8_CODEC:
case FFMPEG_VP9_CODEC:
...
There was a problem hiding this comment.
args[0].ToLowerInvariant() this may unnecessarily allocate a string,
| _logger.LogInformation("ICE Candidate: " + message); | ||
|
|
||
| if (string.IsNullOrWhiteSpace(message) || message.Trim().ToLower() == SDP.END_ICE_CANDIDATES_ATTRIBUTE) | ||
| if (string.IsNullOrWhiteSpace(message) || message.AsSpan().Trim().Equals(SDP.END_ICE_CANDIDATES_ATTRIBUTE, StringComparison.OrdinalIgnoreCase)) |
There was a problem hiding this comment.
I think an extension method for this is justified (as per your main comment) given the frequency of use and the fact it's a bit of a mouthful.
There was a problem hiding this comment.
I tried to optimize strings as much as possible in this PR.
I'm sure more pattern will be uncovered.
I'd like propose other optimizations but, as you mention, these can become large PRs
| { | ||
| string trimmedExtension = extension.Trim().ToLower(); | ||
| switch (trimmedExtension) | ||
| var trimmedExtension = extension.Trim(); |
There was a problem hiding this comment.
The case could be kept here as well by using the ToLowerInvariant() in the original swtitch.
|
I agree with the improvements in this PR but it's big and touches some very fundamental classes. Can it be split up even further. The string formatting on all the demo apps and tests are low risk and could be put in a separate PR. The changes to the core SIP classes are the biggest risk and they should be separated into their own PR. |
|
I'm creating a PR for the changes under What is your plan after that? |
Do you mean as far as merging? I don't see any issues merging the changes in the test and examples directories. If one of the PR's was solely for string formatting and spacing that would aslo be straight forward to verify and merge. The bigger changes, particulatly in the |
|
eecb312 to
00cc653
Compare
00cc653 to
97d9d74
Compare
97d9d74 to
d45794a
Compare
d45794a to
404ff61
Compare
97c2ec0 to
c154c83
Compare
|
Hey @sipsorcery, is this small enough now, or do you want more PRs extracted out of this? |
c154c83 to
e25383e
Compare
e25383e to
0f8f02f
Compare
…edia code - Replace concatenated/interpolated log messages with structured templates in DTLS, ICE, FFmpeg, VP8, and Windows media code. - Add a Log.Debug(string messageTemplate, params object[] args) overload in SharpSRTP Log with placeholder formatting support. - Use safer string comparisons (StringComparison.OrdinalIgnoreCase) instead of ToLower()-based checks. - Reduce temporary string allocations in VP8 debug helpers by switching loop concatenation to StringBuilder. - Apply small string formatting cleanups (interpolation simplifications) and discard-pattern cleanup in SCTP switch guards. - Normalize file mode bits for test/FFmpegConsoleApp/Program.cs and test/unit/net/STUN/STUNUnitTest.cs.
0f8f02f to
dd04d27
Compare
Summary
This PR reduces temporary string generation across SIP, RTSP, SDP, RTP, WebRTC, and related example/test code paths. The changes replace allocation-heavy parsing and comparison patterns with ordinal string comparisons,
ReadOnlySpan<char>slicing, destination-span split buffers, and more efficient serialization patterns.The intent is to preserve existing behavior while reducing avoidable allocations in protocol parsing and message formatting hot paths.
Part of: #1434
Why
Protocol parsing in this library runs frequently and often on network-facing hot paths. Several existing patterns created unnecessary intermediate strings or arrays while parsing or comparing protocol text:
ToUpper()/ToLower()before comparisonTrim()andSubstring()for short-lived parser slicesstring.Split(...)where only a few fields are needed+=string concatenation while serializing headers and SDP blocksThese patterns increase GC pressure and make hot-path parsing more expensive than necessary. They can also obscure the intended comparison semantics, especially for protocol tokens where ordinal comparisons are the correct behavior.
What changed
String comparisons
Replaced case-normalizing comparisons such as:
value.ToUpper() == ...value.ToLower() == ...value.ToUpperInvariant().StartsWith(...)value.ToLowerInvariant().Contains(...)with explicit ordinal comparisons, primarily:
StringComparison.OrdinalStringComparison.OrdinalIgnoreCaseThis avoids temporary strings and makes protocol comparison semantics explicit.
Span-based parsing
Reworked many parser paths to operate on
ReadOnlySpan<char>instead of repeatedly materializing strings.This includes parsing in:
The parser logic now generally slices spans and only converts back to
stringat API/storage boundaries where a string is actually required.Split allocation reduction
Replaced several
string.Split(...)usages with span-based splitting.For fixed-shape parsing, the code now uses destination-span split buffers such as:
This preserves the original “exactly N parts” behavior without allocating a string array or intermediate string segments.
Where appropriate, split options are used to preserve intended trimming/removal behavior without extra
Trim()calls, such as SDP line splitting with remove-empty and trim semantics.Trim and substring reduction
Replaced parser-local
Trim()andSubstring()usage with span slicing and span trimming.This avoids creating temporary strings for values that are only needed for comparison, branching, or short-lived parsing decisions.
Strings are still materialized when they are stored in public object properties, dictionaries, collections, or returned through existing APIs.
Regex removal in fixed-format hot paths
Removed regex usage from several parser paths where the format is fixed and can be parsed directly.
Notable examples include:
These paths now use direct span scanning/slicing instead of regex match objects and replacement strings.
Serialization allocation reduction
Reworked repeated string concatenation in message serialization paths.
Changes include:
StringBuilderfor repeated line/block construction+concatenation where interpolation is clearer and lets the compiler choose the appropriate loweringThis primarily affects SIP, RTSP, and SDP serialization code.
Compatibility support
Added Polyfill support and updated
LangVersionto14.0so newer APIs and compiler features can be used consistently across the library’s target frameworks.This makes span/range/split-based parsing available in cross-targeted code while keeping the implementation aligned with modern C# syntax and compiler behavior.
How
The implementation follows a few consistent patterns:
Span<Range>/stackallocsplit buffers for small fixed-field parsing.StringBuilderfor repeated serialization and interpolated strings for fixed-format output.Validation
Validated with:
src\SIPSorcery\SIPSorcery.csprojSome broader test runs may still be affected by unrelated networking/environment issues.