[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 <fmalita@chromium.org>
Reviewed-by: Mike Klein <mtklein@google.com>
This commit is contained in:
Florin Malita 2018-06-18 22:25:31 -04:00 committed by Skia Commit-Bot
parent 74aa9ab51a
commit fb3beb0591

View File

@ -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<uint8_t>(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<char, 1>(src, size, alloc));
auto* data = this->cast<VectorValue<char, Value::Type::kString>>()->begin();
const_cast<char*>(data)[size] = '\0';
SkASSERT(this->getTag() == Tag::kString);
return;
}
this->init_tagged(Tag::kShortString);
sk_careful_memcpy(this->cast<char>(), src, size);
void initShortString(const char* src, size_t size) {
SkASSERT(size <= kMaxInlineStringSize);
// 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<uint8_t>(Tag::kShortString) == 0, "please don't break this");
this->init_tagged(Tag::kShortString);
sk_careful_memcpy(this->cast<char>(), src, size);
// Null terminator provided by init_tagged() above (fData8 is zero-initialized).
}
SkASSERT(this->getTag() == Tag::kShortString);
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<uint64_t>();
memcpy(s64, src, 8);
#if defined(SK_CPU_LENDIAN)
*s64 &= 0x00ffffffffffffffULL >> ((kMaxInlineStringSize - size) * 8);
#else
static_assert(false, "Big-endian builds are not supported at this time.");
#endif
}
};
} // 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<size_t>(fScopeStack.back()));
SkASSERT(!((fValueStack.size() - SkTo<size_t>(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;
}