From fb3beb0591aed9fd6bf349eb5d08e0e485bcff08 Mon Sep 17 00:00:00 2001 From: Florin Malita Date: Mon, 18 Jun 2018 22:25:31 -0400 Subject: [skjson] More short string micro-optimizations When possible, load 8 chars and mask out the tag and \0 terminator. ~19% faster on a Z840, ~5% faster on a PixelC. Change-Id: I12d4b7d86c92c887b00f5d2480d3ff05a7042df1 Reviewed-on: https://skia-review.googlesource.com/135624 Commit-Queue: Florin Malita Reviewed-by: Mike Klein --- modules/skjson/src/SkJSON.cpp | 86 +++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 19 deletions(-) (limited to 'modules') diff --git a/modules/skjson/src/SkJSON.cpp b/modules/skjson/src/SkJSON.cpp index 23a3d14c9c..2548f4ef41 100644 --- a/modules/skjson/src/SkJSON.cpp +++ b/modules/skjson/src/SkJSON.cpp @@ -109,25 +109,73 @@ ArrayValue::ArrayValue(const Value* src, size_t size, SkArenaAlloc& alloc) { // // The string data plus a null-char terminator are copied over. // -StringValue::StringValue(const char* src, size_t size, SkArenaAlloc& alloc) { +namespace { + +// An internal string builder with a fast 8 byte short string load path +// (for the common case where the string is not at the end of the stream). +class FastString final : public Value { +public: + FastString(const char* src, size_t size, const char* eos, SkArenaAlloc& alloc) { + SkASSERT(src <= eos); + + if (size > kMaxInlineStringSize) { + this->initLongString(src, size, alloc); + SkASSERT(this->getTag() == Tag::kString); + return; + } + + static_assert(static_cast(Tag::kShortString) == 0, "please don't break this"); + static_assert(sizeof(Value) == 8, ""); + + // TODO: LIKELY + if (src + 7 <= eos) { + this->initFastShortString(src, size); + } else { + this->initShortString(src, size); + } + + SkASSERT(this->getTag() == Tag::kShortString); + } + +private: static constexpr size_t kMaxInlineStringSize = sizeof(Value) - 1; - if (size > kMaxInlineStringSize) { + + void initLongString(const char* src, size_t size, SkArenaAlloc& alloc) { + SkASSERT(size > kMaxInlineStringSize); + this->init_tagged_pointer(Tag::kString, MakeVector(src, size, alloc)); auto* data = this->cast>()->begin(); const_cast(data)[size] = '\0'; - SkASSERT(this->getTag() == Tag::kString); - return; } - this->init_tagged(Tag::kShortString); - sk_careful_memcpy(this->cast(), src, size); + void initShortString(const char* src, size_t size) { + SkASSERT(size <= kMaxInlineStringSize); + + this->init_tagged(Tag::kShortString); + sk_careful_memcpy(this->cast(), src, size); + // Null terminator provided by init_tagged() above (fData8 is zero-initialized). + } + + void initFastShortString(const char* src, size_t size) { + SkASSERT(size <= kMaxInlineStringSize); + + // Load 8 chars and mask out the tag and \0 terminator. + uint64_t* s64 = this->cast(); + memcpy(s64, src, 8); - // Null terminator provided by init_tagged() above (fData8 is zero-initialized). - // This is safe because kShortString is also 0 and can act as a terminator when size == 7. - static_assert(static_cast(Tag::kShortString) == 0, "please don't break this"); +#if defined(SK_CPU_LENDIAN) + *s64 &= 0x00ffffffffffffffULL >> ((kMaxInlineStringSize - size) * 8); +#else + static_assert(false, "Big-endian builds are not supported at this time."); +#endif + } +}; - SkASSERT(this->getTag() == Tag::kShortString); +} // namespace + +StringValue::StringValue(const char* src, size_t size, SkArenaAlloc& alloc) { + new (this) FastString(src, size, src, alloc); } ObjectValue::ObjectValue(const Member* src, size_t size, SkArenaAlloc& alloc) { @@ -269,8 +317,8 @@ public: p = skip_ws(p); if (*p != '"') return this->error(NullValue(), p, "expected object key"); - p = this->matchString(p, p_stop, [this](const char* key, size_t size) { - this->pushObjectKey(key, size); + p = this->matchString(p, p_stop, [this](const char* key, size_t size, const char* eos) { + this->pushObjectKey(key, size, eos); }); if (!p) return NullValue(); @@ -287,8 +335,8 @@ public: case '\0': return this->error(NullValue(), p, "unexpected input end"); case '"': - p = this->matchString(p, p_stop, [this](const char* str, size_t size) { - this->pushString(str, size); + p = this->matchString(p, p_stop, [this](const char* str, size_t size, const char* eos) { + this->pushString(str, size, eos); }); break; case '[': @@ -469,11 +517,11 @@ private: ) } - void pushObjectKey(const char* key, size_t size) { + void pushObjectKey(const char* key, size_t size, const char* eos) { SkASSERT(fScopeStack.back() >= 0); SkASSERT(fValueStack.size() >= SkTo(fScopeStack.back())); SkASSERT(!((fValueStack.size() - SkTo(fScopeStack.back())) & 1)); - this->pushString(key, size); + this->pushString(key, size, eos); } void pushTrue() { @@ -488,8 +536,8 @@ private: fValueStack.push_back(NullValue()); } - void pushString(const char* s, size_t size) { - fValueStack.push_back(StringValue(s, size, fAlloc)); + void pushString(const char* s, size_t size, const char* eos) { + fValueStack.push_back(FastString(s, size, eos, fAlloc)); } void pushInt32(int32_t i) { @@ -555,7 +603,7 @@ private: if (*p == '"') { // Valid string found. - func(s_begin, p - s_begin); + func(s_begin, p - s_begin, p_stop); return p + 1; } -- cgit v1.2.3