Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d387e79a authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Offer to write Strings through Parcels as UTF-8.

Recently while investigating some Binder limits, I discovered that
we're still sending Strings across Binder as UTF-16, which is very
wasteful for two reasons:

1. The majority of data flowing through APIs like PackageManager is
already limited to US-ASCII, and by sending UTF-16 we're wasting
half of our transactions on null-byte overhead.

2. Internally ART is already "compressing" simple strings by storing
them as US-ASCII instead of UTF-16, meaning every time we want to
write a simple string to Binder, we're forced to first inflate it
to UTF-16.

This change first updates Parcel.cpp to accept char* UTF-8 strings,
similar to how it accepts char16_t* for UTF-16.  It then offers
both UTF-8 and UTF-16 variants to Parcel.java via JNI.  We also
update the String8 handling to behave identical to String16.

This change adds benchmarking to show that these new methods are
about 50% faster for US-ASCII strings, and about 68% faster for
complex strings that reference higher Unicode planes.  (So an
improvement in both cases!)

Bug: 154436100
Test: atest FrameworksCoreTests:ParcelTest
Test: make core-libart conscrypt okhttp bouncycastle vogar caliper && vogar --mode app_process --benchmark frameworks/base/core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java
Change-Id: I22a11d3497486d922ec8e14c85df66ca096b8f2a
parent 6d187276
Loading
Loading
Loading
Loading
+20 −2
Original line number Original line Diff line number Diff line
@@ -78,10 +78,19 @@ public class PackageParserCacheHelper {
        /**
        /**
         * Read an string index from a parcel, and returns the corresponding string from the pool.
         * Read an string index from a parcel, and returns the corresponding string from the pool.
         */
         */
        @Override
        public String readString(Parcel p) {
        public String readString(Parcel p) {
            return mStrings.get(p.readInt());
            return mStrings.get(p.readInt());
        }
        }

        @Override
        public String readString8(Parcel p) {
            return readString(p);
        }

        @Override
        public String readString16(Parcel p) {
            return readString(p);
        }
    }
    }


    /**
    /**
@@ -110,7 +119,6 @@ public class PackageParserCacheHelper {
         * Instead of writing a string directly to a parcel, this method adds it to the pool,
         * Instead of writing a string directly to a parcel, this method adds it to the pool,
         * and write the index in the pool to the parcel.
         * and write the index in the pool to the parcel.
         */
         */
        @Override
        public void writeString(Parcel p, String s) {
        public void writeString(Parcel p, String s) {
            final Integer cur = mIndexes.get(s);
            final Integer cur = mIndexes.get(s);
            if (cur != null) {
            if (cur != null) {
@@ -133,6 +141,16 @@ public class PackageParserCacheHelper {
            }
            }
        }
        }


        @Override
        public void writeString8(Parcel p, String s) {
            writeString(p, s);
        }

        @Override
        public void writeString16(Parcel p, String s) {
            writeString(p, s);
        }

        /**
        /**
         * Closes a parcel by appending the string pool at the end and updating the pool offset,
         * Closes a parcel by appending the string pool at the end and updating the pool offset,
         * which it assumes is at the first byte.  It also uninstalls itself as a read-write helper.
         * which it assumes is at the first byte.  It also uninstalls itself as a read-write helper.
+63 −12
Original line number Original line Diff line number Diff line
@@ -307,7 +307,9 @@ public final class Parcel {
    @FastNative
    @FastNative
    private static native void nativeWriteDouble(long nativePtr, double val);
    private static native void nativeWriteDouble(long nativePtr, double val);
    @FastNative
    @FastNative
    static native void nativeWriteString(long nativePtr, String val);
    private static native void nativeWriteString8(long nativePtr, String val);
    @FastNative
    private static native void nativeWriteString16(long nativePtr, String val);
    @FastNative
    @FastNative
    private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
    private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
    @FastNative
    @FastNative
@@ -325,7 +327,9 @@ public final class Parcel {
    @CriticalNative
    @CriticalNative
    private static native double nativeReadDouble(long nativePtr);
    private static native double nativeReadDouble(long nativePtr);
    @FastNative
    @FastNative
    static native String nativeReadString(long nativePtr);
    private static native String nativeReadString8(long nativePtr);
    @FastNative
    private static native String nativeReadString16(long nativePtr);
    @FastNative
    @FastNative
    private static native IBinder nativeReadStrongBinder(long nativePtr);
    private static native IBinder nativeReadStrongBinder(long nativePtr);
    @FastNative
    @FastNative
@@ -386,8 +390,12 @@ public final class Parcel {
         * must use {@link #writeStringNoHelper(String)} to avoid
         * must use {@link #writeStringNoHelper(String)} to avoid
         * infinity recursive calls.
         * infinity recursive calls.
         */
         */
        public void writeString(Parcel p, String s) {
        public void writeString8(Parcel p, String s) {
            nativeWriteString(p.mNativePtr, s);
            p.writeString8NoHelper(s);
        }

        public void writeString16(Parcel p, String s) {
            p.writeString16NoHelper(s);
        }
        }


        /**
        /**
@@ -395,8 +403,12 @@ public final class Parcel {
         * must use {@link #readStringNoHelper()} to avoid
         * must use {@link #readStringNoHelper()} to avoid
         * infinity recursive calls.
         * infinity recursive calls.
         */
         */
        public String readString(Parcel p) {
        public String readString8(Parcel p) {
            return nativeReadString(p.mNativePtr);
            return p.readString8NoHelper();
        }

        public String readString16(Parcel p) {
            return p.readString16NoHelper();
        }
        }
    }
    }


