Skip to content

Make header and URI parameters ordered#282

Merged
emiago merged 1 commit intoemiago:mainfrom
livekit:params-slice
Feb 5, 2026
Merged

Make header and URI parameters ordered#282
emiago merged 1 commit intoemiago:mainfrom
livekit:params-slice

Conversation

@dennwc
Copy link
Contributor

@dennwc dennwc commented Jan 19, 2026

This changes switches from a map representation of header/URI params to a slice.

What changes:

  • Parameters now preserve the order in which they appear in the original message.
  • Parameters won't be shuffled around during encoding of SIP messages, which helps with testing.
  • Zero value of HeaderParams is now safe to use, it doesn't require NewParams().
  • This change improves performance and reduces allocations (see below).
  • Some minor changes needs to be done to update existing code (if it uses underlying maps directly).

Benchmarking results

~25-30% faster header parsing, ~50-60% less allocations when parsing headers with params.

                               │   old.txt   │               new.txt                │
                               │   sec/op    │    sec/op     vs base                │
HeaderParams/MAP-12              160.0n ± 1%   112.2n ±  1%  -29.87% (p=0.000 n=10)
ParserHeaders/ViaHeader-12       191.6n ± 2%   138.0n ±  1%  -27.99% (p=0.000 n=10)
ParserHeaders/ToHeader-12        154.0n ± 1%   110.7n ±  1%  -28.14% (p=0.000 n=10)
ParserHeaders/FromHeader-12      153.5n ± 0%   109.9n ±  0%  -28.35% (p=0.000 n=10)
ParserHeaders/ContactHeader-12   177.3n ± 1%   136.1n ±  1%  -23.22% (p=0.000 n=10)
ParserHeaders/Route-12           295.5n ± 1%   247.1n ±  1%  -16.40% (p=0.001 n=10)

                               │    old.txt     │                 new.txt                 │
                               │      B/op      │     B/op      vs base                   │
HeaderParams/MAP-12                85.00 ± 1%      208.00 ± 0%  +144.71% (p=0.000 n=10)
ParserHeaders/ViaHeader-12         464.0 ± 0%       128.0 ± 0%   -72.41% (p=0.000 n=10)
ParserHeaders/ToHeader-12          304.0 ± 0%       208.0 ± 0%   -31.58% (p=0.000 n=10)
ParserHeaders/FromHeader-12        304.0 ± 0%       208.0 ± 0%   -31.58% (p=0.000 n=10)
ParserHeaders/ContactHeader-12     304.0 ± 0%       208.0 ± 0%   -31.58% (p=0.000 n=10)
ParserHeaders/Route-12             464.0 ± 0%       288.0 ± 0%   -37.93% (p=0.000 n=10)

                               │   old.txt    │               new.txt                │
                               │  allocs/op   │ allocs/op   vs base                  │
HeaderParams/MAP-12              3.000 ± 0%     4.000 ± 0%  +33.33% (p=0.000 n=10)
ParserHeaders/ViaHeader-12       4.000 ± 0%     2.000 ± 0%  -50.00% (p=0.000 n=10)
ParserHeaders/ToHeader-12        5.000 ± 0%     2.000 ± 0%  -60.00% (p=0.000 n=10)
ParserHeaders/FromHeader-12      5.000 ± 0%     2.000 ± 0%  -60.00% (p=0.000 n=10)
ParserHeaders/ContactHeader-12   5.000 ± 0%     2.000 ± 0%  -60.00% (p=0.000 n=10)
ParserHeaders/Route-12           4.000 ± 0%     3.000 ± 0%  -25.00% (p=0.000 n=10)

The HeaderParams/MAP shows the same performance improvement, but a bit more allocations. I believe this is caused by NewParams() allocating a bit more space when necessary to compensate for the real-world usage.

Typically, the number of parameters is limited by 4-5, which doesn't justify the overhead of Go maps, so these numbers make sense for the real SIP headers.

Compatibility with existing code

Since the underlying type of HeaderParams is different, there are a couple of changes to the client code that must be done:

  • If the code only uses existing functions (NewParams, Get, Add), then no changes are necessary!
  • Initialization must be changed from {"a":"1", "b":"2"} to {{"a","1"}, {"b":"2"}}.
  • Direct lookups via params[k] must be replaced with params.Get(k) or params.GetOr(k, "") (second one omits bool return).

There are some side affects that must be considered:

  • HeaderParams is no longer a reference type, so passing it to functions and expecting changes to propagate requires an extra pointer.
  • Methods like Add and Remove now require a pointer receiver. This should not require changes, since map was always a reference.

@emiago
Copy link
Owner

emiago commented Jan 19, 2026

Hi @dennwc . Nice once more. I had probably somewhere very exact change, but I can not recall why I didn't push it. I guess I didn't saw some improvements. I was annoyed for no order as well. I will have to check, but I believe benchmarks should show some improvements. Generally I know this makes more sense due to low number of key values.

I only see no improvement or need to change/deprecate current API. Please check comments.

Happy to merge this for next BIG release.

@dennwc
Copy link
Contributor Author

dennwc commented Jan 29, 2026

@emiago would you be open to merging this into a separate branch of sipgo, so that we can use it go.mod instead of using replace in all of our projects? We can then open a new PR that merges that branch to main and keep that open until a new big release is ready. Would that work for you?

@emiago
Copy link
Owner

emiago commented Jan 31, 2026

@dennwc You thumbed up my changes request. I think they are small? I just do not find time now todo it myself, but I will merge once they are there?
Branch can be created as well, I do not mind

@dennwc
Copy link
Contributor Author

dennwc commented Feb 3, 2026

Done! Should be good now.

"github.com/arl/statsviz"
"github.com/emiago/sipgo/sip"

_ "net/http/pprof"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you not remove this. I used this example for heavy loads and profiling

@emiago emiago merged commit 70dcd99 into emiago:main Feb 5, 2026
1 check passed
@emiago
Copy link
Owner

emiago commented Feb 5, 2026

@dennwc merged!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants