-
Notifications
You must be signed in to change notification settings - Fork 999
Description
The current system works but enlists an allocation and memcopy.
As it stands, there is already a mechanism enlisting unsafe and reflect used in the wild for converting a string to a byte slice without the allocation and copy. And the support by tinygo for unsafe and reflect already makes it work. See the example further down.
A function performing the range:
func rangeStringAsBytes(s string) {
for _, b := range []byte(s) {
save += b
}
}is compiled by tinygo to this wasm function (note the call to runtime_stringToBytes):
function rangeStringAsBytes() {
var e:int;
var a:int = stack_pointer - 16;
stack_pointer = a;
a[1]:long@4 = 1L;
var b:int = 0;
var d:int = 99548[0]:int;
99548[0]:int = a;
a[0]:int = d;
(a + 8)[0]:int = (e = runtime_stringToBytes(85512, 35));
var c:int = 101304[0]:ubyte;
loop L_a {
if (b == 35) {
101304[0]:byte = c;
99548[0]:int = d;
stack_pointer = a + 16;
} else {
c = (b + e)[0]:ubyte + c;
b = b + 1;
continue L_a;
}
}
}
A lead go developer provided a solution using unsafe and reflect. This already works with tinygo.
This idea was fleshed out a bit in
https://stackoverflow.com/questions/59209493/how-to-use-unsafe-get-a-byte-slice-from-a-string-without-memory-copy .
The comments by two go developers are here:
https://groups.google.com/g/golang-nuts/c/Zsfk-VMd_fU/m/O1ru4fO-BgAJ
func unsafeGetBytes(s string) []byte {
return (*[0x7fff0000]byte)(unsafe.Pointer(
(*reflect.StringHeader)(unsafe.Pointer(&s)).Data),
)[:len(s):len(s)]
}
// As in
func rangeUnsafeStringAsBytes(s string) {
for _, b := range unsafeGetBytes(s) {
save += b
}
}Some benchmarks showing bytes, strings, and manual/unsafe conversions between the two. Starred are the relevant results for this issue.
tinygo/wasm running in nodejs on older mac:
BenchmarkRangeBytes
BenchmarkRangeBytes 83960235 14.45 ns/op *
BenchmarkRangeBytes 82679247 14.36 ns/op
BenchmarkRangeBytes 83991829 14.38 ns/op
BenchmarkRangeStringAsBytes
BenchmarkRangeStringAsBytes 15033096 79.55 ns/op *
BenchmarkRangeStringAsBytes 14958260 79.64 ns/op
BenchmarkRangeStringAsBytes 14833155 79.80 ns/op
BenchmarkRangeUnsafeStringAsBytes
BenchmarkRangeUnsafeStringAsBytes 83963244 14.51 ns/op *
BenchmarkRangeUnsafeStringAsBytes 81523156 14.43 ns/op
BenchmarkRangeUnsafeStringAsBytes 83891115 14.51 ns/op
BenchmarkMapStringLookup
BenchmarkMapStringLookup 15326641 78.12 ns/op
BenchmarkMapStringLookup 15441502 79.49 ns/op
BenchmarkMapStringLookup 15446286 77.92 ns/op
BenchmarkMapBytesLookup
BenchmarkMapBytesLookup 8191792 144.5 ns/op
BenchmarkMapBytesLookup 8333955 143.1 ns/op
BenchmarkMapBytesLookup 8344280 142.8 ns/op
BenchmarkMapUnsafeBytesLookup
BenchmarkMapUnsafeBytesLookup 15252482 78.89 ns/op
BenchmarkMapUnsafeBytesLookup 14966283 79.39 ns/op
BenchmarkMapUnsafeBytesLookup 15070021 79.89 ns/op
The code for this benchmark can be found in issue #2699