From 6a5c7085bcebe263ac43486f1825407580662924 Mon Sep 17 00:00:00 2001 From: mtklein Date: Wed, 17 Sep 2014 12:21:59 -0700 Subject: [PATCH] Add SkMiniData. This is a bit like a limited SkData, geared to store really tiny byte strings. This is not hooked up anywhere beyond the new unit test. I did experimentally plumb it into SkRecord for drawPosTextH: just over 40% of drawPosTextH calls in our repo can fit into ShortData. BUG=skia: R=reed@google.com, mtklein@google.com Author: mtklein@chromium.org Review URL: https://codereview.chromium.org/573323002 --- gyp/core.gypi | 1 + gyp/tests.gypi | 1 + src/core/SkMiniData.cpp | 80 +++++++++++++++++++++++++++++++++++++++++ src/core/SkMiniData.h | 24 +++++++++++++ tests/MiniDataTest.cpp | 16 +++++++++ 5 files changed, 122 insertions(+) create mode 100644 src/core/SkMiniData.cpp create mode 100644 src/core/SkMiniData.h create mode 100644 tests/MiniDataTest.cpp diff --git a/gyp/core.gypi b/gyp/core.gypi index a2fd438bc1..c05fa48c75 100644 --- a/gyp/core.gypi +++ b/gyp/core.gypi @@ -116,6 +116,7 @@ '<(skia_src_path)/core/SkMatrix.cpp', '<(skia_src_path)/core/SkMessageBus.h', '<(skia_src_path)/core/SkMetaData.cpp', + '<(skia_src_path)/core/SkMiniData.cpp', '<(skia_src_path)/core/SkMipMap.cpp', '<(skia_src_path)/core/SkMultiPictureDraw.cpp', '<(skia_src_path)/core/SkPackBits.cpp', diff --git a/gyp/tests.gypi b/gyp/tests.gypi index f33d864f3c..a3197220ef 100644 --- a/gyp/tests.gypi +++ b/gyp/tests.gypi @@ -139,6 +139,7 @@ '../tests/MemsetTest.cpp', '../tests/MessageBusTest.cpp', '../tests/MetaDataTest.cpp', + '../tests/MiniDataTest.cpp', '../tests/MipMapTest.cpp', '../tests/NameAllocatorTest.cpp', '../tests/OSPathTest.cpp', diff --git a/src/core/SkMiniData.cpp b/src/core/SkMiniData.cpp new file mode 100644 index 0000000000..ded62d1687 --- /dev/null +++ b/src/core/SkMiniData.cpp @@ -0,0 +1,80 @@ +#include "SkMiniData.h" + +namespace { + +// SkMiniData::fRep either stores a LongData* or is punned into a ShortData. +// We use the low bits to distinguish the two: all pointers from malloc are at +// least 8-byte aligned, leaving those low bits clear when it's a LongData*. + +static bool is_long(uint64_t rep) { + // Even on 32-bit machines, we require the bottom 3 bits from malloc'd pointers are clear. + // If any of those bottom 3 bits are set, it's from a ShortData's len. And if no bits are + // set anywhere, it's an empty SkMiniData, which also follows the ShortData path. + return rep && SkIsAlign8(rep); +} + +// Can be used for any length, but we always use it for >=8. +struct LongData { + size_t len; + uint8_t data[8]; // There are actually len >= 8 bytes here. + + static uint64_t Create(const void* data, size_t len) { + SkASSERT(len > 7); + LongData* s = (LongData*)sk_malloc_throw(sizeof(size_t) + len); + s->len = len; + memcpy(s->data, data, len); + + uint64_t rep = reinterpret_cast(s); + SkASSERT(is_long(rep)); + return rep; + } +}; + +// At most 7 bytes fit, but never mallocs. +struct ShortData { + // Order matters here. len must align with the least signficant bits of a pointer. +#ifdef SK_CPU_LENDIAN + uint8_t len; + uint8_t data[7]; +#else // Warning! Only the little-endian path has been tested. + uint8_t data[7]; + uint8_t len; +#endif + + static uint64_t Create(const void* data, size_t len) { + SkASSERT(len <= 7); +#ifdef SK_CPU_LENDIAN + ShortData s = { (uint8_t)len, {0, 0, 0, 0, 0, 0, 0} }; +#else // Warning! Only the little-endian path has been tested. + ShortData s = { {0, 0, 0, 0, 0, 0, 0}, (uint8_t)len }; +#endif + memcpy(s.data, data, len); + return *reinterpret_cast(&s); + } +}; + +} // namespace + +SkMiniData::SkMiniData(const void* data, size_t len) + : fRep(len <= 7 ? ShortData::Create(data, len) + : LongData::Create(data, len)) {} + +SkMiniData::SkMiniData(const SkMiniData& s) + : fRep(s.len() <= 7 ? ShortData::Create(s.data(), s.len()) + : LongData::Create(s.data(), s.len())) {} + +SkMiniData::~SkMiniData() { + if (is_long(fRep)) { + sk_free(reinterpret_cast(fRep)); + } +} + +const void* SkMiniData::data() const { + return is_long(fRep) ? reinterpret_cast( fRep)->data + : reinterpret_cast(&fRep)->data; +} + +size_t SkMiniData::len() const { + return is_long(fRep) ? reinterpret_cast( fRep)->len + : reinterpret_cast(&fRep)->len; +} diff --git a/src/core/SkMiniData.h b/src/core/SkMiniData.h new file mode 100644 index 0000000000..fd22cea9ca --- /dev/null +++ b/src/core/SkMiniData.h @@ -0,0 +1,24 @@ +#ifndef SkMiniData_DEFINED +#define SkMiniData_DEFINED + +// A class that can store any immutable byte string, +// but optimized to store <=7 bytes. + +#include "SkTypes.h" + +class SkMiniData { +public: + SkMiniData(const void*, size_t); + SkMiniData(const SkMiniData&); + ~SkMiniData(); + + const void* data() const; + size_t len() const; + +private: + SkMiniData& operator=(const SkMiniData&); + + const uint64_t fRep; +}; + +#endif//SkMiniData_DEFINED diff --git a/tests/MiniDataTest.cpp b/tests/MiniDataTest.cpp new file mode 100644 index 0000000000..cb656b3e5a --- /dev/null +++ b/tests/MiniDataTest.cpp @@ -0,0 +1,16 @@ +#include "SkMiniData.h" +#include "Test.h" + +DEF_TEST(MiniData, r) { + static const char* s = "abcdefghijklmnopqrstuvwxyz"; + + for (size_t len = 0; len <= 26; len++) { + SkMiniData md(s, len); + REPORTER_ASSERT(r, md.len() == len); + REPORTER_ASSERT(r, 0 == memcmp(md.data(), s, len)); + + SkMiniData copy(md); + REPORTER_ASSERT(r, copy.len() == len); + REPORTER_ASSERT(r, 0 == memcmp(copy.data(), s, len)); + } +}