@@ -759,7 +771,17 @@ public final class Parcel {
     * growing dataCapacity() if needed.
     * growing dataCapacity() if needed.
     */
     */
    public final void writeString(@Nullable String val) {
    public final void writeString(@Nullable String val) {
        mReadWriteHelper.writeString(this, val);
        writeString16(val);
    }

    /** {@hide} */
    public final void writeString8(@Nullable String val) {
        mReadWriteHelper.writeString8(this, val);
    }

    /** {@hide} */
    public final void writeString16(@Nullable String val) {
        mReadWriteHelper.writeString16(this, val);
    }
    }


    /**
    /**
@@ -770,7 +792,17 @@ public final class Parcel {
     * @hide
     * @hide
     */
     */
    public void writeStringNoHelper(@Nullable String val) {
    public void writeStringNoHelper(@Nullable String val) {
        nativeWriteString(mNativePtr, val);
        writeString16NoHelper(val);
    }

    /** {@hide} */
    public void writeString8NoHelper(@Nullable String val) {
        nativeWriteString8(mNativePtr, val);
    }

    /** {@hide} */
    public void writeString16NoHelper(@Nullable String val) {
        nativeWriteString16(mNativePtr, val);
    }
    }


    /**
    /**
@@ -2337,7 +2369,17 @@ public final class Parcel {
     */
     */
    @Nullable
    @Nullable
    public final String readString() {
    public final String readString() {
        return mReadWriteHelper.readString(this);
        return readString16();
    }

    /** {@hide} */
    public final @Nullable String readString8() {
        return mReadWriteHelper.readString8(this);
    }

    /** {@hide} */
    public final @Nullable String readString16() {
        return mReadWriteHelper.readString16(this);
    }
    }


    /**
    /**
@@ -2347,9 +2389,18 @@ public final class Parcel {
     *
     *
     * @hide
     * @hide
     */
     */
    @Nullable
    public @Nullable String readStringNoHelper() {
    public String readStringNoHelper() {
        return readString16NoHelper();
        return nativeReadString(mNativePtr);
    }

    /** {@hide} */
    public @Nullable String readString8NoHelper() {
        return nativeReadString8(mNativePtr);
    }

    /** {@hide} */
    public @Nullable String readString16NoHelper() {
        return nativeReadString16(mNativePtr);
    }
    }


    /**
    /**
+43 −4
Original line number Original line Diff line number Diff line
@@ -273,7 +273,28 @@ static void android_os_Parcel_writeDouble(JNIEnv* env, jclass clazz, jlong nativ
    }
    }
}
}


static void android_os_Parcel_writeString(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
static void android_os_Parcel_writeString8(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        status_t err = NO_MEMORY;
        if (val) {
            const size_t len = env->GetStringUTFLength(val);
            const char* str = env->GetStringUTFChars(val, 0);
            if (str) {
                err = parcel->writeString8(str, len);
                env->ReleaseStringUTFChars(val, str);
            }
        } else {
            err = parcel->writeString8(NULL, 0);
        }
        if (err != NO_ERROR) {
            signalExceptionForError(env, clazz, err);
        }
    }
}

static void android_os_Parcel_writeString16(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
{
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
    if (parcel != NULL) {
@@ -444,7 +465,21 @@ static jdouble android_os_Parcel_readDouble(jlong nativePtr)
    return 0;
    return 0;
}
}


static jstring android_os_Parcel_readString(JNIEnv* env, jclass clazz, jlong nativePtr)
static jstring android_os_Parcel_readString8(JNIEnv* env, jclass clazz, jlong nativePtr)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        size_t len;
        const char* str = parcel->readString8Inplace(&len);
        if (str) {
            return env->NewStringUTF(str);
        }
        return NULL;
    }
    return NULL;
}

static jstring android_os_Parcel_readString16(JNIEnv* env, jclass clazz, jlong nativePtr)
{
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
    if (parcel != NULL) {
@@ -722,7 +757,9 @@ static const JNINativeMethod gParcelMethods[] = {
    // @FastNative
    // @FastNative
    {"nativeWriteDouble",         "(JD)V", (void*)android_os_Parcel_writeDouble},
    {"nativeWriteDouble",         "(JD)V", (void*)android_os_Parcel_writeDouble},
    // @FastNative
    // @FastNative
    {"nativeWriteString",         "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
    {"nativeWriteString8",        "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString8},
    // @FastNative
    {"nativeWriteString16",       "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString16},
    // @FastNative
    // @FastNative
    {"nativeWriteStrongBinder",   "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
    {"nativeWriteStrongBinder",   "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
    // @FastNative
    // @FastNative
@@ -740,7 +777,9 @@ static const JNINativeMethod gParcelMethods[] = {
    // @CriticalNative
    // @CriticalNative
    {"nativeReadDouble",          "(J)D", (void*)android_os_Parcel_readDouble},
    {"nativeReadDouble",          "(J)D", (void*)android_os_Parcel_readDouble},
    // @FastNative
    // @FastNative
    {"nativeReadString",          "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString},
    {"nativeReadString8",         "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString8},
    // @FastNative
    {"nativeReadString16",        "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString16},
    // @FastNative
    // @FastNative
    {"nativeReadStrongBinder",    "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
    {"nativeReadStrongBinder",    "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
    // @FastNative
    // @FastNative
+72 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.os;

import com.google.caliper.AfterExperiment;
import com.google.caliper.BeforeExperiment;
import com.google.caliper.Param;

public class ParcelStringBenchmark {

    @Param({"com.example.typical_package_name", "從不喜歡孤單一個 - 蘇永康/吳雨霏"})
    String mValue;

    private Parcel mParcel;

    @BeforeExperiment
    protected void setUp() {
        mParcel = Parcel.obtain();
    }

    @AfterExperiment
    protected void tearDown() {
        mParcel.recycle();
        mParcel = null;
    }

    public void timeWriteString8(int reps) {
        for (int i = 0; i < reps; i++) {
            mParcel.setDataPosition(0);
            mParcel.writeString8(mValue);
        }
    }

    public void timeReadString8(int reps) {
        mParcel.writeString8(mValue);

        for (int i = 0; i < reps; i++) {
            mParcel.setDataPosition(0);
            mParcel.readString8();
        }
    }

    public void timeWriteString16(int reps) {
        for (int i = 0; i < reps; i++) {
            mParcel.setDataPosition(0);
            mParcel.writeString16(mValue);
        }
    }

    public void timeReadString16(int reps) {
        mParcel.writeString16(mValue);

        for (int i = 0; i < reps; i++) {
            mParcel.setDataPosition(0);
            mParcel.readString16();
        }
    }
}
+23 −0
Original line number Original line Diff line number Diff line
@@ -87,4 +87,27 @@ public class ParcelTest {


        p.recycle();
        p.recycle();
    }
    }

    /**
     * Verify that writing/reading UTF-8 and UTF-16 strings works well.
     */
    @Test
    public void testStrings() {
        final String[] strings = {
                null, "", "abc\0def", "com.example.typical_package_name",
                "從不喜歡孤單一個 - 蘇永康/吳雨霏", "example"
        };

        final Parcel p = Parcel.obtain();
        for (String string : strings) {
            p.writeString8(string);
            p.writeString16(string);
        }

        p.setDataPosition(0);
        for (String string : strings) {
            assertEquals(string, p.readString8());
            assertEquals(string, p.readString16());
        }
    }
}
}