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
Referência
Contexto
Um consumidor downstream (
SbeB3UmdfConsumer) identificou hot path com alocações desnecessárias ao processar mensagens comdatagroups +varDatafields (ex:SecurityDefinition_12). Em bootstrap frio com 11k SecDefs:Detalhes do diagnóstico no consumidor: pedrosakuma/B3MarketDataPlatform#1.
Estado atual do gerador
Ponto positivo: o gerador já emite
ref structenumerators zero-alloc para todos os repeating groups (NoUnderlyingsEnumerator,NoLegsEnumerator, etc), comref readonly CurrenteMoveNext(). Padrão correto, totalmente devirtualizável.Pontos a melhorar:
1.
ReadGroups(callback, callback, callback, callback)continua sendo a única API que cobrevarDataPara messages que misturam repeating groups + varData (ex:
SecurityDefinition_12.SecurityDescription), o consumidor que quer processar tudo numa passada é forçado a usarReadGroups, que aceitadelegatepara CADA grupo + umTextEncoding.Callbackpro varData. Quando o consumidor captura locais, gera 4 closures por chamada.Proposta: emitir propriedade direta para
varDatafields, retornandoReadOnlySpan<byte>(ouTextEncodingstruct) calculada a partir do offset pós-grupos. Algo como:Isso permitiria refactor do consumidor para:
Eliminando os 4 callbacks. O cálculo do offset do
varDataé determinístico (skipar todos os grupos) — o gerador já faz isso internamente emReadGroups.2. (Opcional / discussão) Marcar
ReadGroups(...)como[Obsolete]ou indicar pelo XML doc que existe alternativa zero-allocHoje 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 umref structque implementaIGroupVisitor<T>e o gerador chama métodos sem indireção. Vale discutir se compensa o boilerplate adicional vs oforeachdireto que já funciona.Critérios de aceite
datafield varLength, o reader gerado expõe propriedade direta (ReadOnlySpan<byte>ou wrapper struct) que compute o offset corretamente após os repeating groupsReadGroupsquando não querem o callback modelReferência
SecurityDefinition_12.cslinhas 1025-1063 (ReadGroups) vs 1079/1145/1211 (enumerators struct)