-
Notifications
You must be signed in to change notification settings - Fork 1.7k
HighPerformancePion
Pion was cited as not meeting the performance needs of Signal here. The topic was discussed in Slack here, and people suggested we can do things to improve performance. This document describes actionable things we could do, and how to assert we improved performance.
These are the high level concepts we need to adhere too.
Today we maintain buffers internally, and then copy into the user provided buffer.
One example of this is packetio.Buffer
in pion/ice and pion/srtp. As soon as a packet arrives to the ICE Agent it is copied into a packetio.Buffer
.
When the user calls Read
the buffer is copied out of the packetio.Buffer
into the provided buffer.
Instead network input should be copied into buffers provided by the user directly. Avoiding costly extra copies.
Good Copying network input directly into user provided buffers allows us to avoid an extra copy. It also means that we don't have to waste memory with internal buffers.
Bad
If users don't read fast enough data will be lost. packetio.Buffer
allows us to store data if a user isn't reading packets fast enough. Now users will
need to depend on OS buffers for network traffic.
Data that isn't being actively Read by the user will have to be discarded. If RTP packets arrive for the following SSRCes arrive 1, 2, 3
and the user is only
attempting to Read 2
and 3
we need to discard 1
. We can never be sure that users will ever request 1
.
Today modifying values requires a full Marshal/Unmarshal. If you want to change a value we process the entire packet, and then have to repack the entire packet.
One example of this is rtp.Packet
. Changing the SSRC is an expensive operation. To get a SSRC you need to Unmarshal the packet,
then access the SSRC
member of the struct. This requires parsing the entire packet. Then to write it back to the network it requires
the entire packet to be marshaled into a []byte.
Instead we should always pass around a []byte
and define methods upon that. Accessing+Setting SSRC values would just involve update an offset.
API Changes None
Internal Changes
After a selected candidate pair is chosen the Read
provided by the user will be used for network input.
Before the selected candidate pair is chosen we will need to perform copies. We need to read from multiple sockets at once.
Is it possible to use one buffer for multiple Read calls? Should we not do concurrent Reads?
API Changes
We will remove srtp.Session
. This code provides SSRC demuxing, and it isn't possible to prove the callback API without some
sort of internal buffer. Instead callers have to demux SSRC themselves.
Internal Changes
None, only deletion of srtp.Session
API Changes None
Internal Changes
ApplicationData needs to be read directly into the buffer provided by Read
.
API Changes
If we have multiple Read
calls happening for independent SSRCes how do we know which buffer to use? The first Read call will be
passed into the ICE Agent and will read the packet off the network. What happens in the following code if SSRC 3
is the first to arrive?
a := ice.Agent{}
a.Read(buffA) // Read for SSRC 1
a.Read(buffB) // Read for SSRC 2
a.Read(buffC) // Read for SSRC 3
We have two choices I believe
- Reader based API that you instead pass a
*[]byte
- Callback based API
Internal Changes
Out of scope
Sign up for the Golang Slack and join the #pion channel for discussions and support
If you need commercial support/don't want to use public methods you can contact us at team@pion.ly