Se si zip un file chiamato a.txt
contenente il testo "hello"
(che è 5 caratteri), il risultato zip saranno circa 115 byte. Questo significa che il formato zip non è efficace per comprimere i file di testo? Certamente no. C'è un overhead . Se il file contiene "hello"
un centinaio di volte (500 byte), lo zippamento comporterà il file 120 byte! 1x"hello"
=> 115 byte, 100x"hello"
=> 120 byte! Abbiamo aggiunto 495 byte, eppure le dimensioni compresse sono aumentate solo di 5 byte.
Qualcosa di simile sta accadendo con il pacchetto encoding/gob
:
L'implementazione compila un codec personalizzato per ogni tipo di dati nel flusso ed è più efficace quando un singolo encoder viene utilizzato per trasmettere un flusso di valori, ammortizzando il costo della compilazione.
Quando si "prima" serializzare un valore di un tipo, la definizione del tipo ha anche essere inclusi/trasmissione, in modo che il decoder può correttamente interpretare e decodificare il flusso:
Un flusso di gocce è auto-descrivente.Ogni elemento di dati nel flusso è preceduto da una specifica del suo tipo, espressa in termini di un piccolo insieme di tipi predefiniti. ritorno
Let al tuo esempio:
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
e := Entry{"k1", "v1"}
enc.Encode(e)
fmt.Println(buf.Len())
Esso stampa:
48
Ora diamo codificare un po 'di più del tipo stesso:
enc.Encode(e)
fmt.Println(buf.Len())
enc.Encode(e)
fmt.Println(buf.Len())
Ora l'uscita è:
60
72
Provalo su Go Playground.
Analisi dei risultati:
ulteriori valori dello stesso Entry
tipo unico costo 12 byte, mentre la prima è 48
byte perché è anche incluso la definizione di tipo (che è ~ 26 byte), ma questo è un overhead una tantum .
Quindi sostanzialmente si trasmettere 2 string
s: "k1"
e "v1"
che sono 4 byte, e la lunghezza del string
s deve anche essere inclusi, utilizzando 4
byte (dimensione del int
su architetture a 32-bit) vi dà i 12 byte , che è il "minimo". (Sì, potresti usare un tipo più piccolo per la lunghezza, ma questo avrebbe i suoi limiti. Una codifica a lunghezza variabile sarebbe una scelta migliore per i numeri piccoli, vedi pacchetto encoding/binary
.)
Tutto sommato, encoding/gob
fa un bel buon lavoro per le tue esigenze. Non farti ingannare dalle impressioni iniziali.
Se questo 12 byte per una Entry
è troppo "molto" per te, si può sempre avvolgere il flusso in un compress/flate
o compress/gzip
scrittore di ridurre ulteriormente le dimensioni (in cambio di lenti codifica/decodifica e requisiti di memoria leggermente superiori per il processo).
Dimostrazione:
Testiamo i 3 soluzioni:
- Utilizzando un'uscita "nudo" (senza compressione)
- Utilizzo
compress/flate
per comprimere l'uscita di encoding/gob
- Uso
compress/gzip
comprimere l'output di encoding/gob
Scriveremo mille voci, cambiando chiavi e valori di ciascuno, essendo "k000"
, "v000"
, "k001"
, "v001"
ecc Questo significa che la dimensione non compressa di un Entry
è 4 byte + 4 byte + 4 byte + 4 byte = 16 byte (2x 4 byte di testo, 2x4 byte di lunghezza).
Il codice simile a questo:
names := []string{"Naked", "flate", "gzip"}
for _, name := range names {
buf := &bytes.Buffer{}
var out io.Writer
switch name {
case "Naked":
out = buf
case "flate":
out, _ = flate.NewWriter(buf, flate.DefaultCompression)
case "gzip":
out = gzip.NewWriter(buf)
}
enc := gob.NewEncoder(out)
e := Entry{}
for i := 0; i < 1000; i++ {
e.Key = fmt.Sprintf("k%3d", i)
e.Val = fmt.Sprintf("v%3d", i)
enc.Encode(e)
}
if c, ok := out.(io.Closer); ok {
c.Close()
}
fmt.Printf("[%5s] Length: %5d, average: %5.2f/Entry\n",
name, buf.Len(), float64(buf.Len())/1000)
}
uscita:
[Naked] Length: 16036, average: 16.04/Entry
[flate] Length: 4123, average: 4.12/Entry
[ gzip] Length: 4141, average: 4.14/Entry
Prova sul Go Playground.
Come si può vedere: l'uscita "nuda" è 16.04 bytes/Entry
, appena poco sopra la dimensione calcolata (a causa dell'overhead minuscolo di una volta discusso sopra).
Quando si utilizza flate o gzip per comprimere l'output, è possibile ridurre le dimensioni dell'output a circa 4.13 bytes/Entry
, che corrisponde a circa il 26% della dimensione teorica, sono sicuro che soddisfi voi. (Si noti che con i dati di "vita reale" il rapporto di compressione sarebbe probabilmente molto più alto in quanto le chiavi e i valori che ho usato nel test sono molto simili e quindi molto bene comprimibili, comunque il rapporto dovrebbe essere intorno al 50% con i dati reali).
Impressionante analisi (ammiro sempre le tue risposte) ma in questo caso particolare mi sembra di spiegare la scienza missilistica a un bambino che ha chiesto perché la sua bicicletta a tre ruote è un po 'lenta. ;-) Anche se penso che 'gob' abbia decisamente dei suoi usi, per un compito così semplice che l'OP sembra avere, sono sicuro che una semplice reimplementazione di ciò che è già stato fatto in C++ è giustificata. Un altro aspetto positivo di questo approccio è che il nuovo codice sarà comparabile con i dati legacy che hanno. – kostix
@kostix Questo è stato il mio primo pensiero e impressione anche sulla domanda, ma poi ho visto la sua ultima riga: _ "senza codifica manuale" _... Ecco perché ho deciso di rimanere con 'encoding/gob'. – icza