Improve libcore_util_CharsetUtils performance.
Use ScopedFastNativeObjectAccess for @FastNative methods.
Avoid expensive JNI calls by using mirror::ByteArray.
For compressed strings, just copy the ASCII data.
For uncompressed strings, pre-calculate the UTF-8 length
to avoid unnecessary reallocations. Also access 16-bit
characters directly instead of using String::CharAt() to
avoid unnecessary string compression checks that clang++
is unable to optimize away.
The results for StringToBytesBenchmark on blueline little
cores running at fixed frequency 1420800 are approximately
(medians from 3 runs) before after
timeGetBytesAscii EMPTY 1599.86 519.36
timeGetBytesAscii L_16 1849.31 535.59
timeGetBytesAscii L_64 2582.72 646.07
timeGetBytesAscii L_256 5566.70 1132.11
timeGetBytesAscii L_512 9585.88 1649.34
timeGetBytesAscii A_16 1840.06 540.05
timeGetBytesAscii A_64 2550.41 614.85
timeGetBytesAscii A_256 5382.15 919.59
timeGetBytesAscii A_512 9181.93 1226.82
timeGetBytesIso88591 EMPTY 1589.57 515.62
timeGetBytesIso88591 L_16 1835.09 535.58
timeGetBytesIso88591 L_64 2588.90 650.84
timeGetBytesIso88591 L_256 5585.69 1118.37
timeGetBytesIso88591 L_512 9635.12 1625.92
timeGetBytesIso88591 A_16 1827.21 529.83
timeGetBytesIso88591 A_64 2548.83 603.32
timeGetBytesIso88591 A_256 5356.75 916.76
timeGetBytesIso88591 A_512 9172.74 1224.04
timeGetBytesUtf8 EMPTY 1599.00 510.61
timeGetBytesUtf8 L_16 1876.05 632.55
timeGetBytesUtf8 L_64 2781.85 1054.06
timeGetBytesUtf8 L_256 12136.15 3708.94
timeGetBytesUtf8 L_512 21357.30 7811.28
timeGetBytesUtf8 A_16 1888.64 531.15
timeGetBytesUtf8 A_64 2785.70 598.75
timeGetBytesUtf8 A_256 6300.25 906.34
timeGetBytesUtf8 A_512 11074.56 1231.62
Test: run-libcore-tests.sh --mode=host
Bug: 170281727
Change-Id: I03d2420b2e1eefc1fa5232deddba593aebd51941
diff --git a/runtime/native/libcore_util_CharsetUtils.cc b/runtime/native/libcore_util_CharsetUtils.cc
index 95e0d79..f0764f1 100644
--- a/runtime/native/libcore_util_CharsetUtils.cc
+++ b/runtime/native/libcore_util_CharsetUtils.cc
@@ -30,87 +30,6 @@
namespace art {
-/**
- * Approximates java.lang.UnsafeByteSequence so we don't have to pay the cost of calling back into
- * Java when converting a char[] to a UTF-8 byte[]. This lets us have UTF-8 conversions slightly
- * faster than ICU for large char[]s without paying for the NIO overhead with small char[]s.
- *
- * We could avoid this by keeping the UTF-8 bytes on the native heap until we're done and only
- * creating a byte[] on the Java heap when we know how big it needs to be, but one shouldn't lie
- * to the garbage collector (nor hide potentially large allocations from it).
- *
- * Because a call to append might require an allocation, it might fail. Callers should always
- * check the return value of append.
- */
-class NativeUnsafeByteSequence {
- public:
- explicit NativeUnsafeByteSequence(JNIEnv* env)
- : mEnv(env), mJavaArray(nullptr), mRawArray(nullptr), mSize(-1), mOffset(0) {
- }
-
- ~NativeUnsafeByteSequence() {
- // Release our pointer to the raw array, copying changes back to the Java heap.
- if (mRawArray != nullptr) {
- mEnv->ReleaseByteArrayElements(mJavaArray, mRawArray, 0);
- }
- }
-
- bool append(jbyte b) {
- if (mOffset == mSize && !resize(mSize * 2)) {
- return false;
- }
- mRawArray[mOffset++] = b;
- return true;
- }
-
- bool resize(int newSize) {
- if (newSize == mSize) {
- return true;
- }
-
- // Allocate a new array.
- jbyteArray newJavaArray = mEnv->NewByteArray(newSize);
- if (newJavaArray == nullptr) {
- return false;
- }
- jbyte* newRawArray = mEnv->GetByteArrayElements(newJavaArray, nullptr);
- if (newRawArray == nullptr) {
- return false;
- }
-
- // Copy data out of the old array and then let go of it.
- // Note that we may be trimming the array.
- if (mRawArray != nullptr) {
- memcpy(newRawArray, mRawArray, mOffset);
- mEnv->ReleaseByteArrayElements(mJavaArray, mRawArray, JNI_ABORT);
- mEnv->DeleteLocalRef(mJavaArray);
- }
-
- // Point ourselves at the new array.
- mJavaArray = newJavaArray;
- mRawArray = newRawArray;
- mSize = newSize;
- return true;
- }
-
- jbyteArray toByteArray() {
- // Trim any unused space, if necessary.
- bool okay = resize(mOffset);
- return okay ? mJavaArray : nullptr;
- }
-
- private:
- JNIEnv* mEnv;
- jbyteArray mJavaArray;
- jbyte* mRawArray;
- jint mSize;
- jint mOffset;
-
- // Disallow copy and assignment.
- NativeUnsafeByteSequence(const NativeUnsafeByteSequence&);
- void operator=(const NativeUnsafeByteSequence&);
-};
-
static void CharsetUtils_asciiBytesToChars(JNIEnv* env, jclass, jbyteArray javaBytes, jint offset,
jint length, jcharArray javaChars) {
ScopedByteArrayRO bytes(env, javaBytes);
@@ -156,29 +75,30 @@
*/
static jbyteArray charsToBytes(JNIEnv* env, jstring java_string, jint offset, jint length,
jchar maxValidChar) {
- ScopedObjectAccess soa(env);
+ ScopedFastNativeObjectAccess soa(env);
StackHandleScope<1> hs(soa.Self());
Handle<mirror::String> string(hs.NewHandle(soa.Decode<mirror::String>(java_string)));
if (string == nullptr) {
return nullptr;
}
- jbyteArray javaBytes = env->NewByteArray(length);
- ScopedByteArrayRW bytes(env, javaBytes);
- if (bytes.get() == nullptr) {
+ ObjPtr<mirror::ByteArray> result = mirror::ByteArray::Alloc(soa.Self(), length);
+ if (result == nullptr) {
return nullptr;
}
- jbyte* dst = &bytes[0];
- for (int i = 0; i < length; ++i) {
- jchar ch = string->CharAt(offset + i);
- if (ch > maxValidChar) {
- ch = '?';
- }
- *dst++ = static_cast<jbyte>(ch);
+ if (string->IsCompressed()) {
+ // All characters in a compressed string are ASCII and therefore do not need a replacement.
+ DCHECK_GE(maxValidChar, 0x7f);
+ memcpy(result->GetData(), string->GetValueCompressed() + offset, length);
+ } else {
+ const uint16_t* src = string->GetValue() + offset;
+ auto clamp = [maxValidChar](uint16_t c) {
+ return static_cast<jbyte>(dchecked_integral_cast<uint8_t>((c > maxValidChar) ? '?' : c));
+ };
+ std::transform(src, src + length, result->GetData(), clamp);
}
-
- return javaBytes;
+ return soa.AddLocalReference<jbyteArray>(result);
}
static jbyteArray CharsetUtils_toAsciiBytes(JNIEnv* env, jclass, jstring java_string, jint offset,
@@ -193,63 +113,74 @@
static jbyteArray CharsetUtils_toUtf8Bytes(JNIEnv* env, jclass, jstring java_string, jint offset,
jint length) {
- ScopedObjectAccess soa(env);
+ ScopedFastNativeObjectAccess soa(env);
StackHandleScope<1> hs(soa.Self());
Handle<mirror::String> string(hs.NewHandle(soa.Decode<mirror::String>(java_string)));
if (string == nullptr) {
return nullptr;
}
- NativeUnsafeByteSequence out(env);
- if (!out.resize(length)) {
+ DCHECK_GE(offset, 0);
+ DCHECK_LE(offset, string->GetLength());
+ DCHECK_GE(length, 0);
+ DCHECK_LE(length, string->GetLength() - offset);
+
+ auto visit_chars16 = [string, offset, length](auto append) REQUIRES_SHARED(Locks::mutator_lock_) {
+ const uint16_t* chars16 = string->GetValue() + offset;
+ for (int i = 0; i < length; ++i) {
+ jint ch = chars16[i];
+ if (ch < 0x80) {
+ // One byte.
+ append(ch);
+ } else if (ch < 0x800) {
+ // Two bytes.
+ append((ch >> 6) | 0xc0);
+ append((ch & 0x3f) | 0x80);
+ } else if (U16_IS_SURROGATE(ch)) {
+ // A supplementary character.
+ jchar high = static_cast<jchar>(ch);
+ jchar low = (i + 1 != length) ? chars16[i + 1] : 0;
+ if (!U16_IS_SURROGATE_LEAD(high) || !U16_IS_SURROGATE_TRAIL(low)) {
+ append('?');
+ continue;
+ }
+ // Now we know we have a *valid* surrogate pair, we can consume the low surrogate.
+ ++i;
+ ch = U16_GET_SUPPLEMENTARY(high, low);
+ // Four bytes.
+ append((ch >> 18) | 0xf0);
+ append(((ch >> 12) & 0x3f) | 0x80);
+ append(((ch >> 6) & 0x3f) | 0x80);
+ append((ch & 0x3f) | 0x80);
+ } else {
+ // Three bytes.
+ append((ch >> 12) | 0xe0);
+ append(((ch >> 6) & 0x3f) | 0x80);
+ append((ch & 0x3f) | 0x80);
+ }
+ }
+ };
+
+ bool compressed = string->IsCompressed();
+ size_t utf8_length = 0;
+ if (compressed) {
+ utf8_length = length;
+ } else {
+ visit_chars16([&utf8_length](jbyte c ATTRIBUTE_UNUSED) { ++utf8_length; });
+ }
+ ObjPtr<mirror::ByteArray> result =
+ mirror::ByteArray::Alloc(soa.Self(), dchecked_integral_cast<int32_t>(utf8_length));
+ if (result == nullptr) {
return nullptr;
}
- const int end = offset + length;
- for (int i = offset; i < end; ++i) {
- jint ch = string->CharAt(i);
- if (ch < 0x80) {
- // One byte.
- if (!out.append(ch)) {
- return nullptr;
- }
- } else if (ch < 0x800) {
- // Two bytes.
- if (!out.append((ch >> 6) | 0xc0) || !out.append((ch & 0x3f) | 0x80)) {
- return nullptr;
- }
- } else if (U16_IS_SURROGATE(ch)) {
- // A supplementary character.
- jchar high = static_cast<jchar>(ch);
- jchar low = (i + 1 != end) ? string->CharAt(i + 1) : 0;
- if (!U16_IS_SURROGATE_LEAD(high) || !U16_IS_SURROGATE_TRAIL(low)) {
- if (!out.append('?')) {
- return nullptr;
- }
- continue;
- }
- // Now we know we have a *valid* surrogate pair, we can consume the low surrogate.
- ++i;
- ch = U16_GET_SUPPLEMENTARY(high, low);
- // Four bytes.
- jbyte b1 = (ch >> 18) | 0xf0;
- jbyte b2 = ((ch >> 12) & 0x3f) | 0x80;
- jbyte b3 = ((ch >> 6) & 0x3f) | 0x80;
- jbyte b4 = (ch & 0x3f) | 0x80;
- if (!out.append(b1) || !out.append(b2) || !out.append(b3) || !out.append(b4)) {
- return nullptr;
- }
- } else {
- // Three bytes.
- jbyte b1 = (ch >> 12) | 0xe0;
- jbyte b2 = ((ch >> 6) & 0x3f) | 0x80;
- jbyte b3 = (ch & 0x3f) | 0x80;
- if (!out.append(b1) || !out.append(b2) || !out.append(b3)) {
- return nullptr;
- }
- }
+ if (compressed) {
+ memcpy(result->GetData(), string->GetValueCompressed() + offset, length);
+ } else {
+ int8_t* data = result->GetData();
+ visit_chars16([&data](jbyte c) { *data++ = c; });
}
- return out.toByteArray();
+ return soa.AddLocalReference<jbyteArray>(result);
}
static JNINativeMethod gMethods[] = {