gfodor / p2pcf

Low cost, low effort P2P WebRTC serverless signalling using Cloudflare Workers
MIT License
964 stars 53 forks source link

perf: migrate from simple-peer to polite-peer #1

Closed ThaUnknown closed 2 years ago

ThaUnknown commented 2 years ago

Please consider migrating from simple-peer to polite-peer: https://github.com/jimmywarting/jimmy.warting.se/blob/master/packages/peer/perfect-negotiation.js [there isn't an NPM package for this but I could make one]

Why this matters: simple-peer relies on stream which in turn relies on buffer, and process which relies on setTimeout. This is an important issue because those require to be polyfilled, and those polyfills are VERY slow. Since WebTorrent was mentioned, from my testing more than half of the app's CPU time was consumed by node's stream and buffer or actually it's polyfills. Another massive issue is the reliance on the slow process polyfill of most bundlers which uses setTimeouts for delying tasks instead of promises or microtask.

Problems: polite-peer is a much simper package than simple-peer [oh the irony] and as such doesn't expose that many wrappers so it might require extra work. Another issue might be the immaturity of the package as very little people actually used it [not to be confused with it wasn't used a lot, because it was used in public projects a lot] so unexpected issues might arise.

ThaUnknown commented 2 years ago

I forgot to explain why using timeouts instead of microtasks is bad: this is because when the browser tab in background all timeouts are capped at [at least] 4ms, this means your 1µs operation all of a sudden becomes 4ms which slows down the operation x4000!

gfodor commented 2 years ago

Thanks for this info - I learned a lot from this summary. I actually had this written using raw RTPPeerConnections and then just pulled in simple-peer to make those have a nicer API (but wasn't thrilled about the bloat) so I'll look into migrating to polite peer.

gfodor commented 2 years ago

Ah polite-peer is just packing in the "perfect negotiation" pattern, I had come across this code before actually, it's quite nice. However, I don't think it's going to work (or add much benefit) here since the negotiation pattern for P2PCF isn't using this approach but a bespoke one that takes advantage of the signalling server's custom protocol. Additionally, polite-peer doesn't have any of the bells and whistles for managing media streams and renegotiation that simple-peer does. Maybe there's another library that has similar features but less bloat + avoids the perf issue you mention?

gfodor commented 2 years ago

Also, I haven't dug into it - but I think the way you'd incur this performance tax is if you ended up using the Stream-like APIs with simple-peer when using P2PCF. I'd have to check to see if that code path gets exercised during the general send function, in which case if it doesn't at least for most data channel based apps doing simple messaging there won't be a tax. I'm guessing they use the stream API in WebTorrent for sending large files between peers.

gfodor commented 2 years ago

In general it does seem like this issue would be best addressed by a PR to simple-peer, it’s a pretty nice library afaict and has a lot of downstream consumers that would benefit from fixing the issue you mention.

ThaUnknown commented 2 years ago

Additionally, polite-peer doesn't have any of the bells and whistles for managing media streams

it exposes the rtcpeerconnection via pc on which you simply run the same mediastream commands as you would on simple-peer as they themselves simply run this._pc.addTrack(track, stream) for this.addTrack which has mostly the same functionality I'm not certain about the re-negotiation stuff

library that has similar features but less bloat

peerjs... but it's in no way lighter

if you ended up using the Stream-like APIs

nope, sending any data runs Buffer.from, so pretty much anything except mediastreams invokes this penalty

would be best addressed by a PR to simple-peer

way ahead of you, I think I have ~12 outstanding PR's to drop readable-stream and buffer all across the webtorrent ecosystem, but they are all stalled as the org is effectively dead, and noone maintains it, so I've been waiting for someone to review them, but that won't happen anytime soon

ThaUnknown commented 2 years ago

coincidentally readable-stream is now 50% of the bundle size ;)

gfodor commented 2 years ago

Ok you’ve convinced me it might be worth taking the useful bits of simple-peer and bringing them into a bespoke Peer class - will investigate this when I can. Agree these stream APIs are not very useful and are adding a lot of ugliness to the whole thing. I wasn’t aware of these aspects so thanks for bringing them up.

Also, what about a fork of simple-peer that lands your PRs? Would that also solve this in a way that might have wider impact?

gfodor commented 2 years ago

Fyi here is the pre-simple peer migration. I did this before getting media streams and renegotiation working since I felt after I got the data channel set up it would be ideal to get on the glide path of an existing library to future proof this from webrtc quirks and make it more accessible to people using a known good library.

https://github.com/gfodor/p2pcf/commit/70f7daff49abf6263c7da85e77dd995a088904ef

ThaUnknown commented 2 years ago

Ok you’ve convinced me it might be worth taking the useful bits of simple-peer and bringing them into a bespoke Peer class - will investigate this when I can. Agree these stream APIs are not very useful and are adding a lot of ugliness to the whole thing. I wasn’t aware of these aspects so thanks for bringing them up.

Also, what about a fork of simple-peer that lands your PRs? Would that also solve this in a way that might have wider impact?

simple-peer is tightly integrated with stream which means when I went to drop stream I ran into a LOT of issues, and only managed to pass ~90% of the tests, that 10% being enough for the entire lib to fail, so the PR is just sitting there waiting for someone to fix it, or at least read the discord message about it

gfodor commented 2 years ago

Basically once the data channel is set up the whole thing looks like a normal webrtc app with a traditional signalling server (but the renegotiations go over the data channel.)

ThaUnknown commented 2 years ago

Basically once the data channel is set up the whole thing looks like a normal webrtc app with a traditional signalling server (but the renegotiations go over the data channel.)

yeah, that's pretty much what polite-peer or perfect-peer does [idk what to call it anymore]

gfodor commented 2 years ago

Yep clean renegotiation is gonna be necessary for whatever we go with - simple-peer does this by asking the initiator to start an offer.

gfodor commented 2 years ago

For P2PCF fortunately both peers have a pre-defined role so a similar mechanism can be used. The perfect negotiation pattern of polite-peer is a generalized solution where both peers have no knowledge of one another or coordination beforehand. For P2PCF this isn’t the case since the signaling server helps coordinate the role assignment. (This is part of the protocol because you actually need to assign roles based on the NAT status of both peers to best avoid unnecessary double hole punching and TURN candidates.)

gfodor commented 2 years ago

(Sorry accidental button tap :))

ThaUnknown commented 2 years ago

I'll pretend like I understood what you said, nod twice and hope you manage to pull some black magic and drop simple-peer sometime soon, I can't wait to be able to use this in my electron projects which can't do signaling via the web push api, which is the hack I used up until now to get signaling for free :p

gfodor commented 2 years ago

https://github.com/gfodor/p2pcf/pull/6

ThaUnknown commented 2 years ago

🥳 @gfodor with these changes pako, stream we went from 156.9kB/46.9kB to 39.6kB/12.5kB

gfodor commented 2 years ago

Yep way better!

Yuu6883 commented 2 years ago

Nice package trimming and probably good optimization changes! If you want to even smaller package size you can also try esbuild option --mangle-props=^_ and possibly have a separate production build of tiny-simple-peer that's independent of debug package. That would probably trim off another 10kb uncompressed, even though it's already good enough rn 👏