Converting a Go string to a string suitable use a specialized function, UTF16Encode, that can encode the string directly to a malloc'ed buffer. That way, only two copies are made when strings are passed from Go to Java; once for UTF-8 to UTF-16 encoding and once for the creation of the Java String. This CL implements the same optimization in the other direction, with a UTF-16 to UTF-8 decoder implemented in C. Unfortunately, while calling into a Go decoder also saves the extra copy, the Cgo overhead makes the calls much slower for short strings. To alleviate the risk of introducing decoding bugs, I've added the tests from the encoding/utf16 package to SeqTest. As a sideeffect, both Java and ObjC now always copy strings, regardless of the argument mode. The cpy argument can therefore be removed from the string conversion functions. Furthermore, the modeRetained and modeReturned modes can be collapsed into just one. While we're here, delete a leftover function from seq/strings.go that wasn't removed when the old seq buffers went away. Benchmarks, as compared with benchstat over 5 runs: name old time/op new time/op delta JavaStringShort 11.4µs ±13% 11.6µs ± 4% ~ (p=0.859 n=10+5) JavaStringShortDirect 19.5µs ± 9% 20.3µs ± 2% +3.68% (p=0.019 n=9+5) JavaStringLong 103µs ± 8% 24µs ± 4% -77.13% (p=0.001 n=9+5) JavaStringLongDirect 113µs ± 9% 32µs ± 7% -71.63% (p=0.001 n=9+5) JavaStringShortUnicode 11.1µs ±16% 10.7µs ± 5% ~ (p=0.190 n=9+5) JavaStringShortUnicodeDirect 19.6µs ± 7% 20.2µs ± 1% +2.78% (p=0.029 n=9+5) JavaStringLongUnicode 97.1µs ± 9% 28.0µs ± 5% -71.17% (p=0.001 n=9+5) JavaStringLongUnicodeDirect 105µs ±10% 34µs ± 5% -67.23% (p=0.002 n=8+5) JavaStringRetShort 14.2µs ± 2% 13.9µs ± 1% -2.15% (p=0.006 n=8+5) JavaStringRetShortDirect 20.8µs ± 2% 20.4µs ± 2% ~ (p=0.065 n=8+5) JavaStringRetLong 42.2µs ± 9% 42.4µs ± 3% ~ (p=0.190 n=9+5) JavaStringRetLongDirect 51.2µs ±21% 50.8µs ± 8% ~ (p=0.518 n=9+5) GoStringShort 23.4µs ± 7% 22.5µs ± 3% -3.55% (p=0.019 n=9+5) GoStringLong 51.9µs ± 9% 53.1µs ± 3% ~ (p=0.240 n=9+5) GoStringShortUnicode 24.2µs ± 6% 22.8µs ± 1% -5.54% (p=0.002 n=9+5) GoStringLongUnicode 58.6µs ± 8% 57.6µs ± 3% ~ (p=0.518 n=9+5) GoStringRetShort 27.6µs ± 1% 23.2µs ± 2% -15.87% (p=0.003 n=7+5) GoStringRetLong 129µs ±12% 33µs ± 2% -74.03% (p=0.001 n=10+5) Change-Id: Icb9481981493ffca8defed9fb80a9433d6048937 Reviewed-on: https://go-review.googlesource.com/20250 Reviewed-by: David Crawshaw <crawshaw@golang.org>
98 lines
2.7 KiB
Plaintext
98 lines
2.7 KiB
Plaintext
// Copyright 2016 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package gomobile_bind
|
|
|
|
// Go support functions for bindings. This file is copied into the
|
|
// generated gomobile_bind package and compiled along with the
|
|
// generated binding files.
|
|
|
|
//#cgo LDFLAGS: -llog
|
|
//#include <jni.h>
|
|
//#include <stdint.h>
|
|
//#include <stdlib.h>
|
|
//#include "seq.h"
|
|
import "C"
|
|
import (
|
|
"unsafe"
|
|
|
|
"golang.org/x/mobile/bind/seq"
|
|
)
|
|
|
|
// DestroyRef is called by Java to inform Go it is done with a reference.
|
|
//export DestroyRef
|
|
func DestroyRef(refnum C.int32_t) {
|
|
seq.Delete(int32(refnum))
|
|
}
|
|
|
|
// encodeString returns a copy of a Go string as a UTF16 encoded nstring.
|
|
// The returned data is freed in go_seq_to_java_string.
|
|
//
|
|
// encodeString uses UTF16 as the intermediate format. Note that UTF8 is an obvious
|
|
// alternative, but JNI only supports a C-safe variant of UTF8 (modified UTF8).
|
|
func encodeString(s string) C.nstring {
|
|
n := C.int(len(s))
|
|
if n == 0 {
|
|
return C.nstring{}
|
|
}
|
|
// Allocate enough for the worst case estimate, every character is a surrogate pair
|
|
worstCaseLen := 4 * len(s)
|
|
utf16buf := C.malloc(C.size_t(worstCaseLen))
|
|
if utf16buf == nil {
|
|
panic("encodeString: malloc failed")
|
|
}
|
|
chars := (*[1<<30 - 1]uint16)(unsafe.Pointer(utf16buf))[:worstCaseLen/2 : worstCaseLen/2]
|
|
nchars := seq.UTF16Encode(s, chars)
|
|
return C.nstring{chars: unsafe.Pointer(utf16buf), len: C.jsize(nchars*2)}
|
|
}
|
|
|
|
// decodeString decodes a UTF8 encoded nstring to a Go string. The data
|
|
// in str is freed after use.
|
|
func decodeString(str C.nstring) string {
|
|
if str.chars == nil {
|
|
return ""
|
|
}
|
|
chars := (*[1<<31 - 1]byte)(str.chars)[:str.len]
|
|
s := string(chars)
|
|
C.free(str.chars)
|
|
return s
|
|
}
|
|
|
|
// fromSlice converts a slice to a jbyteArray cast as a nbyteslice. If cpy
|
|
// is set, the returned slice is a copy to be free by go_seq_to_java_bytearray.
|
|
func fromSlice(s []byte, cpy bool) C.nbyteslice {
|
|
if s == nil || len(s) == 0 {
|
|
return C.nbyteslice{}
|
|
}
|
|
var ptr *C.jbyte
|
|
n := C.jsize(len(s))
|
|
if cpy {
|
|
ptr = (*C.jbyte)(C.malloc(C.size_t(n)))
|
|
if ptr == nil {
|
|
panic("fromSlice: malloc failed")
|
|
}
|
|
copy((*[1<<31 - 1]byte)(unsafe.Pointer(ptr))[:n], s)
|
|
} else {
|
|
ptr = (*C.jbyte)(unsafe.Pointer(&s[0]))
|
|
}
|
|
return C.nbyteslice{ptr: unsafe.Pointer(ptr), len: n}
|
|
}
|
|
|
|
// toSlice takes a nbyteslice (jbyteArray) and returns a byte slice
|
|
// with the data. If cpy is set, the slice contains a copy of the data and is
|
|
// freed.
|
|
func toSlice(s C.nbyteslice, cpy bool) []byte {
|
|
if s.ptr == nil || s.len == 0 {
|
|
return nil
|
|
}
|
|
var b []byte
|
|
if cpy {
|
|
b = C.GoBytes(s.ptr, C.int(s.len))
|
|
C.free(s.ptr)
|
|
} else {
|
|
b = (*[1<<31 - 1]byte)(unsafe.Pointer(s.ptr))[:s.len:s.len]
|
|
}
|
|
return b
|
|
}
|