StandardPacketBroadcaster: Improve performance when broadcasting small packets

In refactors during PM4, I stripped out packet buffer caching, as it was problematic when events alter packets in undetectable ways.
However, I never cleaned this part of the code up properly after enabling DataPacketSendEvent to include multiple packets and multiple targets, so we were still individually encoding the packet(s) for every single session if the sum total of the sizes was below 256 bytes.

This change encodes packets once in the StandardPacketBroadcaster and retains their buffers to post to the session's send buffer directly if the resulting batch is below compression threshold.
This code is still not optimal (see ##5589), but fixing this brings broadcasting performance back to PM3 levels, without any of PM3's problems.
This commit is contained in:
Dylan K. Taylor 2023-02-22 21:51:49 +00:00
parent 75bb4f8da6
commit 6a64486f55
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
2 changed files with 33 additions and 32 deletions

View File

@ -177,7 +177,7 @@ class NetworkSession{
private ?EncryptionContext $cipher = null;
/** @var ClientboundPacket[] */
/** @var string[] */
private array $sendBuffer = [];
/**
@ -264,23 +264,6 @@ class NetworkSession{
);
}
/**
* @param ClientboundPacket[] $packets
*/
public static function encodePacketBatchTimed(BinaryStream $stream, PacketSerializerContext $context, array $packets) : void{
PacketBatch::encodeRaw($stream, array_map(function(ClientboundPacket $packet) use ($context) : string{
$timings = Timings::getEncodeDataPacketTimings($packet);
$timings->startTiming();
try{
$stream = PacketSerializer::encoder($context);
$packet->encode($stream);
return $stream->getBuffer();
}finally{
$timings->stopTiming();
}
}, $packets));
}
private function onPlayerCreated(Player $player) : void{
if(!$this->isConnected()){
//the remote player might have disconnected before spawn terrain generation was finished
@ -498,7 +481,7 @@ class NetworkSession{
return false;
}
$this->addToSendBuffer($packet);
$this->addToSendBuffer(self::encodePacketTimed(PacketSerializer::encoder($this->packetSerializerContext), $packet));
if($immediate){
$this->flushSendBuffer(true);
}
@ -512,16 +495,24 @@ class NetworkSession{
/**
* @internal
*/
public function addToSendBuffer(ClientboundPacket $packet) : void{
$timings = Timings::getSendDataPacketTimings($packet);
public static function encodePacketTimed(PacketSerializer $serializer, ClientboundPacket $packet) : string{
$timings = Timings::getEncodeDataPacketTimings($packet);
$timings->startTiming();
try{
$this->sendBuffer[] = $packet;
$packet->encode($serializer);
return $serializer->getBuffer();
}finally{
$timings->stopTiming();
}
}
/**
* @internal
*/
public function addToSendBuffer(string $buffer) : void{
$this->sendBuffer[] = $buffer;
}
private function flushSendBuffer(bool $immediate = false) : void{
if(count($this->sendBuffer) > 0){
Timings::$playerNetworkSend->startTiming();
@ -534,7 +525,7 @@ class NetworkSession{
}
$stream = new BinaryStream();
self::encodePacketBatchTimed($stream, $this->packetSerializerContext, $this->sendBuffer);
PacketBatch::encodeRaw($stream, $this->sendBuffer);
if($this->enableCompression){
$promise = $this->server->prepareBatch(new PacketBatch($stream->getBuffer()), $this->compressor, $syncMode);

View File

@ -23,25 +23,35 @@ declare(strict_types=1);
namespace pocketmine\network\mcpe;
use pocketmine\network\mcpe\protocol\ClientboundPacket;
use pocketmine\network\mcpe\protocol\serializer\PacketBatch;
use pocketmine\network\mcpe\protocol\serializer\PacketSerializer;
use pocketmine\Server;
use pocketmine\utils\BinaryStream;
use function array_map;
use function spl_object_id;
final class StandardPacketBroadcaster implements PacketBroadcaster{
public function __construct(private Server $server){}
public function broadcastPackets(array $recipients, array $packets) : void{
$buffers = [];
$batchBuffers = [];
/** @var string[][] $packetBuffers */
$packetBuffers = [];
$compressors = [];
/** @var NetworkSession[][][] $targetMap */
$targetMap = [];
foreach($recipients as $recipient){
$serializerContext = $recipient->getPacketSerializerContext();
$bufferId = spl_object_id($serializerContext);
if(!isset($buffers[$bufferId])){
if(!isset($batchBuffers[$bufferId])){
$packetBuffers[$bufferId] = array_map(function(ClientboundPacket $packet) use ($serializerContext) : string{
return NetworkSession::encodePacketTimed(PacketSerializer::encoder($serializerContext), $packet);
}, $packets);
$stream = new BinaryStream();
NetworkSession::encodePacketBatchTimed($stream, $serializerContext, $packets);
$buffers[$bufferId] = $stream->getBuffer();
PacketBatch::encodeRaw($stream, $packetBuffers[$bufferId]);
$batchBuffers[$bufferId] = $stream->getBuffer();
}
//TODO: different compressors might be compatible, it might not be necessary to split them up by object
@ -52,17 +62,17 @@ final class StandardPacketBroadcaster implements PacketBroadcaster{
}
foreach($targetMap as $bufferId => $compressorMap){
$buffer = $buffers[$bufferId];
$batchBuffer = $batchBuffers[$bufferId];
foreach($compressorMap as $compressorId => $compressorTargets){
$compressor = $compressors[$compressorId];
if(!$compressor->willCompress($buffer)){
if(!$compressor->willCompress($batchBuffer)){
foreach($compressorTargets as $target){
foreach($packets as $pk){
$target->addToSendBuffer($pk);
foreach($packetBuffers[$bufferId] as $packetBuffer){
$target->addToSendBuffer($packetBuffer);
}
}
}else{
$promise = $this->server->prepareBatch(new PacketBatch($buffer), $compressor);
$promise = $this->server->prepareBatch(new PacketBatch($batchBuffer), $compressor);
foreach($compressorTargets as $target){
$target->queueCompressed($promise);
}