Skip to content

perf: expor varData fields como propriedade direta para eliminar callbacks em ReadGroups #162

Description

@pedrosakuma

Contexto

Um consumidor downstream (SbeB3UmdfConsumer) identificou hot path com alocações desnecessárias ao processar mensagens com data groups + varData fields (ex: SecurityDefinition_12). Em bootstrap frio com 11k SecDefs:

  • ~44k allocs de delegate + DisplayClass (closures capturando variáveis locais)
  • ~11k indirect calls não-devirtualizáveis
  • ~200 ms de stall do dispatcher do canal de InstrDef

Detalhes do diagnóstico no consumidor: pedrosakuma/B3MarketDataPlatform#1.

Estado atual do gerador

Ponto positivo: o gerador já emite ref struct enumerators zero-alloc para todos os repeating groups (NoUnderlyingsEnumerator, NoLegsEnumerator, etc), com ref readonly Current e MoveNext(). Padrão correto, totalmente devirtualizável.

Pontos a melhorar:

1. ReadGroups(callback, callback, callback, callback) continua sendo a única API que cobre varData

Para messages que misturam repeating groups + varData (ex: SecurityDefinition_12.SecurityDescription), o consumidor que quer processar tudo numa passada é forçado a usar ReadGroups, que aceita delegate para CADA grupo + um TextEncoding.Callback pro varData. Quando o consumidor captura locais, gera 4 closures por chamada.

Proposta: emitir propriedade direta para varData fields, retornando ReadOnlySpan<byte> (ou TextEncoding struct) calculada a partir do offset pós-grupos. Algo como:

// Já existe (groups):
public NoUnderlyingsEnumerator NoUnderlyings    => ...;
public NoLegsEnumerator        NoLegs           => ...;
public NoInstrAttribsEnumerator NoInstrAttribs  => ...;

// Proposto (varData):
public ReadOnlySpan<byte>      SecurityDesc     => ...;
// ou:
public TextEncoding            SecurityDesc     => ...;

Isso permitiria refactor do consumidor para:

foreach (ref readonly var u   in reader.NoUnderlyings)   { ... }
foreach (ref readonly var leg in reader.NoLegs)          { ... }
foreach (ref readonly var a   in reader.NoInstrAttribs)  { ... }
ProcessDescription(reader.SecurityDesc); // zero closure

Eliminando os 4 callbacks. O cálculo do offset do varData é determinístico (skipar todos os grupos) — o gerador já faz isso internamente em ReadGroups.

2. (Opcional / discussão) Marcar ReadGroups(...) como [Obsolete] ou indicar pelo XML doc que existe alternativa zero-alloc

Hoje o consumidor descobre as APIs alternativas só explorando o código gerado. Um <remarks> indicando "use as propriedades enumerator + propriedade varData para zero-alloc" ajudaria a discoverability.

3. (Stretch) Padrão "struct visitor" para messages complexas

Mesma filosofia do ISbeMessageHandler (dispatcher de templates), mas no nível dos grupos: o consumidor passa um ref struct que implementa IGroupVisitor<T> e o gerador chama métodos sem indireção. Vale discutir se compensa o boilerplate adicional vs o foreach direto que já funciona.

Critérios de aceite

  • Para qualquer message com data field varLength, o reader gerado expõe propriedade direta (ReadOnlySpan<byte> ou wrapper struct) que compute o offset corretamente após os repeating groups
  • Testes do gerador cobrem mensagem com mix de groups + varData
  • Consumidores podem migrar para 100% foreach + property access, sem chamar ReadGroups quando não querem o callback model
  • (Opcional) Doc/atributo direcionando para a API zero-alloc

Referência

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions