diff --git a/args.go b/args.go index b7ac464..514d51d 100644 --- a/args.go +++ b/args.go @@ -428,15 +428,15 @@ func (s *argsScanner) next(kv *argsKV) bool { case '=': if isKey { isKey = false - kv.key = decodeArgAppend(kv.key[:0], s.b[:i], true) + kv.key = decodeArgAppend(kv.key[:0], s.b[:i]) k = i + 1 } case '&': if isKey { - kv.key = decodeArgAppend(kv.key[:0], s.b[:i], true) + kv.key = decodeArgAppend(kv.key[:0], s.b[:i]) kv.value = kv.value[:0] } else { - kv.value = decodeArgAppend(kv.value[:0], s.b[k:i], true) + kv.value = decodeArgAppend(kv.value[:0], s.b[k:i]) } s.b = s.b[i+1:] return true @@ -444,17 +444,17 @@ func (s *argsScanner) next(kv *argsKV) bool { } if isKey { - kv.key = decodeArgAppend(kv.key[:0], s.b, true) + kv.key = decodeArgAppend(kv.key[:0], s.b) kv.value = kv.value[:0] } else { - kv.value = decodeArgAppend(kv.value[:0], s.b[k:], true) + kv.value = decodeArgAppend(kv.value[:0], s.b[k:]) } s.b = s.b[len(s.b):] return true } -func decodeArgAppend(dst, src []byte, decodePlus bool) []byte { - if bytes.IndexByte(src, '%') < 0 && (!decodePlus || bytes.IndexByte(src, '+') < 0) { +func decodeArgAppend(dst, src []byte) []byte { + if bytes.IndexByte(src, '%') < 0 && bytes.IndexByte(src, '+') < 0 { // fast path: src doesn't contain encoded chars return append(dst, src...) } @@ -466,15 +466,15 @@ func decodeArgAppend(dst, src []byte, decodePlus bool) []byte { if i+2 >= n { return append(dst, src[i:]...) } - x1 := hexbyte2int(src[i+1]) - x2 := hexbyte2int(src[i+2]) - if x1 < 0 || x2 < 0 { + x1 := hex2intTable[src[i+1]] + x2 := hex2intTable[src[i+2]] + if x1 == 16 || x2 == 16 { dst = append(dst, c) } else { - dst = append(dst, byte(x1<<4|x2)) + dst = append(dst, x1<<4|x2) i += 2 } - } else if decodePlus && c == '+' { + } else if c == '+' { dst = append(dst, ' ') } else { dst = append(dst, c) @@ -482,3 +482,36 @@ func decodeArgAppend(dst, src []byte, decodePlus bool) []byte { } return dst } + +// decodeArgAppendNoPlus is almost identical to decodeArgAppend, but it doesn't +// substitute '+' with ' '. +// +// The function is copy-pasted from decodeArgAppend due to the preformance +// reasons only. +func decodeArgAppendNoPlus(dst, src []byte) []byte { + if bytes.IndexByte(src, '%') < 0 { + // fast path: src doesn't contain encoded chars + return append(dst, src...) + } + + // slow path + for i, n := 0, len(src); i < n; i++ { + c := src[i] + if c == '%' { + if i+2 >= n { + return append(dst, src[i:]...) + } + x1 := hex2intTable[src[i+1]] + x2 := hex2intTable[src[i+2]] + if x1 == 16 || x2 == 16 { + dst = append(dst, c) + } else { + dst = append(dst, x1<<4|x2) + i += 2 + } + } else { + dst = append(dst, c) + } + } + return dst +} diff --git a/bytesconv.go b/bytesconv.go index 56d6ca2..d92150f 100644 --- a/bytesconv.go +++ b/bytesconv.go @@ -265,8 +265,8 @@ func readHexInt(r *bufio.Reader) (int, error) { } return -1, err } - k = hexbyte2int(c) - if k < 0 { + k = int(hex2intTable[c]) + if k == 16 { if i == 0 { return -1, errEmptyHexNum } @@ -324,23 +324,19 @@ func hexCharUpper(c byte) byte { var hex2intTable = func() []byte { b := make([]byte, 255) for i := byte(0); i < 255; i++ { - c := byte(0) + c := byte(16) if i >= '0' && i <= '9' { - c = 1 + i - '0' + c = i - '0' } else if i >= 'a' && i <= 'f' { - c = 1 + i - 'a' + 10 + c = i - 'a' + 10 } else if i >= 'A' && i <= 'F' { - c = 1 + i - 'A' + 10 + c = i - 'A' + 10 } b[i] = c } return b }() -func hexbyte2int(c byte) int { - return int(hex2intTable[c]) - 1 -} - const toLower = 'a' - 'A' var toLowerTable = func() [256]byte { @@ -401,7 +397,7 @@ func s2b(s string) []byte { // // dst may point to src. In this case src will be overwritten. func AppendUnquotedArg(dst, src []byte) []byte { - return decodeArgAppend(dst, src, true) + return decodeArgAppend(dst, src) } // AppendQuotedArg appends url-encoded src to dst and returns appended dst. diff --git a/bytesconv_timing_test.go b/bytesconv_timing_test.go index 53f04ac..24b6a49 100644 --- a/bytesconv_timing_test.go +++ b/bytesconv_timing_test.go @@ -75,18 +75,6 @@ func BenchmarkInt2HexByte(b *testing.B) { }) } -func BenchmarkHexByte2Int(b *testing.B) { - buf := []byte("0A1B2c3d4E5F6C7a8D9ab7cd03ef") - b.RunParallel(func(pb *testing.PB) { - var c byte - for pb.Next() { - for _, c = range buf { - hexbyte2int(c) - } - } - }) -} - func BenchmarkWriteHexInt(b *testing.B) { b.RunParallel(func(pb *testing.PB) { var w ByteBuffer diff --git a/uri.go b/uri.go index 0f6bc04..1d10e47 100644 --- a/uri.go +++ b/uri.go @@ -277,7 +277,7 @@ func (u *URI) parse(host, uri []byte, h *RequestHeader) { func normalizePath(dst, src []byte) []byte { dst = dst[:0] dst = addLeadingSlash(dst, src) - dst = decodeArgAppend(dst, src, false) + dst = decodeArgAppendNoPlus(dst, src) // remove duplicate slashes b := dst