Comparação de Desempenho entre Protocol Buffers e FlatBuffers

Protocol Buffers é uma biblioteca de serialização multiplataforma e multilinguagem desenvolvida pelo Google. FlatBuffers, também criado pelo Google, compartilha essas características, mas prioriza eficiência de tempo de execução — sendo especialmente projetado para cenários com requisitos rigorosos de desempenho, como jogos.

Protocol Buffers repreesnta mensagens como campos numerados compactados em um buffer linear. Por exemplo:

message PingRequest {
  int32 x = 1;
  int32 y = 2;
  int32 z = 3;
  int32 direction = 4;
}

Cada campo é identificado por um número de tag (1 a 4), e os dados são codificados com técnicas como varint, resultando em um layout sequencial na memória após serialização.

FlatBuffers, por outro lado, adota um modelo baseado em mapeamento direto de memória, inspirado em estruturas C. Considere:

table PingRequest {
  x: int;
  message: string;
}

Tipos primitivos fixos (como int) são armazenados diretamente no buffer, enquanto tipos dinâmicos (como strings) são acessados via offsets. Um cabeçalho no início do buffer contém os offsets dos campos não-POD, permitindo acesso direto sem necessidade de desserialização completa. Alinhamento de memória rigoroso garante compatibilidade entre linguagens e plataformas.

Em um framework de servidor implementado em C++ com lógica em Lua, mensagens recebidas são automaticamente convertidas em tabelas Lua para facilitar o desenvolvimento. Com o esquema acima, uma mensagem recebida torna-se:

{
  x = 999,
  y = 123,
  z = 777,
  direction = 0
}

As bibliotecas utilizadas foram:

Para avaliar desempenho, foi montado um cenário com quatro processos, cada um simulando 2500 jogadores, enviando 8 pacotes por segundo durante 125 segundos (totalizando 1000 pacotes por jogador). O ambiente foi Ubuntu 14.04 rodando em Docker (17.03.1-ce) em uma máquina com CPU AMD A8-4500M (quad-core, 1.4 GHz).

O esquema inicial incluía apenas campos inteiros:

// Requisição do cliente
message CPing {
  int32 x = 1;
  int32 y = 2;
  int32 z = 3;
  int32 way = 4;
}

// Resposta do servidor
message SPing {
  int32 timestamp = 1;
}

Neste cenário simples, ambos os formatos apresentaram tempos de execução semelhantes. No entanto, o processo de mundo (world) usando Protocol Buffers consumiu mais CPU durante a serialização, enquanto o gateway teve menor uso de CPU — indicando que o Protocol Buffers gera payloads menores (menos I/O), mas exige mais processamento para codificação/decodificação. FlatBuffers mostrou comportamento inverso: maior volume de dados transmitidos, mas menor carga de CPU na desserialização.

Em um segundo teste, foram adicionados campos complexos:

message CPing {
  int32 x = 1;
  int32 y = 2;
  int32 z = 3;
  int32 way = 4;
  repeated int32 targets = 5;
  string comment = 6;
}

O array targets continha sempre [1,2,3,4,5,6,7,8,9], e comment era fixo como uma string repetitiva longa. O número total de jogadores foi reduzido para 5000 (dois processos com 2500 cada).

Neste cenário, FlatBuffers demonstrou consumo significativamente menor de CPU em comparação ao Protocol Buffers, especialmente durante a desserialização de dados aninhados. A diferença de throughput não foi clara devido à natureza do benchmark — limitado por gargalos externos como coleta de lixo do Lua e latência de rede simulada.

Vale ressaltar que o objetivo do teste não era medir o desempenho bruto das bibliotecas isoladamente, mas avaliar seu impacto integrado em um pipeline real com camadas de abstração (C++ ↔ Lua). Fatores como GC do Lua provavelmente dominaram o perfil de desempenho geral.

Conclui-se que FlatBuffers oferece vantagem em cenários sensíveis à latência e uso de CPU, enquanto Protocol Buffers se destaca em eficiência de largura de banda e maturidade ecológica. A escolha depende dos requisitos específicos do projeto.

Tags: protocol-buffers flatbuffers Lua cpp Serialization

Publicado em 8-31 21:44