mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-09 23:00:58 +00:00
lib/protocol: Optimize luhn and chunk functions
These functions were very naive and slow. We haven't done much about them because they pretty much don't matter at all for Syncthing performance. They are however called very often in the discovery server and these optimizations have a huge effect on the CPU load on the public discovery servers. The code isn't exactly obvious, but we have good test coverage on all these functions. benchmark old ns/op new ns/op delta BenchmarkLuhnify-8 12458 1045 -91.61% BenchmarkUnluhnify-8 12598 1074 -91.47% BenchmarkChunkify-8 10792 104 -99.04% benchmark old allocs new allocs delta BenchmarkLuhnify-8 18 1 -94.44% BenchmarkUnluhnify-8 18 1 -94.44% BenchmarkChunkify-8 44 2 -95.45% benchmark old bytes new bytes delta BenchmarkLuhnify-8 1278 64 -94.99% BenchmarkUnluhnify-8 1278 64 -94.99% BenchmarkChunkify-8 42552 128 -99.70% GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4346
This commit is contained in:
parent
9e6a1fdcd4
commit
9682bbfbda
@ -8,7 +8,6 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/calmh/luhn"
|
||||
@ -155,16 +154,17 @@ func luhnify(s string) (string, error) {
|
||||
panic("unsupported string length")
|
||||
}
|
||||
|
||||
res := make([]string, 0, 4)
|
||||
res := make([]byte, 4*(13+1))
|
||||
for i := 0; i < 4; i++ {
|
||||
p := s[i*13 : (i+1)*13]
|
||||
copy(res[i*(13+1):], p)
|
||||
l, err := luhn.Base32.Generate(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
res = append(res, fmt.Sprintf("%s%c", p, l))
|
||||
res[(i+1)*(13)+i] = byte(l)
|
||||
}
|
||||
return res[0] + res[1] + res[2] + res[3], nil
|
||||
return string(res), nil
|
||||
}
|
||||
|
||||
func unluhnify(s string) (string, error) {
|
||||
@ -172,25 +172,31 @@ func unluhnify(s string) (string, error) {
|
||||
return "", fmt.Errorf("%q: unsupported string length %d", s, len(s))
|
||||
}
|
||||
|
||||
res := make([]string, 0, 4)
|
||||
res := make([]byte, 52)
|
||||
for i := 0; i < 4; i++ {
|
||||
p := s[i*14 : (i+1)*14-1]
|
||||
p := s[i*(13+1) : (i+1)*(13+1)-1]
|
||||
copy(res[i*13:], p)
|
||||
l, err := luhn.Base32.Generate(p)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if g := fmt.Sprintf("%s%c", p, l); g != s[i*14:(i+1)*14] {
|
||||
if s[(i+1)*14-1] != byte(l) {
|
||||
return "", fmt.Errorf("%q: check digit incorrect", s)
|
||||
}
|
||||
res = append(res, p)
|
||||
}
|
||||
return res[0] + res[1] + res[2] + res[3], nil
|
||||
return string(res), nil
|
||||
}
|
||||
|
||||
func chunkify(s string) string {
|
||||
s = regexp.MustCompile("(.{7})").ReplaceAllString(s, "$1-")
|
||||
s = strings.Trim(s, "-")
|
||||
return s
|
||||
chunks := len(s) / 7
|
||||
res := make([]byte, chunks*(7+1)-1)
|
||||
for i := 0; i < chunks; i++ {
|
||||
if i > 0 {
|
||||
res[i*(7+1)-1] = '-'
|
||||
}
|
||||
copy(res[i*(7+1):], s[i*7:(i+1)*7])
|
||||
}
|
||||
return string(res)
|
||||
}
|
||||
|
||||
func unchunkify(s string) string {
|
||||
|
@ -150,3 +150,41 @@ func TestNewDeviceIDMarshalling(t *testing.T) {
|
||||
t.Error("Mismatch in old -> new direction")
|
||||
}
|
||||
}
|
||||
|
||||
var resStr string
|
||||
|
||||
func BenchmarkLuhnify(b *testing.B) {
|
||||
str := "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB"
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
resStr, err = luhnify(str)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnluhnify(b *testing.B) {
|
||||
str, _ := luhnify("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB")
|
||||
var err error
|
||||
for i := 0; i < b.N; i++ {
|
||||
resStr, err = unluhnify(str)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChunkify(b *testing.B) {
|
||||
str := "ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB"
|
||||
for i := 0; i < b.N; i++ {
|
||||
resStr = chunkify(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnchunkify(b *testing.B) {
|
||||
str := chunkify("ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJAB")
|
||||
for i := 0; i < b.N; i++ {
|
||||
resStr = unchunkify(str)
|
||||
}
|
||||
}
|
||||
|
21
vendor/github.com/calmh/luhn/luhn.go
generated
vendored
21
vendor/github.com/calmh/luhn/luhn.go
generated
vendored
@ -19,10 +19,6 @@ var (
|
||||
// Generate returns a check digit for the string s, which should be composed
|
||||
// of characters from the Alphabet a.
|
||||
func (a Alphabet) Generate(s string) (rune, error) {
|
||||
if err := a.check(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
factor := 1
|
||||
sum := 0
|
||||
n := len(a)
|
||||
@ -57,14 +53,15 @@ func (a Alphabet) Validate(s string) bool {
|
||||
return rune(s[len(s)-1]) == c
|
||||
}
|
||||
|
||||
// check returns an error if the given alphabet does not consist of unique characters
|
||||
func (a Alphabet) check() error {
|
||||
cm := make(map[byte]bool, len(a))
|
||||
for i := range a {
|
||||
if cm[a[i]] {
|
||||
return fmt.Errorf("Digit %q non-unique in alphabet %q", a[i], a)
|
||||
// NewAlphabet converts the given string an an Alphabet, verifying that it
|
||||
// is correct.
|
||||
func NewAlphabet(s string) (Alphabet, error) {
|
||||
cm := make(map[byte]bool, len(s))
|
||||
for i := range s {
|
||||
if cm[s[i]] {
|
||||
return "", fmt.Errorf("Digit %q non-unique in alphabet %q", s[i], s)
|
||||
}
|
||||
cm[a[i]] = true
|
||||
cm[s[i]] = true
|
||||
}
|
||||
return nil
|
||||
return Alphabet(s), nil
|
||||
}
|
||||
|
2
vendor/manifest
vendored
2
vendor/manifest
vendored
@ -53,7 +53,7 @@
|
||||
"importpath": "github.com/calmh/luhn",
|
||||
"repository": "https://github.com/calmh/luhn",
|
||||
"vcs": "git",
|
||||
"revision": "0c8388ff95fa92d4094011e5a04fc99dea3d1632",
|
||||
"revision": "c0f1d77264fb3d1bfc65b70eea6ee264058c57c0",
|
||||
"branch": "master",
|
||||
"notests": true
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user