-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Performance optimizations for non-ASCII input #4889
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
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
c28a978
Avoid over-allocation in ToChars
junegunn 7761f3f
Skip rune decoding for ASCII bytes in ToChars
junegunn 89939f1
Prefilter rune-mode input
junegunn be50293
Skip the normalization map for runes that cannot normalize
junegunn 76db141
Prefilter rune-mode input for non-ASCII patterns
junegunn 01c44a7
Copy the item text in replace-query
junegunn caf4253
Update CHANGELOG
junegunn f5512cb
Parenthesize an assert_equal argument in the zsh chpwd test
junegunn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| //go:build !386 && !amd64 && !arm64 | ||
|
|
||
| package algo | ||
|
|
||
| // The byte-view scanners in runeindex_x86.go reinterpret a []rune as | ||
| // little-endian 4-byte lanes, which is not valid everywhere. Elsewhere the | ||
| // reference scanners are the implementation. | ||
|
|
||
| func indexAsciiRune(runes []rune, caseSensitive bool, b byte, from int) int { | ||
| return indexAsciiRuneRef(runes, caseSensitive, b, from) | ||
| } | ||
|
|
||
| func lastIndexAsciiRune(runes []rune, caseSensitive bool, b byte, from int) int { | ||
| return lastIndexAsciiRuneRef(runes, caseSensitive, b, from) | ||
| } | ||
|
|
||
| func indexRune(runes []rune, r rune, from int) int { | ||
| return indexRuneRef(runes, r, from) | ||
| } | ||
|
|
||
| func lastIndexRune(runes []rune, r rune, from int) int { | ||
| return lastIndexRuneRef(runes, r, from) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package algo | ||
|
|
||
| // Reference scanners over a []rune, with no representation tricks. | ||
| // | ||
| // They have two roles. Where reinterpreting a []rune as little-endian bytes is | ||
| // not valid, they are the shipped implementation, via runeindex_others.go. | ||
| // Everywhere else, the tests feed the same inputs to these and to the byte-view | ||
| // scanners in runeindex_x86.go and require identical answers. | ||
| // | ||
| // They carry no build tag so that both roles hold on every platform. Otherwise | ||
| // the portable build would be code that nothing here ever runs. | ||
|
|
||
| func indexAsciiRuneRef(runes []rune, caseSensitive bool, b byte, from int) int { | ||
| lower, upper := rune(b), rune(-1) | ||
| if !caseSensitive && b >= 'a' && b <= 'z' { | ||
| upper = rune(b - 32) | ||
| } | ||
| for i := from; i < len(runes); i++ { | ||
| if runes[i] == lower || runes[i] == upper { | ||
| return i | ||
| } | ||
| } | ||
| return -1 | ||
| } | ||
|
|
||
| func lastIndexAsciiRuneRef(runes []rune, caseSensitive bool, b byte, from int) int { | ||
| lower, upper := rune(b), rune(-1) | ||
| if !caseSensitive && b >= 'a' && b <= 'z' { | ||
| upper = rune(b - 32) | ||
| } | ||
| for i := len(runes) - 1; i >= from; i-- { | ||
| if runes[i] == lower || runes[i] == upper { | ||
| return i | ||
| } | ||
| } | ||
| return -1 | ||
| } | ||
|
|
||
| func indexRuneRef(runes []rune, r rune, from int) int { | ||
| for i := from; i < len(runes); i++ { | ||
| if runes[i] == r { | ||
| return i | ||
| } | ||
| } | ||
| return -1 | ||
| } | ||
|
|
||
| func lastIndexRuneRef(runes []rune, r rune, from int) int { | ||
| for i := len(runes) - 1; i >= from; i-- { | ||
| if runes[i] == r { | ||
| return i | ||
| } | ||
| } | ||
| return -1 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| //go:build 386 || amd64 || arm64 | ||
|
|
||
| package algo | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "unsafe" | ||
| ) | ||
|
|
||
| // On these architectures a []rune is a little-endian array of 4-byte lanes, so | ||
| // an ASCII rune is the byte itself followed by three zero bytes at a 4-byte | ||
| // aligned offset. That lets the SIMD byte scanners run over the rune array | ||
| // directly: find the low byte, then confirm alignment and the three zeroes. | ||
| // A byte equal to the needle can also appear as the low byte of a multi-byte | ||
| // rune (0x0165 has low byte 'e'), which those two checks reject. | ||
|
|
||
| func runeBytes(runes []rune) []byte { | ||
| return unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(runes))), len(runes)*4) | ||
| } | ||
|
|
||
| // indexAsciiRune returns the index of the first rune equal to b, or to its | ||
| // uppercase form when ignoring case, at or after rune index from. | ||
| func indexAsciiRune(runes []rune, caseSensitive bool, b byte, from int) int { | ||
| view := runeBytes(runes) | ||
| both := !caseSensitive && b >= 'a' && b <= 'z' | ||
| for off := from * 4; off < len(view); { | ||
| var idx int | ||
| if both { | ||
| idx = IndexByteTwo(view[off:], b, b-32) | ||
| } else { | ||
| idx = bytes.IndexByte(view[off:], b) | ||
| } | ||
| if idx < 0 { | ||
| return -1 | ||
| } | ||
| pos := off + idx | ||
| if pos&3 == 0 && view[pos+1]|view[pos+2]|view[pos+3] == 0 { | ||
| return pos >> 2 | ||
| } | ||
| off = pos + 1 | ||
| } | ||
| return -1 | ||
| } | ||
|
|
||
| // runeNeedle picks which of the rune's four bytes to scan for, and returns its | ||
| // lane index and value. A zero byte is a useless needle because every ASCII | ||
| // rune contributes three of them, so U+AE00 scanned by its low byte would hit | ||
| // on almost every character of an ASCII-heavy line. Prefer a byte that cannot | ||
| // occur in an ASCII rune at all, then any non-zero byte. | ||
| func runeNeedle(r rune) (int, byte) { | ||
| var b [4]byte | ||
| b[0], b[1], b[2], b[3] = byte(r), byte(r>>8), byte(r>>16), byte(r>>24) | ||
| for i, v := range b { | ||
| if v >= 0x80 { | ||
| return i, v | ||
| } | ||
| } | ||
| for i, v := range b { | ||
| if v != 0 { | ||
| return i, v | ||
| } | ||
| } | ||
| return 0, 0 | ||
| } | ||
|
|
||
| func runeAt(view []byte, start int) rune { | ||
| return rune(view[start]) | rune(view[start+1])<<8 | rune(view[start+2])<<16 | rune(view[start+3])<<24 | ||
| } | ||
|
|
||
| // indexRune returns the index of the first rune equal to r at or after rune | ||
| // index from. Case is not folded, so the caller must have established that no | ||
| // other rune can transform into r. | ||
| func indexRune(runes []rune, r rune, from int) int { | ||
| view := runeBytes(runes) | ||
| lane, needle := runeNeedle(r) | ||
| for off := from*4 + lane; off < len(view); { | ||
| idx := bytes.IndexByte(view[off:], needle) | ||
| if idx < 0 { | ||
| return -1 | ||
| } | ||
| pos := off + idx | ||
| if start := pos - lane; start&3 == 0 && runeAt(view, start) == r { | ||
| return start >> 2 | ||
| } | ||
| off = pos + 1 | ||
| } | ||
| return -1 | ||
| } | ||
|
|
||
| // lastIndexRune is indexRune scanning backwards from the end. | ||
| func lastIndexRune(runes []rune, r rune, from int) int { | ||
| view := runeBytes(runes) | ||
| lane, needle := runeNeedle(r) | ||
| for end := len(view); end > from*4+lane; { | ||
| idx := bytes.LastIndexByte(view[from*4+lane:end], needle) | ||
| if idx < 0 { | ||
| return -1 | ||
| } | ||
| pos := from*4 + lane + idx | ||
| if start := pos - lane; start&3 == 0 && runeAt(view, start) == r { | ||
| return start >> 2 | ||
| } | ||
| end = pos | ||
| } | ||
| return -1 | ||
| } | ||
|
|
||
| // lastIndexAsciiRune is indexAsciiRune scanning backwards from the end. | ||
| func lastIndexAsciiRune(runes []rune, caseSensitive bool, b byte, from int) int { | ||
| view := runeBytes(runes)[from*4:] | ||
| both := !caseSensitive && b >= 'a' && b <= 'z' | ||
| for end := len(view); end > 0; { | ||
| var idx int | ||
| if both { | ||
| idx = lastIndexByteTwo(view[:end], b, b-32) | ||
| } else { | ||
| idx = bytes.LastIndexByte(view[:end], b) | ||
| } | ||
| if idx < 0 { | ||
| return -1 | ||
| } | ||
| if idx&3 == 0 && view[idx+1]|view[idx+2]|view[idx+3] == 0 { | ||
| return from + idx>>2 | ||
| } | ||
| end = idx | ||
| } | ||
| return -1 | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.