From fbbc5a6b361c623e47a433f83e7200b4e2ba3501 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Thu, 5 Jan 2017 20:44:14 +0900 Subject: [PATCH] Make SparseBitSet serializable. To share the calculated coverage information across the processes, make SparseBitSet serializable. Bug: 34042446 Test: minikin_tests passes Change-Id: I0463138adcf234739bb3ce1cdadf382021921f3e --- include/minikin/FontFamily.h | 17 ++++ include/minikin/SparseBitSet.h | 25 ++++- libs/minikin/FontFamily.cpp | 24 +++++ libs/minikin/SparseBitSet.cpp | 106 +++++++++++++++++---- tests/perftests/Android.mk | 1 + tests/perftests/FontFamily.cpp | 54 +++++++++++ tests/unittest/Android.mk | 1 + tests/unittest/SparseBitSetTest.cpp | 140 ++++++++++++++++++++++++++++ 8 files changed, 348 insertions(+), 20 deletions(-) create mode 100644 tests/perftests/FontFamily.cpp create mode 100644 tests/unittest/SparseBitSetTest.cpp diff --git a/include/minikin/FontFamily.h b/include/minikin/FontFamily.h index c7018a6f8c0..4186c2baabb 100644 --- a/include/minikin/FontFamily.h +++ b/include/minikin/FontFamily.h @@ -128,8 +128,23 @@ public: FontFamily(int variant, std::vector&& fonts); FontFamily(uint32_t langId, int variant, std::vector&& fonts); + // The accelerator table won't be copied. Do not release the memory until the created FontFamily + // is destructed. + FontFamily(std::vector&& fonts, const uint8_t* acceleratorTable, size_t tableSize); + FontFamily(int variant, std::vector&& fonts, const uint8_t* acceleratorTable, + size_t tableSize); + FontFamily(uint32_t langId, int variant, std::vector&& fonts, + const uint8_t* acceleratorTable, size_t tableSize); + ~FontFamily(); + // Writes internal accelerator tables into the 'out' buffer. + // + // This method returns the number of bytes written to the buffer. By calling the method with + // 'out' set to nullptr, the method just returns the size needed, which the caller can then use + // for allocating a buffer for a second call. + size_t writeAcceleratorTable(uint8_t* out) const; + // TODO: Good to expose FontUtil.h. static bool analyzeStyle(const std::shared_ptr& typeface, int* weight, bool* italic); @@ -164,6 +179,8 @@ public: private: void computeCoverage(); + void readAcceleratorTable(const uint8_t* data, size_t size); + uint32_t mLangId; int mVariant; std::vector mFonts; diff --git a/include/minikin/SparseBitSet.h b/include/minikin/SparseBitSet.h index 2552d14d0b7..491e68ae7fb 100644 --- a/include/minikin/SparseBitSet.h +++ b/include/minikin/SparseBitSet.h @@ -34,7 +34,7 @@ namespace minikin { class SparseBitSet { public: - SparseBitSet(): mMaxVal(0) { + SparseBitSet(): mMaxVal(0), mOwnIndicesAndBitmaps(false) { } // Clear the set @@ -45,10 +45,21 @@ public: // inclusive of start, exclusive of end, laid out in a uint32 array. void initFromRanges(const uint32_t* ranges, size_t nRanges); + // Initializes the set with pre-calculted data. Returns false if the serialized data is invalid. + // Even if this function returns false, the internal data is cleared. + bool initFromBuffer(const uint8_t* data, size_t size); + + // Serialize the set and write into out. + // + // This method returns the number of bytes written to the buffer. By calling the method with + // 'out' set to nullptr, the method just returns the size needed, which the caller can then use + // for allocating a buffer for a second call. + size_t writeToBuffer(uint8_t* out) const; + // Determine whether the value is included in the set bool get(uint32_t ch) const { if (ch >= mMaxVal) return false; - uint32_t *bitmap = &mBitmaps[mIndices[ch >> kLogValuesPerPage]]; + const uint32_t *bitmap = &mBitmaps[mIndices[ch >> kLogValuesPerPage]]; uint32_t index = ch & kPageMask; return (bitmap[index >> kLogBitsPerEl] & (kElFirst >> (index & kElMask))) != 0; } @@ -80,8 +91,14 @@ private: static int CountLeadingZeros(element x); uint32_t mMaxVal; - std::unique_ptr mIndices; - std::unique_ptr mBitmaps; + + // True if this SparseBitSet is responsible for freeing mIndices and mBitamps. + bool mOwnIndicesAndBitmaps; + + uint32_t mIndexSize; + const uint32_t* mIndices; + uint32_t mBitmapSize; + const element* mBitmaps; uint32_t mZeroPageIndex; }; diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp index d0a0138ef2d..4097e8fb239 100644 --- a/libs/minikin/FontFamily.cpp +++ b/libs/minikin/FontFamily.cpp @@ -112,6 +112,22 @@ FontFamily::FontFamily(uint32_t langId, int variant, std::vector&& fonts) computeCoverage(); } +FontFamily::FontFamily(std::vector&& fonts, const uint8_t* acceleratorTable, size_t tableSize) + : FontFamily(0 /* variant */, std::move(fonts), acceleratorTable, tableSize) { +} + +FontFamily::FontFamily(int variant, std::vector&& fonts, const uint8_t* acceleratorTable, + size_t tableSize) + : FontFamily(FontLanguageListCache::kEmptyListId, variant, std::move(fonts), acceleratorTable, + tableSize) { +} + +FontFamily::FontFamily(uint32_t langId, int variant, std::vector&& fonts, + const uint8_t* acceleratorTable, size_t tableSize) + : mLangId(langId), mVariant(variant), mFonts(std::move(fonts)), mHasVSTable(false) { + readAcceleratorTable(acceleratorTable, tableSize); +} + FontFamily::~FontFamily() { } @@ -247,4 +263,12 @@ std::shared_ptr FontFamily::createFamilyWithVariation( return std::shared_ptr(new FontFamily(mLangId, mVariant, std::move(fonts))); } +size_t FontFamily::writeAcceleratorTable(uint8_t* out) const { + return mCoverage.writeToBuffer(out); +} + +void FontFamily::readAcceleratorTable(const uint8_t* data, size_t size) { + bool result = mCoverage.initFromBuffer(data, size); + LOG_ALWAYS_FATAL_IF(!result, "Failed to reconstruct accelerator table from buffer"); +} } // namespace minikin diff --git a/libs/minikin/SparseBitSet.cpp b/libs/minikin/SparseBitSet.cpp index 85eddaef359..a95af4d716f 100644 --- a/libs/minikin/SparseBitSet.cpp +++ b/libs/minikin/SparseBitSet.cpp @@ -29,8 +29,13 @@ const uint32_t SparseBitSet::kNotFound; void SparseBitSet::clear() { mMaxVal = 0; - mIndices.reset(); - mBitmaps.reset(); + if (mOwnIndicesAndBitmaps) { + delete[] mIndices; + delete[] mBitmaps; + mIndexSize = 0; + mBitmapSize = 0; + mOwnIndicesAndBitmaps = false; + } } uint32_t SparseBitSet::calcNumPages(const uint32_t* ranges, size_t nRanges) { @@ -59,17 +64,17 @@ uint32_t SparseBitSet::calcNumPages(const uint32_t* ranges, size_t nRanges) { void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) { if (nRanges == 0) { - mMaxVal = 0; - mIndices.reset(); - mBitmaps.reset(); + clear(); return; } mMaxVal = ranges[nRanges * 2 - 1]; - size_t indexSize = (mMaxVal + kPageMask) >> kLogValuesPerPage; - mIndices.reset(new uint32_t[indexSize]); + mIndexSize = (mMaxVal + kPageMask) >> kLogValuesPerPage; + uint32_t* indices = new uint32_t[mIndexSize]; uint32_t nPages = calcNumPages(ranges, nRanges); - mBitmaps.reset(new element[nPages << (kLogValuesPerPage - kLogBitsPerEl)]); - memset(mBitmaps.get(), 0, nPages << (kLogValuesPerPage - 3)); + mBitmapSize = nPages << (kLogValuesPerPage - kLogBitsPerEl); + element* bitmaps = new element[mBitmapSize]; + mOwnIndicesAndBitmaps = true; + memset(bitmaps, 0, nPages << (kLogValuesPerPage - 3)); mZeroPageIndex = noZeroPage; uint32_t nonzeroPageEnd = 0; uint32_t currentPage = 0; @@ -85,30 +90,99 @@ void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) { mZeroPageIndex = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); } for (uint32_t j = nonzeroPageEnd; j < startPage; j++) { - mIndices[j] = mZeroPageIndex; + indices[j] = mZeroPageIndex; } } - mIndices[startPage] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); + indices[startPage] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); } size_t index = ((currentPage - 1) << (kLogValuesPerPage - kLogBitsPerEl)) + ((start & kPageMask) >> kLogBitsPerEl); size_t nElements = (end - (start & ~kElMask) + kElMask) >> kLogBitsPerEl; if (nElements == 1) { - mBitmaps[index] |= (kElAllOnes >> (start & kElMask)) & + bitmaps[index] |= (kElAllOnes >> (start & kElMask)) & (kElAllOnes << ((~end + 1) & kElMask)); } else { - mBitmaps[index] |= kElAllOnes >> (start & kElMask); + bitmaps[index] |= kElAllOnes >> (start & kElMask); for (size_t j = 1; j < nElements - 1; j++) { - mBitmaps[index + j] = kElAllOnes; + bitmaps[index + j] = kElAllOnes; } - mBitmaps[index + nElements - 1] |= kElAllOnes << ((~end + 1) & kElMask); + bitmaps[index + nElements - 1] |= kElAllOnes << ((~end + 1) & kElMask); } for (size_t j = startPage + 1; j < endPage + 1; j++) { - mIndices[j] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); + indices[j] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); } nonzeroPageEnd = endPage + 1; } + mBitmaps = bitmaps; + mIndices = indices; +} + +struct SparseBitSetHeader { + uint32_t maxValue; + uint32_t zeroPageIndex; + uint32_t indexSize; + uint32_t bitmapSize; +}; + +bool SparseBitSet::initFromBuffer(const uint8_t* data, size_t size) { + // No need to be concerned about endianness here since Intel x86 CPUs are little-endian. ARM + // CPUs are bi-endian but the endianness is only changeable at reset time and is impossible to + // change at runtime. Thus incoming data is guaranteed to have the same endianness as when it + // was created. + + if (data == nullptr || size < sizeof(SparseBitSetHeader)) { + clear(); + return false; + } + + // The serialized data starts with SparseBitSetHeader. + const SparseBitSetHeader* header = reinterpret_cast(data); + mMaxVal = header->maxValue; + mZeroPageIndex = header->zeroPageIndex; + mIndexSize = header->indexSize; + mBitmapSize = header->bitmapSize; + + mOwnIndicesAndBitmaps = false; + if (mIndexSize == 0 || mBitmapSize == 0 || mMaxVal == 0) { + const bool isValidEmptyBitSet = (mIndexSize == 0 && mBitmapSize == 0 && mMaxVal == 0); + if (!isValidEmptyBitSet) { + clear(); + } + return isValidEmptyBitSet; + } + + const size_t indicesSizeInBytes = sizeof(mIndices[0]) * mIndexSize; + const size_t bitmapsSizeInBytes = sizeof(mBitmaps[0]) * mBitmapSize; + if (size != sizeof(SparseBitSetHeader) + indicesSizeInBytes + bitmapsSizeInBytes) { + clear(); + return false; + } + data += sizeof(SparseBitSetHeader); + mIndices = reinterpret_cast(data); + data += indicesSizeInBytes; + mBitmaps = reinterpret_cast(data); + return true; +} + +size_t SparseBitSet::writeToBuffer(uint8_t* out) const{ + // See comments in SparseBitSet::initFromBuffer for the data structure. + const size_t indicesSizeInBytes = sizeof(mIndices[0]) * mIndexSize; + const size_t bitmapsSizeInBytes = sizeof(mBitmaps[0]) * mBitmapSize; + size_t necessarySize = sizeof(SparseBitSetHeader) + indicesSizeInBytes + bitmapsSizeInBytes; + if (out != nullptr) { + SparseBitSetHeader* header = reinterpret_cast(out); + header->maxValue = mMaxVal; + header->zeroPageIndex = mZeroPageIndex; + header->indexSize = mIndexSize; + header->bitmapSize = mBitmapSize; + + out += sizeof(SparseBitSetHeader); + memcpy(out, mIndices, indicesSizeInBytes); + out += indicesSizeInBytes; + memcpy(out, mBitmaps, bitmapsSizeInBytes); + } + return necessarySize; } int SparseBitSet::CountLeadingZeros(element x) { diff --git a/tests/perftests/Android.mk b/tests/perftests/Android.mk index de5769c7436..c60123a83d7 100644 --- a/tests/perftests/Android.mk +++ b/tests/perftests/Android.mk @@ -22,6 +22,7 @@ perftest_src_files := \ ../util/MinikinFontForTest.cpp \ ../util/UnicodeUtils.cpp \ FontCollection.cpp \ + FontFamily.cpp \ FontLanguage.cpp \ GraphemeBreak.cpp \ Hyphenator.cpp \ diff --git a/tests/perftests/FontFamily.cpp b/tests/perftests/FontFamily.cpp new file mode 100644 index 00000000000..a731ef640f3 --- /dev/null +++ b/tests/perftests/FontFamily.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 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. + */ +#include + +#include +#include "../util/MinikinFontForTest.h" + +namespace minikin { + +static void BM_FontFamily_create(benchmark::State& state) { + std::shared_ptr minikinFont = + std::make_shared("/system/fonts/NotoSansCJK-Regular.ttc", 0); + + while (state.KeepRunning()) { + std::shared_ptr family = std::make_shared( + std::vector({Font(minikinFont, FontStyle())})); + } +} + +BENCHMARK(BM_FontFamily_create); + +static void BM_FontFamily_create_fromBuffer(benchmark::State& state) { + std::shared_ptr minikinFont = + std::make_shared("/system/fonts/NotoSansCJK-Regular.ttc", 0); + + std::shared_ptr family = std::make_shared( + std::vector({Font(minikinFont, FontStyle())})); + + size_t bufSize = family->writeAcceleratorTable(nullptr); + std::unique_ptr buffer(new uint8_t[bufSize]); + family->writeAcceleratorTable(buffer.get()); + + while (state.KeepRunning()) { + std::shared_ptr family = std::make_shared( + std::vector({Font(minikinFont, FontStyle())}), buffer.get(), bufSize); + } +} + +BENCHMARK(BM_FontFamily_create_fromBuffer); + +} // namespace minikin diff --git a/tests/unittest/Android.mk b/tests/unittest/Android.mk index da7851a3e7d..fdcc0c4e472 100644 --- a/tests/unittest/Android.mk +++ b/tests/unittest/Android.mk @@ -76,6 +76,7 @@ LOCAL_SRC_FILES += \ GraphemeBreakTests.cpp \ LayoutTest.cpp \ LayoutUtilsTest.cpp \ + SparseBitSetTest.cpp \ UnicodeUtilsTest.cpp \ WordBreakerTests.cpp diff --git a/tests/unittest/SparseBitSetTest.cpp b/tests/unittest/SparseBitSetTest.cpp new file mode 100644 index 00000000000..ab38f6ca755 --- /dev/null +++ b/tests/unittest/SparseBitSetTest.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017 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. + */ + +#include + +#include +#include + +namespace minikin { + +TEST(SparseBitSetTest, randomTest) { + const uint32_t kTestRangeNum = 4096; + + std::mt19937 mt; // Fix seeds to be able to reproduce the result. + std::uniform_int_distribution distribution(1, 512); + + std::vector range { distribution(mt) }; + for (size_t i = 1; i < kTestRangeNum * 2; ++i) { + range.push_back((range.back() - 1) + distribution(mt)); + } + + SparseBitSet bitset; + bitset.initFromRanges(range.data(), range.size() / 2); + + uint32_t ch = 0; + for (size_t i = 0; i < range.size() / 2; ++i) { + uint32_t start = range[i * 2]; + uint32_t end = range[i * 2 + 1]; + + for (; ch < start; ch++) { + ASSERT_FALSE(bitset.get(ch)) << std::hex << ch; + } + for (; ch < end; ch++) { + ASSERT_TRUE(bitset.get(ch)) << std::hex << ch; + } + } + for (; ch < 0x1FFFFFF; ++ch) { + ASSERT_FALSE(bitset.get(ch)) << std::hex << ch; + } +} + +TEST(SparseBitSetTest, randomTest_restoredFromBuffer) { + const uint32_t kTestRangeNum = 4096; + + std::mt19937 mt; // Fix seeds to be able to reproduce the result. + std::uniform_int_distribution distribution(1, 512); + + std::vector range { distribution(mt) }; + for (size_t i = 1; i < kTestRangeNum * 2; ++i) { + range.push_back((range.back() - 1) + distribution(mt)); + } + + SparseBitSet tmpBitset; + tmpBitset.initFromRanges(range.data(), range.size() / 2); + + size_t bufSize = tmpBitset.writeToBuffer(nullptr); + ASSERT_NE(0U, bufSize); + std::vector buffer(bufSize); + tmpBitset.writeToBuffer(buffer.data()); + + SparseBitSet bitset; + bitset.initFromBuffer(buffer.data(), buffer.size()); + + uint32_t ch = 0; + for (size_t i = 0; i < range.size() / 2; ++i) { + uint32_t start = range[i * 2]; + uint32_t end = range[i * 2 + 1]; + + for (; ch < start; ch++) { + ASSERT_FALSE(bitset.get(ch)) << std::hex << ch; + } + for (; ch < end; ch++) { + ASSERT_TRUE(bitset.get(ch)) << std::hex << ch; + } + } + for (; ch < 0x1FFFFFF; ++ch) { + ASSERT_FALSE(bitset.get(ch)) << std::hex << ch; + } +} + +TEST(SparseBitSetTest, emptyBitSet) { + SparseBitSet bitset; + uint32_t empty_bitset[4] = { + 0 /* max value */, 0 /* zero page index */, 0 /* index size */, 0 /* bitmap size */ + }; + EXPECT_TRUE(bitset.initFromBuffer( + reinterpret_cast(empty_bitset), sizeof(empty_bitset))); +} + +TEST(SparseBitSetTest, invalidData) { + SparseBitSet bitset; + EXPECT_FALSE(bitset.initFromBuffer(nullptr, 0)); + + // Buffer is too small. + uint32_t small_buffer[3] = { 0, 0, 0 }; + EXPECT_FALSE(bitset.initFromBuffer( + reinterpret_cast(small_buffer), sizeof(small_buffer))); + + // Buffer size does not match with necessary size. + uint32_t invalid_size_buffer[4] = { + 0x12345678 /* max value */, 0 /* zero page index */, 0x50 /* index size*/, + 0x80 /* bitmap size */ + }; + EXPECT_FALSE(bitset.initFromBuffer( + reinterpret_cast(invalid_size_buffer), sizeof(invalid_size_buffer))); + + // max value, index size, bitmap size must be zero if the bitset is empty. + uint32_t invalid_empty_bitset1[4] = { + 1 /* max value */, 0 /* zero page index */, 0 /* index size */, 0 /* bitmap size */ + }; + EXPECT_FALSE(bitset.initFromBuffer( + reinterpret_cast(invalid_empty_bitset1), sizeof(invalid_empty_bitset1))); + + uint32_t invalid_empty_bitset2[4] = { + 0 /* max value */, 0 /* zero page index */, 1 /* index size */, 0 /* bitmap size */ + }; + EXPECT_FALSE(bitset.initFromBuffer( + reinterpret_cast(invalid_empty_bitset2), sizeof(invalid_empty_bitset2))); + + uint32_t invalid_empty_bitset3[4] = { + 0 /* max value */, 0 /* zero page index */, 0 /* index size */, 1 /* bitmap size */ + }; + EXPECT_FALSE(bitset.initFromBuffer( + reinterpret_cast(invalid_empty_bitset3), sizeof(invalid_empty_bitset3))); +} + +} // namespace minikin