Skip to content

Commit

Permalink
perf!: reduce crc16 pointer dereference (#184)
Browse files Browse the repository at this point in the history
* perf: reduce crc16 pointer dereference

* tests: update crc16 unit test
  • Loading branch information
muktihari authored Apr 15, 2024
1 parent 4066db4 commit ca78c95
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 35 deletions.
2 changes: 1 addition & 1 deletion decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func New(r io.Reader, opts ...Option) *Decoder {
options: options,
factory: options.factory,
accumulator: NewAccumulator(),
crc16: crc16.New(crc16.MakeFitTable()),
crc16: crc16.New(nil),
localMessageDefinitions: [proto.LocalMesgNumMask + 1]*proto.MessageDefinition{},
mesgListeners: options.mesgListeners,
mesgDefListeners: options.mesgDefListeners,
Expand Down
4 changes: 2 additions & 2 deletions decoder/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ func TestCheckIntegrity(t *testing.T) {
DataType: proto.DataTypeFIT,
}
b, _ := h.MarshalBinary()
crc := crc16.New(crc16.MakeFitTable())
crc := crc16.New(nil)
crc.Write(b[:12])
binary.LittleEndian.PutUint16(b[12:14], crc.Sum16())
return bytes.NewReader(b)
Expand Down Expand Up @@ -539,7 +539,7 @@ func createFitForTest() (proto.FIT, []byte) {
bytesbuffer.Write(b)

// Marshal and calculate data size and crc checksum
crc16checker := crc16.New(crc16.MakeFitTable())
crc16checker := crc16.New(nil)
for i := range fit.Messages {
mesg := fit.Messages[i]
mesgDef := proto.CreateMessageDefinition(&mesg)
Expand Down
2 changes: 1 addition & 1 deletion decoder/raw_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ var fnDecodeRawErr = func(flag RawFlag, b []byte) error {

func TestRawDecoderDecode(t *testing.T) {
_, buf := createFitForTest()
hash16 := crc16.New(crc16.MakeFitTable())
hash16 := crc16.New(nil)

tt := []struct {
name string
Expand Down
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ func main() {
defer f.Close()
dec := decoder.NewRaw()
hash16 := crc16.New(crc16.MakeFitTable())
hash16 := crc16.New(nil)
_, err = dec.Decode(bufio.NewReader(f), func(flag decoder.RawFlag, b []byte) error {
switch flag {
Expand Down
2 changes: 1 addition & 1 deletion encoder/encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func New(w io.Writer, opts ...Option) *Encoder {
lruCapacity = options.multipleLocalMessageType + 1
}

crc16 := crc16.New(crc16.MakeFitTable())
crc16 := crc16.New(nil)
e := &Encoder{
w: w,
options: options,
Expand Down
2 changes: 1 addition & 1 deletion encoder/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ func TestEncodeHeader(t *testing.T) {

binary.LittleEndian.PutUint16(b[2:4], profile.Version)

crc := crc16.New(crc16.MakeFitTable())
crc := crc16.New(nil)
crc.Write(b[:12])
binary.LittleEndian.PutUint16(b[12:14], crc.Sum16())

Expand Down
20 changes: 14 additions & 6 deletions kit/hash/crc16/crc16.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@ const (
Size = 2 // An uint16 requires 2 bytes to be represented in its binary form.
)

var fitTable = &Table{
var fitTable = Table{
0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400,
}

// MakeFitTable is the table defined in [https://developer.garmin.com/fit/protocol]
func MakeFitTable() *Table { return fitTable }
// MakeFITTable makes new table as defined in [https://developer.garmin.com/fit/protocol]
func MakeFITTable() *Table {
t := fitTable
return &t
}

// New creates a new hash.Hash16 computing the CRC-16 checksum using the polynomial represented by the Table.
// The computing algorithm is using FIT algorithm defined in [https://developer.garmin.com/fit/protocol].
// Its Sum method will lay the value out in big-endian byte order.
// If table is nil, default FIT table will be used. The computing algorithm is using FIT algorithm defined in
// [https://developer.garmin.com/fit/protocol]. Its Sum method will lay the value out in big-endian byte order.
func New(table *Table) hash.Hash16 {
if table == nil {
table = &fitTable
}
return &crc16{table: table}
}

Expand All @@ -36,9 +42,11 @@ type crc16 struct {
}

func (c *crc16) Write(p []byte) (n int, err error) {
crc := c.crc // PERF: Reduce pointer dereference every time 'c.crc' is computed.
for _, b := range p {
c.crc = c.compute(c.crc, b)
crc = c.compute(crc, b)
}
c.crc = crc
return len(p), nil
}

Expand Down
68 changes: 46 additions & 22 deletions kit/hash/crc16/crc16_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,65 @@
package crc16_test

import (
"fmt"
"testing"

"github.com/muktihari/fit/kit/hash/crc16"
)

func TestCRC16(t *testing.T) {
b := []byte{14, 32, 84, 8, 214, 204, 9, 0, 46, 70, 73, 84} // example from some fit header.
crc := uint16(12856) // should match this checksum.
tt := []struct {
name string
table *crc16.Table
}{
{name: "nil table", table: nil},
{name: "crc16.MakeFITTable()", table: crc16.MakeFITTable()},
}

c16 := crc16.New(crc16.MakeFitTable())
for i, tc := range tt {
t.Run(fmt.Sprintf("[%d] %s", i, tc.name), func(t *testing.T) {
b := []byte{14, 32, 84, 8, 214, 204, 9, 0, 46, 70, 73, 84} // example from some fit header.
crc := uint16(12856) // should match this checksum.

if c16.BlockSize() != 1 {
t.Fatalf("blocksize mismatch")
}
c16 := crc16.New(tc.table)

if c16.Size() != 2 {
t.Fatalf("size mismatch")
}
if c16.BlockSize() != 1 {
t.Fatalf("blocksize mismatch")
}

_, _ = c16.Write(b)
if c16.Sum16() != crc {
t.Fatalf("expected: %v, got: %v", crc, c16.Sum16())
}
if c16.Size() != 2 {
t.Fatalf("size mismatch")
}

sum := c16.Sum([]byte{10})
expect := []byte{10, 50, 56}
for i := range sum {
if sum[i] != expect[i] {
t.Fatalf("expected: %v, got: %v", expect[i], sum[i])
}
_, _ = c16.Write(b)
if c16.Sum16() != crc {
t.Fatalf("expected: %v, got: %v", crc, c16.Sum16())
}

sum := c16.Sum([]byte{10})
expect := []byte{10, 50, 56}
for i := range sum {
if sum[i] != expect[i] {
t.Fatalf("expected: %v, got: %v", expect[i], sum[i])
}
}

c16.Reset()

if c16.Sum16() != 0 {
t.Fatalf("expected 0 after reset, got: %v", c16.Sum16())
}
})
}
}

c16.Reset()
func BenchmarkWrite(b *testing.B) {
b.StopTimer()
buf := make([]byte, 4096)
h := crc16.New(nil)
b.StartTimer()

if c16.Sum16() != 0 {
t.Fatalf("expected 0 after reset, got: %v", c16.Sum16())
for i := 0; i < b.N; i++ {
h.Write(buf)
}
}

0 comments on commit ca78c95

Please sign in to comment.