mobile/bind: avoid intermediate []rune copy converting Java string to Go

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>
This commit is contained in:
Elias Naur
2016-03-05 10:02:05 +00:00
parent 4e994ac070
commit ba0a725146
25 changed files with 230 additions and 151 deletions
+11 -15
View File
@@ -15,7 +15,6 @@ package gomobile_bind
//#include "seq.h"
import "C"
import (
"unicode/utf16"
"unsafe"
"golang.org/x/mobile/bind/seq"
@@ -27,13 +26,12 @@ func DestroyRef(refnum C.int32_t) {
seq.Delete(int32(refnum))
}
// encodeString encodes a Go string to utf16 and returns a Java string as a nstring
// containing the jstring.
// 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).
// The returned data is always a copy, regardless of cpy, and will be freed in
// go_seq_to_java_string.
func encodeString(s string, cpy bool) C.nstring {
func encodeString(s string) C.nstring {
n := C.int(len(s))
if n == 0 {
return C.nstring{}
@@ -46,20 +44,18 @@ func encodeString(s string, cpy bool) C.nstring {
}
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)}
return C.nstring{chars: unsafe.Pointer(utf16buf), len: C.jsize(nchars*2)}
}
// decodeString decodes a nstring (jstring) to a Go string.
// If cpy is set, the string contains a copy and is freed.
func decodeString(str C.nstring, cpy bool) string {
// 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<<30 - 1]uint16)(unsafe.Pointer(str.chars))[:str.len]
s := string(utf16.Decode(chars)) // TODO: avoid the []rune allocation
if cpy {
C.free(str.chars)
}
chars := (*[1<<31 - 1]byte)(str.chars)[:str.len]
s := string(chars)
C.free(str.chars)
return s
}