From 138ce3c77d7005e7bbc668efab5630d93e954c5d Mon Sep 17 00:00:00 2001 From: Sean DuBois Date: Wed, 6 Mar 2024 21:27:17 -0500 Subject: [PATCH] Use a sync.Pool for Unmarshal Attributes cause the most memory pressure. Start with caching those Before """ 12904 B/op 48 allocs/op """ After """ 7483 B/op 34 allocs/op """ --- unmarshal.go | 45 ++++++++++++++++++++++++++++++++++++--------- unmarshal_cache.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ util.go | 3 ++- 3 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 unmarshal_cache.go diff --git a/unmarshal.go b/unmarshal.go index 180e54e..4eced76 100644 --- a/unmarshal.go +++ b/unmarshal.go @@ -9,6 +9,7 @@ import ( "net/url" "strconv" "strings" + "sync" ) var ( @@ -16,6 +17,14 @@ var ( errSDPInvalidNumericValue = errors.New("sdp: invalid numeric value") errSDPInvalidValue = errors.New("sdp: invalid value") errSDPInvalidPortValue = errors.New("sdp: invalid port value") + errSDPCacheInvalid = errors.New("sdp: invalid cache") + + //nolint: gochecknoglobals + unmarshalCachePool = sync.Pool{ + New: func() interface{} { + return &unmarshalCache{} + }, + } ) // UnmarshalString is the primary function that deserializes the session description @@ -100,9 +109,17 @@ var ( // | s16 | | 14 | | | | 15 | | | 12 | | | | | | | | | // +--------+----+-------+----+-----+----+-----+---+----+----+---+---+-----+---+---+----+---+----+ func (s *SessionDescription) UnmarshalString(value string) error { + var ok bool l := new(lexer) + if l.cache, ok = unmarshalCachePool.Get().(*unmarshalCache); !ok { + return errSDPCacheInvalid + } + defer unmarshalCachePool.Put(l.cache) + + l.cache.reset() l.desc = s l.value = value + for state := s1; state != nil; { var err error state, err = state(l) @@ -110,6 +127,9 @@ func (s *SessionDescription) UnmarshalString(value string) error { return err } } + + s.Attributes = l.cache.cloneSessionAttributes() + populateMediaAttributes(l.cache, l.desc) return nil } @@ -730,18 +750,19 @@ func unmarshalSessionAttribute(l *lexer) (stateFn, error) { } i := strings.IndexRune(value, ':') - var a Attribute + a := l.cache.getSessionAttribute() if i > 0 { - a = NewAttribute(value[:i], value[i+1:]) + a.Key = value[:i] + a.Value = value[i+1:] } else { - a = NewPropertyAttribute(value) + a.Key = value } - l.desc.Attributes = append(l.desc.Attributes, a) return s11, nil } func unmarshalMediaDescription(l *lexer) (stateFn, error) { + populateMediaAttributes(l.cache, l.desc) var newMediaDesc MediaDescription // @@ -869,15 +890,14 @@ func unmarshalMediaAttribute(l *lexer) (stateFn, error) { } i := strings.IndexRune(value, ':') - var a Attribute + a := l.cache.getMediaAttribute() if i > 0 { - a = NewAttribute(value[:i], value[i+1:]) + a.Key = value[:i] + a.Value = value[i+1:] } else { - a = NewPropertyAttribute(value) + a.Key = value } - latestMediaDesc := l.desc.MediaDescriptions[len(l.desc.MediaDescriptions)-1] - latestMediaDesc.Attributes = append(latestMediaDesc.Attributes, a) return s14, nil } @@ -927,3 +947,10 @@ func parsePort(value string) (int, error) { return port, nil } + +func populateMediaAttributes(c *unmarshalCache, s *SessionDescription) { + if len(s.MediaDescriptions) != 0 { + lastMediaDesc := s.MediaDescriptions[len(s.MediaDescriptions)-1] + lastMediaDesc.Attributes = c.cloneMediaAttributes() + } +} diff --git a/unmarshal_cache.go b/unmarshal_cache.go new file mode 100644 index 0000000..728b305 --- /dev/null +++ b/unmarshal_cache.go @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package sdp + +type unmarshalCache struct { + sessionAttributes []Attribute + mediaAttributes []Attribute +} + +func (c *unmarshalCache) reset() { + c.sessionAttributes = c.sessionAttributes[:0] + c.mediaAttributes = c.mediaAttributes[:0] +} + +func (c *unmarshalCache) getSessionAttribute() *Attribute { + c.sessionAttributes = append(c.sessionAttributes, Attribute{}) + return &c.sessionAttributes[len(c.sessionAttributes)-1] +} + +func (c *unmarshalCache) cloneSessionAttributes() []Attribute { + if len(c.sessionAttributes) == 0 { + return nil + } + s := make([]Attribute, len(c.sessionAttributes)) + copy(s, c.sessionAttributes) + c.sessionAttributes = c.sessionAttributes[:0] + return s +} + +func (c *unmarshalCache) getMediaAttribute() *Attribute { + c.mediaAttributes = append(c.mediaAttributes, Attribute{}) + return &c.mediaAttributes[len(c.mediaAttributes)-1] +} + +func (c *unmarshalCache) cloneMediaAttributes() []Attribute { + if len(c.mediaAttributes) == 0 { + return nil + } + s := make([]Attribute, len(c.mediaAttributes)) + copy(s, c.mediaAttributes) + c.mediaAttributes = c.mediaAttributes[:0] + return s +} diff --git a/util.go b/util.go index 5449dca..f78824b 100644 --- a/util.go +++ b/util.go @@ -311,7 +311,8 @@ func (s *SessionDescription) GetPayloadTypeForCodec(wanted Codec) (uint8, error) type stateFn func(*lexer) (stateFn, error) type lexer struct { - desc *SessionDescription + desc *SessionDescription + cache *unmarshalCache baseLexer }