Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(performance): improve countParams #2378

Merged
merged 5 commits into from May 17, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 37 additions & 8 deletions tree.go
Expand Up @@ -5,10 +5,18 @@
package gin

import (
"bytes"
"net/url"
"reflect"
"strings"
"unicode"
"unicode/utf8"
"unsafe"
)

var (
strColon = []byte(":")
strStar = []byte("*")
)

// Param is a single URL parameter, consisting of a key and a value.
Expand Down Expand Up @@ -72,14 +80,35 @@ func longestCommonPrefix(a, b string) int {
return i
}

// bytesToStr converts byte slice to a string without memory allocation.
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func bytesToStr(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}

// strToBytes converts string to a byte slice without memory allocation.
//
// Note it may break if string and/or slice header will change
// in the future go versions.
func strToBytes(s string) (b []byte) {
/* #nosec G103 */
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
/* #nosec G103 */
sh := *(*reflect.StringHeader)(unsafe.Pointer(&s))
bh.Data = sh.Data
bh.Len = sh.Len
bh.Cap = sh.Len
return b
}

func countParams(path string) uint16 {
var n uint
for i := range []byte(path) {
switch path[i] {
case ':', '*':
n++
}
}
s := strToBytes(path)
n += uint(bytes.Count(s, strColon))
n += uint(bytes.Count(s, strStar))
return uint16(n)
}

Expand Down Expand Up @@ -163,7 +192,7 @@ walk:

n.children = []*node{&child}
// []byte for proper unicode char conversion, see #65
n.indices = string([]byte{n.path[i]})
n.indices = bytesToStr([]byte{n.path[i]})
n.path = path[:i]
n.handlers = nil
n.wildChild = false
Expand Down Expand Up @@ -223,7 +252,7 @@ walk:
// Otherwise insert it
if c != ':' && c != '*' {
// []byte for proper unicode char conversion, see #65
n.indices += string([]byte{c})
n.indices += bytesToStr([]byte{c})
child := &node{
fullPath: fullPath,
}
Expand Down