Closed mogren closed 2 years ago
Thanks for your interest and please excuse the late reply; I am somewhat busy at moment.
Initially I wrote cboring as a CBOR library for dtn7-go, as the Bundle Protocol Version 7 makes some strange use of the CBOR specification. In BPv7, every structure is serialized as a CBOR array, where the array's length may vary. For an example, please check out the section about the Primary Block.
As it turned out, this was hard to realize with the go-code library, which tried to be smart and transform the data structure to the "obvious" CBOR object through the use of reflection with optional addition tags. It was also possible to write custom handlers for types, which I ended up doing, as the deserialization of arrays of custom length to structs was not really in the library's scope.
Back then I looked for other CBOR libraries, especially those who don't try to outsmart me and leave me the option to specify how data should be proceeded. Because I found no such library, I built cboring with the goals of being easy to use with custom serializations and being as small in code base as possible.
During the same time the fxamacker/cbor emerged, compare both initial commits: cboring, fxamacker/cbor. I only came across the other library some months later.
However, there seems to be a different scope of those two libraries. As go-codec, fxamacker/cbor library tries to work with assumptions. There is also an toarray
attribute, but I have not tested how it behaves with different length.
For dtn7-go, the cbor library does everything that is necessary, as it implements a good part of the CBOR standard and enables the flexibility to mostly work with different CBOR arrays. For a more general use case, the fxamacker/cbor library or go-codec should be the better choice.
In a small, truly non-significant test, I briefly compared the two libraries in terms of encoding UInts.
$ go mod graph
github.com/oxzi/cbor-bench github.com/dtn7/cboring@v0.1.5
github.com/oxzi/cbor-bench github.com/fxamacker/cbor/v2@v2.4.0
github.com/fxamacker/cbor/v2@v2.4.0 github.com/x448/float16@v0.8.4
$ cat bench_uint_test.go
package main
import (
"bytes"
"testing"
"github.com/dtn7/cboring"
"github.com/fxamacker/cbor/v2"
)
func BenchmarkUintWriteCboring(b *testing.B) {
var buff bytes.Buffer
for i := 0; i < b.N; i++ {
if err := cboring.WriteUInt(uint64(i), &buff); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkUintWriteCbor(b *testing.B) {
var buff bytes.Buffer
var enc = cbor.NewEncoder(&buff)
for i := 0; i < b.N; i++ {
if err := enc.Encode(uint64(i)); err != nil {
b.Fatal(err)
}
}
}
$ go test -bench=.
goos: linux
goarch: amd64
pkg: github.com/oxzi/cbor-bench
cpu: Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz
BenchmarkUintWriteCboring-4 13475816 82.24 ns/op
BenchmarkUintWriteCbor-4 9289813 122.3 ns/op
PASS
ok github.com/oxzi/cbor-bench 2.480s
Thanks a lot for this long and detailed answer! Especially the link to the Primary Bundle Block and the code sample. I think you are totally right that cboring is the right implementation for dtn7-go. 😄
Hi! :wave: I'm sharing this to provide a more complete picture about encoding speed of uint64 with fxamacker/cbor.
StreamEncoder.EncodeUint64()
is available the feature/stream-mode
branch. It's fast and doesn't allocate memory:
name time/op alloc/op allocs/op
UintWriteCboring-8 33.7ns ± 3% 27.0B ± 0% 1
UintWriteCbor-8 69.1ns ± 0% 19.0B ± 0% 0
UintStreamWriteCbor-8 10.1ns ± 1% 16.0B ± 0% 0
* Go 1.17 linux_amd64 (Haswell Xeon)
The feature/stream-mode
branch (superset of main) was created in April 2021. It's regularly fuzz tested and is used in production for nearly a year. However, stream-mode is meant for advanced users comfortable with CBOR.
It will be merged into main branch after adding any missing features and documenting new features.
@oxzi @mogren It's great to find kindred spirits who worked on CBOR around the same time. BTW, I created stream-mode while displaced (Winter Storm Uri made my place uninhabitable for 6 months).
$ go test -bench=. -benchmem -benchtime=100000000x -count=10 | tee bench_cbor_encode_uint64_c10.log
``` goos: linux goarch: amd64 pkg: compare_cboring cpu: Intel(R) Xeon(R) CPU E3-1246 v3 @ 3.50GHz BenchmarkUintWriteCboring-8 100000000 34.64 ns/op 27 B/op 1 allocs/op BenchmarkUintWriteCboring-8 100000000 33.51 ns/op 27 B/op 1 allocs/op BenchmarkUintWriteCboring-8 100000000 33.65 ns/op 27 B/op 1 allocs/op BenchmarkUintWriteCboring-8 100000000 33.57 ns/op 27 B/op 1 allocs/op BenchmarkUintWriteCboring-8 100000000 33.57 ns/op 27 B/op 1 allocs/op BenchmarkUintWriteCboring-8 100000000 33.60 ns/op 27 B/op 1 allocs/op BenchmarkUintWriteCboring-8 100000000 33.94 ns/op 27 B/op 1 allocs/op BenchmarkUintWriteCboring-8 100000000 36.43 ns/op 27 B/op 1 allocs/op BenchmarkUintWriteCboring-8 100000000 33.67 ns/op 27 B/op 1 allocs/op BenchmarkUintWriteCboring-8 100000000 33.56 ns/op 27 B/op 1 allocs/op BenchmarkUintWriteCbor-8 100000000 69.15 ns/op 19 B/op 0 allocs/op BenchmarkUintWriteCbor-8 100000000 69.15 ns/op 19 B/op 0 allocs/op BenchmarkUintWriteCbor-8 100000000 69.12 ns/op 19 B/op 0 allocs/op BenchmarkUintWriteCbor-8 100000000 70.85 ns/op 19 B/op 0 allocs/op BenchmarkUintWriteCbor-8 100000000 69.14 ns/op 19 B/op 0 allocs/op BenchmarkUintWriteCbor-8 100000000 69.09 ns/op 19 B/op 0 allocs/op BenchmarkUintWriteCbor-8 100000000 69.35 ns/op 19 B/op 0 allocs/op BenchmarkUintWriteCbor-8 100000000 70.40 ns/op 19 B/op 0 allocs/op BenchmarkUintWriteCbor-8 100000000 69.17 ns/op 19 B/op 0 allocs/op BenchmarkUintWriteCbor-8 100000000 68.96 ns/op 19 B/op 0 allocs/op BenchmarkUintStreamWriteCbor-8 100000000 10.24 ns/op 16 B/op 0 allocs/op BenchmarkUintStreamWriteCbor-8 100000000 10.04 ns/op 16 B/op 0 allocs/op BenchmarkUintStreamWriteCbor-8 100000000 10.09 ns/op 16 B/op 0 allocs/op BenchmarkUintStreamWriteCbor-8 100000000 10.12 ns/op 16 B/op 0 allocs/op BenchmarkUintStreamWriteCbor-8 100000000 10.06 ns/op 16 B/op 0 allocs/op BenchmarkUintStreamWriteCbor-8 100000000 10.05 ns/op 16 B/op 0 allocs/op BenchmarkUintStreamWriteCbor-8 100000000 10.36 ns/op 16 B/op 0 allocs/op BenchmarkUintStreamWriteCbor-8 100000000 10.12 ns/op 16 B/op 0 allocs/op BenchmarkUintStreamWriteCbor-8 100000000 10.03 ns/op 16 B/op 0 allocs/op BenchmarkUintStreamWriteCbor-8 100000000 10.11 ns/op 16 B/op 0 allocs/op PASS ok compare_cboring 114.223s ```
$ benchstat bench_cbor_encode_uint64_c10.log
``` benchstat bench_cbor_encode_uint64_c10.log name time/op UintWriteCboring-8 33.7ns ± 3% UintWriteCbor-8 69.1ns ± 0% UintStreamWriteCbor-8 10.1ns ± 1% name alloc/op UintWriteCboring-8 27.0B ± 0% UintWriteCbor-8 19.0B ± 0% UintStreamWriteCbor-8 16.0B ± 0% name allocs/op UintWriteCboring-8 1.00 ± 0% UintWriteCbor-8 0.00 UintStreamWriteCbor-8 0.00 ```
$ cat go.mod
``` module compare_cboring go 1.17 require ( [github.com/dtn7/cboring](http://github.com/dtn7/cboring) v0.1.5 // indirect [github.com/fxamacker/cbor/v2](http://github.com/fxamacker/cbor/v2) v2.3.1-0.20211029162100-5d5d7c3edd41 // indirect [github.com/x448/float16](http://github.com/x448/float16) v0.8.4 // indirect ) ```
$ cat bench_uint64_test.go
```Go package main import ( "bytes" "testing" "github.com/dtn7/cboring" "github.com/fxamacker/cbor/v2" ) func BenchmarkUintWriteCboring(b *testing.B) { var buff bytes.Buffer for i := 0; i < b.N; i++ { if err := cboring.WriteUInt(uint64(i), &buff); err != nil { b.Fatal(err) } } } func BenchmarkUintWriteCbor(b *testing.B) { var buff bytes.Buffer var enc = cbor.NewEncoder(&buff) for i := 0; i < b.N; i++ { if err := enc.Encode(uint64(i)); err != nil { b.Fatal(err) } } } // BenchmarkUintStreamWriteCbor requires feature/stream-mode branch // of github.com/fxamacker/cbor. func BenchmarkUintStreamWriteCbor(b *testing.B) { var buff bytes.Buffer var enc = cbor.NewStreamEncoder(&buff) for i := 0; i < b.N; i++ { if err := enc.EncodeUint64(uint64(i)); err != nil { b.Fatal(err) } } // Flush to make sure all previously encoded data is copied to buff. enc.Flush() } ```
Just curious if there are any comparisons with other go CBOR implementations?
https://github.com/fxamacker/cbor/ for example.
Does the current implementation do everything needed?