Smart2Raw
Início Aplicações Como funciona Desempenho Comece

Comercial

Premium Licenciamento Investimento

Mais

Escopo técnico Citar Sobre
Fale conosco

O seu dado não vira outra coisa.

Como funciona

O Smart2Raw não é um compressor. Um compressor transforma o seu dado em outra coisa e devolve quando você pede. O Smart2Raw faz o contrário: ele decide, uma vez, de quanto o dado realmente precisa — e depois deixa o dado em paz.

Três passos: medir a amplitude real, escolher a menor classe nativa que a contém, e operar direto nos bytes guardados. É o terceiro que separa a abordagem de tudo o que codifica — não há passo de decodificação para pagar.

3passos: medir a amplitude, guardar na classe nativa, operar sem materializar
3formas — pool plano, afim e em blocos — escolhidas por medição, não por palpite
3,8 MB4 milhões de elementos entre 0 e 200 que ocupavam 30,5 MB — sem dicionário e sem empacotar bits
0passos de decodificação: os bytes guardados são inteiros nativos

Passo 1 · Medir a amplitude real

Uma coluna int64 ocupa 8 bytes por elemento, guarde ela timestamps de nanossegundo ou a leitura de um sensor entre 0 e 200. A primeira coisa que a biblioteca faz é olhar:

int8_t cls = s2r_classify_array(valores, n);   /* uma passagem, sem alocar */

Para 0..200 a resposta é S2R_8. Para −500..500 é S2R_I16. A classe é a largura em bits, e o sinal dela é a presença de sinal — então abs(size) >> 3 é o número de bytes por elemento e size < 0 quer dizer com sinal. Esse único truque tira uma tabela inteira da biblioteca.

Passo 2 · Guardar nessa classe, nativamente

S2RPool p;
s2r_pool_init(&p, cls, n);
for (size_t i = 0; i < n; i++) s2r_push(&p, valores[i]);

Os elementos agora são uint8_t na memória. Não "códigos de 8 bits" — uint8_t de verdade. Uma coluna de 0..200 com 4 milhões de elementos sai de 30,5 MB para 3,8 MB, e os bytes em p.data são um vetor que qualquer compilador C já sabe ler.

Passo 3 · Operar sem materializar

É esta a parte que separa a abordagem da codificação por dicionário. Como os bytes guardados são inteiros nativos, um predicado roda em cima deles como estão:

size_t k = s2r_count_gt_fast(&p, 100);

Não há passo de decodificação, não há consulta a dicionário, não há buffer intermediário. Isso tem duas consequências, e as duas estão medidas.

A primeira é a largura. Um registrador de 512 bits carrega 64 valores u8 por instrução, contra 8 valores int64. A classe estreita não é um custo pago na leitura: é o que destrava a vetorização. Medido, count_gt com sinal em i8 vai de 1402 para 18833 Mval/s — 13,4× — e a soma em u8 com vpsadbw fica entre 2,8× e 10×, conforme o dado caiba ou não no cache.

A segunda é o que deixa de acontecer. Compare com um par que ocupa praticamente o mesmo espaço — 11,44 MB contra 11,45 MB — para que os bytes saiam da conta e sobre só processamento. O SUM sai em 0,44 ms contra 7,52 ms, 17×, porque um código de dicionário não é um operando que se some: é preciso histogramar os códigos e dobrar o dicionário em cima. E chegar a um kernel que não é SQL — um produto escalar quantizado, uma convolução, um matmul int8 — custa 7,9 ms de materialização do lado do par e zero do nosso, porque o pool já é o buffer contíguo na largura nativa que esse kernel exige.

Ler um quarto dos bytes significa um quarto do tráfego de memória, e tráfego de memória é o que uma varredura custa. Mas o ganho maior costuma ser o segundo: o buffer que nunca precisou existir.

Três formas, escolhidas por medição

formao que fazquando ganha
pool planotodo elemento na menor classe que cabe a coluna inteiracolunas uniformes; é a porta de entrada natural
afimv = base + passo·i — o passo comum é dividido, de forma exataintervalos de amostragem fixos, dinheiro em ponto fixo, passos de quantização
em blocoscada bloco é guardado relativo ao próprio mínimo, com metadados que respondem consultas sem tocar no payloaddado particionado no tempo, e tudo em que a amplitude local é bem menor que a global

O s2r_recommend() mede as três e diz qual delas a sua coluna quer — porque a porta de entrada óbvia costuma ser a pior. E o s2r_blocked_plan() precifica todos os tamanhos de bloco candidatos a partir de uma única passagem sobre o dado, de modo que o tamanho de bloco é classificado em vez de chutado.

Por que ele nunca pode aumentar o seu dado

Todo formato clássico tem um regime em que a saída fica maior que a entrada. Dicionário numa coluna de cardinalidade alta guarda um dicionário do tamanho do dado. RLE em dado não ordenado guarda uma corrida por valor. Bitmap só existe quando há dois valores distintos.

O Smart2Raw classifica por amplitude, e a classe mais larga que ele tem é o int64 de entrada. Então o pior caso dele é "a amplitude precisa de 64 bits", que é exatamente a linha de base. Isso não é sorte nem ajuste fino — é consequência estrutural do projeto, e o benchmarks/format_matrix.c verifica esse limite por asserção antes de imprimir cada linha.

O arquivo .s2r

Uma coluna serializada é um cabeçalho pequeno e fixo seguido do payload, em little-endian canônico para que os bytes sejam idênticos em qualquer máquina, com um CRC32 no fim.

fmto que é
1pool plano
2em blocos
3em blocos, com passo por bloco

O formato 3 só é emitido quando algum bloco de fato tem passo maior que 1 — então uma coluna sem passo comum é byte a byte o arquivo que a versão 3.4.0 escrevia, e uma build 3.4.0 abre esse arquivo. Quando há passo, o arquivo é fmt = 3 e as versões antigas o recusam corretamente, em vez de lerem errado.

Por onde seguir

Veja os números

A mesma coluna em sete formatos, e os tempos de consulta.

Desempenho →

Onde a troca está

A menor classe nativa tem 8 bits. Tudo o que isso compra, e o que custa.

Escopo técnico →

Escreva as três linhas

Um header, sem sistema de build, sem configuração.

Comece →