mobile/bind: replace seq serialization with direct calls

The seq serialization machinery is a historic artifact from when Go
mobile code had to run in a separate process. Now that Go code is running
in-process, replace the explicit serialization with direct calls and pass
arguments on the stack.

The benefits are a much smaller bind runtime, much less garbage (and, in
Java, fewer objects with finalizers), less argument copying, and faster
cross-language calls.
The cost is a more complex generator, because some of the work from the
bind runtime is moved to generated code. Generated code now handles
conversion between Go and Java/ObjC types, multiple return values and memory
management of byte slice and string arguments.

To overcome the lack of calling C code between Go packages, all bound
packages now end up in the same (fake) package, "gomobile_bind", instead of
separate packages (go_<pkgname>). To avoid name clashes, the package name is
added as a prefix to generated functions and types.

Also, don't copy byte arrays passed to Go, saving call time and
allowing read([]byte)-style interfaces to foreign callers (#12113).

Finally, add support for nil interfaces and struct pointers to objc.

This is a large CL, but most of the changes stem from changing testdata.

The full benchcmp output on the CL/20095 benchmarks on my Nexus 5 is
reproduced below. Note that the savings for the JavaSlice* benchmarks are
skewed because byte slices are no longer copied before passing them to Go.

benchmark                                 old ns/op     new ns/op     delta
BenchmarkJavaEmpty                        26.0          19.0          -26.92%
BenchmarkJavaEmptyDirect                  23.0          22.0          -4.35%
BenchmarkJavaNoargs                       7685          2339          -69.56%
BenchmarkJavaNoargsDirect                 17405         8041          -53.80%
BenchmarkJavaOnearg                       26887         2366          -91.20%
BenchmarkJavaOneargDirect                 34266         7910          -76.92%
BenchmarkJavaOneret                       38325         2245          -94.14%
BenchmarkJavaOneretDirect                 46265         7708          -83.34%
BenchmarkJavaManyargs                     41720         2535          -93.92%
BenchmarkJavaManyargsDirect               51026         8373          -83.59%
BenchmarkJavaRefjava                      38139         21260         -44.26%
BenchmarkJavaRefjavaDirect                42706         28150         -34.08%
BenchmarkJavaRefgo                        34403         6843          -80.11%
BenchmarkJavaRefgoDirect                  40193         16582         -58.74%
BenchmarkJavaStringShort                  32366         9323          -71.20%
BenchmarkJavaStringShortDirect            41973         19118         -54.45%
BenchmarkJavaStringLong                   127879        94420         -26.16%
BenchmarkJavaStringLongDirect             133776        114760        -14.21%
BenchmarkJavaStringShortUnicode           32562         9221          -71.68%
BenchmarkJavaStringShortUnicodeDirect     41464         19094         -53.95%
BenchmarkJavaStringLongUnicode            131015        89401         -31.76%
BenchmarkJavaStringLongUnicodeDirect      134130        90786         -32.31%
BenchmarkJavaSliceShort                   42462         7538          -82.25%
BenchmarkJavaSliceShortDirect             52940         17017         -67.86%
BenchmarkJavaSliceLong                    138391        8466          -93.88%
BenchmarkJavaSliceLongDirect              205804        15666         -92.39%
BenchmarkGoEmpty                          3.00          3.00          +0.00%
BenchmarkGoEmptyDirect                    3.00          3.00          +0.00%
BenchmarkGoNoarg                          40342         13716         -66.00%
BenchmarkGoNoargDirect                    46691         13569         -70.94%
BenchmarkGoOnearg                         43529         13757         -68.40%
BenchmarkGoOneargDirect                   44867         14078         -68.62%
BenchmarkGoOneret                         45456         13559         -70.17%
BenchmarkGoOneretDirect                   44694         13442         -69.92%
BenchmarkGoRefjava                        55111         28071         -49.06%
BenchmarkGoRefjavaDirect                  60883         26872         -55.86%
BenchmarkGoRefgo                          57038         29223         -48.77%
BenchmarkGoRefgoDirect                    56153         27812         -50.47%
BenchmarkGoManyargs                       67967         17398         -74.40%
BenchmarkGoManyargsDirect                 60617         16998         -71.96%
BenchmarkGoStringShort                    57538         22600         -60.72%
BenchmarkGoStringShortDirect              52627         22704         -56.86%
BenchmarkGoStringLong                     128485        52530         -59.12%
BenchmarkGoStringLongDirect               138377        52079         -62.36%
BenchmarkGoStringShortUnicode             57062         22994         -59.70%
BenchmarkGoStringShortUnicodeDirect       62563         22938         -63.34%
BenchmarkGoStringLongUnicode              139913        55553         -60.29%
BenchmarkGoStringLongUnicodeDirect        150863        57791         -61.69%
BenchmarkGoSliceShort                     59279         20215         -65.90%
BenchmarkGoSliceShortDirect               60160         21136         -64.87%
BenchmarkGoSliceLong                      411225        301870        -26.59%
BenchmarkGoSliceLongDirect                399029        298915        -25.09%

Fixes golang/go#12619
Fixes golang/go#12113
Fixes golang/go#13033

Change-Id: I2b45e9e98a1248e3c23a5137f775f7364908bec7
Reviewed-on: https://go-review.googlesource.com/19821
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
This commit is contained in:
Elias Naur
2016-03-03 15:03:45 +00:00
parent 988d17d203
commit 6fca37c69e
100 changed files with 6574 additions and 6307 deletions
+262
View File
@@ -0,0 +1,262 @@
// 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.
// C support functions for bindings. This file is copied into the
// generated gomobile_bind package and compiled along with the
// generated binding files.
#include <android/log.h>
#include <errno.h>
#include <jni.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include "seq.h"
#include "_cgo_export.h"
#define NULL_REFNUM 41
static JavaVM *jvm;
// jnienvs holds the per-thread JNIEnv* for Go threads where we called AttachCurrentThread.
// A pthread key destructor is supplied to call DetachCurrentThread on exit. This trick is
// documented in http://developer.android.com/training/articles/perf-jni.html under "Threads".
static pthread_key_t jnienvs;
static jclass seq_class;
static jmethodID seq_throw_exc;
static jmethodID seq_getRef;
static jmethodID seq_decRef;
static jmethodID seq_incRef;
static jmethodID throwable_getMessage;
static jfieldID ref_objField;
// env_destructor is registered as a thread data key destructor to
// clean up a Go thread that is attached to the JVM.
static void env_destructor(void *env) {
if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) {
LOG_INFO("failed to detach current thread");
}
}
static JNIEnv *go_seq_get_thread_env(void) {
JNIEnv *env;
jint ret = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_6);
if (ret != JNI_OK) {
if (ret != JNI_EDETACHED) {
LOG_FATAL("failed to get thread env");
return;
}
if ((*jvm)->AttachCurrentThread(jvm, &env, NULL) != JNI_OK) {
LOG_FATAL("failed to attach current thread");
return;
}
pthread_setspecific(jnienvs, env);
}
return env;
}
void go_seq_maybe_throw_exception(JNIEnv *env, jobject msg) {
if (msg != NULL && (*env)->GetStringLength(env, msg) > 0) {
(*env)->CallStaticVoidMethod(env, seq_class, seq_throw_exc, msg);
}
}
jstring go_seq_get_exception_message(JNIEnv *env) {
jthrowable exc = (*env)->ExceptionOccurred(env);
if (!exc) {
return NULL;
}
(*env)->ExceptionClear(env);
return (*env)->CallObjectMethod(env, exc, throwable_getMessage);
}
jbyteArray go_seq_to_java_bytearray(JNIEnv *env, nbyteslice s, int copy) {
if (s.ptr == NULL) {
return NULL;
}
jbyteArray res = (*env)->NewByteArray(env, s.len);
if (res == NULL) {
LOG_FATAL("NewByteArray failed");
return res;
}
(*env)->SetByteArrayRegion(env, res, 0, s.len, s.ptr);
if (copy) {
free(s.ptr);
}
return res;
}
nstring go_seq_from_java_string(JNIEnv *env, jstring str, int copy) {
struct nstring res = {NULL, 0};
if (str == NULL) {
return res;
}
jsize nchars = (*env)->GetStringLength(env, str);
jchar *chars = (jchar *)(*env)->GetStringChars(env, str, NULL);
if (chars == NULL) {
LOG_FATAL("GetStringChars failed");
return res;
}
if (copy) {
void *arr_copy = malloc(nchars*2);
if (arr_copy == NULL) {
LOG_FATAL("malloc failed");
return res;
}
memcpy(arr_copy, chars, nchars*2);
(*env)->ReleaseStringChars(env, str, chars);
chars = (jchar *)arr_copy;
}
res.chars = chars;
res.len = nchars;
return res;
}
nbyteslice go_seq_from_java_bytearray(JNIEnv *env, jbyteArray arr, int copy) {
struct nbyteslice res = {NULL, 0};
if (arr == NULL) {
return res;
}
jsize len = (*env)->GetArrayLength(env, arr);
if (len == 0) {
return res;
}
jbyte *ptr = (*env)->GetByteArrayElements(env, arr, NULL);
if (ptr == NULL) {
LOG_FATAL("GetByteArrayElements failed");
return res;
}
if (copy) {
void *ptr_copy = (void *)malloc(len);
if (ptr_copy == NULL) {
LOG_FATAL("malloc failed");
return res;
}
memcpy(ptr_copy, ptr, len);
(*env)->ReleaseByteArrayElements(env, arr, ptr, JNI_ABORT);
ptr = (jbyte *)ptr_copy;
}
res.ptr = ptr;
res.len = len;
return res;
}
int32_t go_seq_to_refnum(JNIEnv *env, jobject o) {
if (o == NULL) {
return NULL_REFNUM;
}
return (int32_t)(*env)->CallStaticIntMethod(env, seq_class, seq_incRef, o);
}
jobject go_seq_from_refnum(JNIEnv *env, int32_t refnum, jclass proxy_class, jmethodID proxy_cons) {
if (refnum == NULL_REFNUM) {
return NULL;
}
// Seq.Ref ref = Seq.getRef(refnum)
jobject ref = (*env)->CallStaticObjectMethod(env, seq_class, seq_getRef, (jint)refnum);
if (ref == NULL) {
LOG_FATAL("Unknown reference: %d", refnum);
return NULL;
}
if (refnum > 0) { // Java object
// return ref.obj
return (*env)->GetObjectField(env, ref, ref_objField);
} else {
// return new <Proxy>(ref)
return (*env)->NewObject(env, proxy_class, proxy_cons, ref);
}
}
// go_seq_to_java_string converts a nstring to a jstring.
jstring go_seq_to_java_string(JNIEnv *env, nstring str) {
jstring s = (*env)->NewString(env, str.chars, str.len);
if (str.chars != NULL) {
free(str.chars);
}
return s;
}
// go_seq_push_local_frame retrieves or creates the JNIEnv* for the current thread
// and pushes a JNI reference frame. Must be matched with call to go_seq_pop_local_frame.
JNIEnv *go_seq_push_local_frame(jint cap) {
JNIEnv *env = go_seq_get_thread_env();
if ((*env)->PushLocalFrame(env, cap) < 0) {
LOG_FATAL("PushLocalFrame failed");
}
return env;
}
// Pop the current local frame, freeing all JNI local references in it
void go_seq_pop_local_frame(JNIEnv *env) {
(*env)->PopLocalFrame(env, NULL);
}
void go_seq_dec_ref(int32_t ref) {
JNIEnv *env = go_seq_get_thread_env();
(*env)->CallStaticVoidMethod(env, seq_class, seq_decRef, (jint)ref);
}
JNIEXPORT void JNICALL
Java_go_Seq_init(JNIEnv *env, jclass clazz) {
if ((*env)->GetJavaVM(env, &jvm) != 0) {
LOG_FATAL("failed to get JVM");
return;
}
if (pthread_key_create(&jnienvs, env_destructor) != 0) {
LOG_FATAL("failed to initialize jnienvs thread local storage");
return;
}
seq_class = (*env)->NewGlobalRef(env, clazz);
seq_throw_exc = (*env)->GetStaticMethodID(env, seq_class, "throwException", "(Ljava/lang/String;)V");
if (seq_throw_exc == NULL) {
LOG_FATAL("failed to find method Seq.throwException");
return;
}
seq_getRef = (*env)->GetStaticMethodID(env, seq_class, "getRef", "(I)Lgo/Seq$Ref;");
if (seq_getRef == NULL) {
LOG_FATAL("failed to find method Seq.getRef");
return;
}
seq_decRef = (*env)->GetStaticMethodID(env, seq_class, "decRef", "(I)V");
if (seq_decRef == NULL) {
LOG_FATAL("failed to find method Seq.decRef");
return;
}
seq_incRef = (*env)->GetStaticMethodID(env, seq_class, "incRef", "(Lgo/Seq$Object;)I");
if (seq_incRef == NULL) {
LOG_FATAL("failed to find method Seq.incRef");
return;
}
jclass throwable_class = (*env)->FindClass(env, "java/lang/Throwable");
if (throwable_class == NULL) {
LOG_FATAL("failed to find Throwable class");
return;
}
throwable_getMessage = (*env)->GetMethodID(env, throwable_class, "getMessage", "()Ljava/lang/String;");
if (throwable_getMessage == NULL) {
LOG_FATAL("failed to find method Throwable.getMessage");
return;
}
jclass ref_class = (*env)->FindClass(env, "go/Seq$Ref");
if (ref_class == NULL) {
LOG_FATAL("failed to find the Seq.Ref class");
return;
}
ref_objField = (*env)->GetFieldID(env, ref_class, "obj", "Lgo/Seq$Object;");
if (ref_objField == NULL) {
LOG_FATAL("failed to find the Seq.Ref.obj field");
return;
}
}
JNIEXPORT void JNICALL
Java_go_Seq_destroyRef(JNIEnv *env, jclass clazz, jint refnum) {
DestroyRef(refnum);
}