Browse Source

Use vectorized `index_*` procs in `core`

Feoramund 1 year ago
parent
commit
f66fcd9acb
2 changed files with 78 additions and 16 deletions
  1. 39 8
      core/bytes/bytes.odin
  2. 39 8
      core/strings/strings.odin

+ 39 - 8
core/bytes/bytes.odin

@@ -1,6 +1,8 @@
 package bytes
 package bytes
 
 
+import "base:intrinsics"
 import "core:mem"
 import "core:mem"
+@require import simd_util "core:simd/util"
 import "core:unicode"
 import "core:unicode"
 import "core:unicode/utf8"
 import "core:unicode/utf8"
 
 
@@ -295,22 +297,51 @@ split_after_iterator :: proc(s: ^[]byte, sep: []byte) -> ([]byte, bool) {
 
 
 
 
 index_byte :: proc(s: []byte, c: byte) -> int {
 index_byte :: proc(s: []byte, c: byte) -> int {
-	for i := 0; i < len(s); i += 1 {
-		if s[i] == c {
-			return i
+	_index_byte :: #force_inline proc(s: []byte, c: byte) -> int {
+		for i := 0; i < len(s); i += 1 {
+			if s[i] == c {
+				return i
+			}
+		}
+		return -1
+	}
+
+	// NOTE(Feoramund): On my Alder Lake CPU, I have only witnessed a
+	// significant speedup when compiling in either Size or Speed mode.
+	// The SIMD version is usually 2-3x slower without optimizations on.
+	when ODIN_OPTIMIZATION_MODE > .Minimal && intrinsics.has_target_feature("sse2") {
+		// SIMD's benefits are noticeable only past a certain threshold of data.
+		// For small data, use the plain old algorithm.
+		if len(s) >= simd_util.RECOMMENDED_SCAN_SIZE {
+			return simd_util.index_byte(s, c)
+		} else {
+			return _index_byte(s, c)
 		}
 		}
+	} else {
+		return _index_byte(s, c)
 	}
 	}
-	return -1
 }
 }
 
 
 // Returns -1 if c is not present
 // Returns -1 if c is not present
 last_index_byte :: proc(s: []byte, c: byte) -> int {
 last_index_byte :: proc(s: []byte, c: byte) -> int {
-	for i := len(s)-1; i >= 0; i -= 1 {
-		if s[i] == c {
-			return i
+	_last_index_byte :: #force_inline proc(s: []byte, c: byte) -> int {
+		for i := len(s)-1; i >= 0; i -= 1 {
+			if s[i] == c {
+				return i
+			}
 		}
 		}
+		return -1
+	}
+
+	when ODIN_OPTIMIZATION_MODE > .Minimal && intrinsics.has_target_feature("sse2") {
+		if len(s) >= simd_util.RECOMMENDED_SCAN_SIZE {
+			return simd_util.last_index_byte(s, c)
+		} else {
+			return _last_index_byte(s, c)
+		}
+	} else {
+		return _last_index_byte(s, c)
 	}
 	}
-	return -1
 }
 }
 
 
 
 

+ 39 - 8
core/strings/strings.odin

@@ -1,7 +1,9 @@
 // Procedures to manipulate UTF-8 encoded strings
 // Procedures to manipulate UTF-8 encoded strings
 package strings
 package strings
 
 
+import "base:intrinsics"
 import "core:io"
 import "core:io"
+@require import simd_util "core:simd/util"
 import "core:mem"
 import "core:mem"
 import "core:unicode"
 import "core:unicode"
 import "core:unicode/utf8"
 import "core:unicode/utf8"
@@ -1424,12 +1426,29 @@ Output:
 
 
 */
 */
 index_byte :: proc(s: string, c: byte) -> (res: int) {
 index_byte :: proc(s: string, c: byte) -> (res: int) {
-	for i := 0; i < len(s); i += 1 {
-		if s[i] == c {
-			return i
+	_index_byte :: #force_inline proc(s: string, c: byte) -> int {
+		for i := 0; i < len(s); i += 1 {
+			if s[i] == c {
+				return i
+			}
+		}
+		return -1
+	}
+
+	// NOTE(Feoramund): On my Alder Lake CPU, I have only witnessed a
+	// significant speedup when compiling in either Size or Speed mode.
+	// The SIMD version is usually 2-3x slower without optimizations on.
+	when ODIN_OPTIMIZATION_MODE > .Minimal && intrinsics.has_target_feature("sse2") {
+		// SIMD's benefits are noticeable only past a certain threshold of data.
+		// For small data, use the plain old algorithm.
+		if len(s) >= simd_util.RECOMMENDED_SCAN_SIZE {
+			return simd_util.index_byte(transmute([]u8)s, c)
+		} else {
+			return _index_byte(s, c)
 		}
 		}
+	} else {
+		return _index_byte(s, c)
 	}
 	}
-	return -1
 }
 }
 /*
 /*
 Returns the byte offset of the last byte `c` in the string `s`, -1 when not found.
 Returns the byte offset of the last byte `c` in the string `s`, -1 when not found.
@@ -1464,12 +1483,24 @@ Output:
 
 
 */
 */
 last_index_byte :: proc(s: string, c: byte) -> (res: int) {
 last_index_byte :: proc(s: string, c: byte) -> (res: int) {
-	for i := len(s)-1; i >= 0; i -= 1 {
-		if s[i] == c {
-			return i
+	_last_index_byte :: #force_inline proc(s: string, c: byte) -> int {
+		for i := len(s)-1; i >= 0; i -= 1 {
+			if s[i] == c {
+				return i
+			}
 		}
 		}
+		return -1
+	}
+
+	when ODIN_OPTIMIZATION_MODE > .Minimal && intrinsics.has_target_feature("sse2") {
+		if len(s) >= simd_util.RECOMMENDED_SCAN_SIZE {
+			return simd_util.last_index_byte(transmute([]u8)s, c)
+		} else {
+			return _last_index_byte(s, c)
+		}
+	} else {
+		return _last_index_byte(s, c)
 	}
 	}
-	return -1
 }
 }
 /*
 /*
 Returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found.
 Returns the byte offset of the first rune `r` in the string `s` it finds, -1 when not found.