Commit a53f4c11 authored by Anders Hartvoll Ruud's avatar Anders Hartvoll Ruud Committed by Commit Bot

Add CSSBitset

CSSBitset is the same as a std::bitset, except it uses CSSPropertyID
in its API, and has substantially (~10x) better performance when
traversing the set bits for cases where few bits are set. Previous
experiments has shown that the typical number of applied declarations
per element is in the 10-20 range (out of a 500+ bitset), which makes
this optimization relevant.

There are two reasons for adding this. First, there is a need (in the
near future) to interact with a CSSPropertyID-based bitset outside
of the StyleCascade. Therefore it's nice to have an API which actually
accepts/yields CSSPropertyIDs, to avoid noisy casting at every call-
site.

Second, there will be a need to traverse the bits efficiently in a
situation where the common case has very few bits set.

This CL replaces std::bitset with CSSBitset in CascadeMap, without
using any of the capabilities which make CSSBitset special, but those
capabilities will be used in the near future.

Change-Id: I7080c97f784f01c4f560e9fb570443dd927b7a64
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2152370Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/master@{#760314}
parent 75345b14
......@@ -440,6 +440,7 @@ blink_core_sources("css") {
"part_names.h",
"properties/computed_style_utils.cc",
"properties/computed_style_utils.h",
"properties/css_bitset.h",
"properties/css_direction_aware_resolver.cc",
"properties/css_direction_aware_resolver.h",
"properties/css_exposure.h",
......@@ -655,6 +656,7 @@ blink_core_tests("unit_tests") {
"parser/sizes_attribute_parser_test.cc",
"parser/sizes_math_function_parser_test.cc",
"properties/computed_style_utils_test.cc",
"properties/css_bitset_test.cc",
"properties/css_exposure_test.cc",
"properties/css_parsing_utils_test.cc",
"properties/css_property_ref_test.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PROPERTIES_CSS_BITSET_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PROPERTIES_CSS_BITSET_H_
#include <algorithm>
#include <cstring>
#include <initializer_list>
#include "third_party/blink/renderer/core/css/css_property_names.h"
namespace blink {
// A bitset designed for CSSPropertyIDs.
//
// It's different from std::bitset, in that it provides optimized traversal
// for situations where only a few bits are set (which is the common case for
// e.g. CSS declarations which apply to an element).
//
// The bitset can store a configurable amount of bits for testing purposes,
// (though not more than numCSSProperties).
template <size_t kBits>
class CORE_EXPORT CSSBitsetBase {
public:
static_assert(
kBits <= numCSSProperties,
"Bit count must not exceed numCSSProperties, as each bit position must "
"be representable as a CSSPropertyID");
static const size_t kChunks = (kBits + 63) / 64;
CSSBitsetBase() : chunks_() {}
CSSBitsetBase(const CSSBitsetBase<kBits>& o) { *this = o; }
CSSBitsetBase(std::initializer_list<CSSPropertyID> list) : chunks_() {
for (CSSPropertyID id : list)
Set(id);
}
void operator=(const CSSBitsetBase& o) {
std::memcpy(chunks_, o.chunks_, sizeof(chunks_));
}
bool operator==(const CSSBitsetBase& o) const {
return std::memcmp(chunks_, o.chunks_, sizeof(chunks_)) == 0;
}
bool operator!=(const CSSBitsetBase& o) const { return !(*this == o); }
inline void Set(CSSPropertyID id) {
size_t bit = static_cast<size_t>(id);
DCHECK_LT(bit, kBits);
chunks_[bit / 64] |= (1ull << (bit % 64));
}
inline void Or(CSSPropertyID id, bool v) {
size_t bit = static_cast<size_t>(id);
DCHECK_LT(bit, kBits);
chunks_[bit / 64] |= (static_cast<uint64_t>(v) << (bit % 64));
}
inline bool Has(CSSPropertyID id) const {
size_t bit = static_cast<size_t>(id);
DCHECK_LT(bit, kBits);
return chunks_[bit / 64] & (1ull << (bit % 64));
}
inline bool HasAny() const {
for (uint64_t chunk : chunks_) {
if (chunk)
return true;
}
return false;
}
inline void Reset() { std::memset(chunks_, 0, sizeof(chunks_)); }
// Yields the CSSPropertyIDs which are set.
class Iterator {
public:
Iterator(const uint64_t* chunks, size_t index)
: chunks_(chunks), index_(index), chunk_(ChunkAt(index)) {}
inline void operator++() {
do {
++index_;
chunk_ = chunk_ >> 1ull;
DCHECK_LE(index_, kBits);
if (index_ >= kBits)
return;
if (!chunk_) {
// If the chunk is empty, we can fast-forward to the next chunk.
size_t next_chunk_index = (index_ - 1) / 64 + 1;
index_ = std::min(next_chunk_index * 64, kBits);
chunk_ = ChunkAt(index_);
}
} while (!(chunk_ & 1));
}
inline CSSPropertyID operator*() const {
DCHECK_LT(index_, static_cast<size_t>(numCSSProperties));
return static_cast<CSSPropertyID>(index_);
}
inline bool operator==(const Iterator& o) const {
return index_ == o.index_;
}
inline bool operator!=(const Iterator& o) const {
return index_ != o.index_;
}
private:
// For a given index, return the corresponding chunk, down-shifted
// such that the given index is the LSB of the chunk.
//
// In other words, (ChunkAt(index) & 1) is a valid way of checking whether
// the bit at 'index' is set.
//
// If the given index is out of bounds, we don't really have a chunk to
// return. This function returns 1, solely to automatically fail the
// do-while condition in operator++. (It avoids having special handling of
// index > kBits there).
uint64_t ChunkAt(size_t index) const {
return index < kBits ? chunks_[index / 64] >> (index % 64) : 1ull;
}
const uint64_t* chunks_;
// The current bit index this Iterator is pointing to. Note that this is
// the "global" index, i.e. it has the range [0, kBits]. (It is not a local
// index with range [0, 64]).
//
// Never exceeds kBits.
size_t index_ = 0;
// The iterator works by "pre-fetching" the current chunk (corresponding
// (to the current index), and down-shifting by one for every iteration.
// This allows the iterator to skip the remainder of the chunk when we
// shift away the last bit.
uint64_t chunk_ = 0;
};
Iterator begin() const { return Iterator(chunks_, FirstIndex()); }
Iterator end() const { return Iterator(chunks_, kBits); }
private:
// Find the first index (i.e. first set bit). If no bits are set, returns
// kBits.
size_t FirstIndex() const {
size_t index = 0;
// Skip all empty chunks.
while (index < kBits && !chunks_[index / 64])
index += 64;
// Within the non-empty chunk, iterate until we find the set bit.
while (index < kBits && !(chunks_[index / 64] & (1ull << (index % 64))))
++index;
return std::min(index, kBits);
}
uint64_t chunks_[kChunks];
};
using CSSBitset = CSSBitsetBase<numCSSProperties>;
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_CSS_PROPERTIES_CSS_BITSET_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/css/properties/css_bitset.h"
#include <bitset>
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
template <size_t kBits>
std::bitset<kBits> ToStdBitsetUsingHas(const CSSBitsetBase<kBits>& bitset) {
std::bitset<kBits> ret;
for (size_t i = 0; i < kBits; ++i) {
if (bitset.Has(static_cast<CSSPropertyID>(i)))
ret.set(i);
}
return ret;
}
template <size_t kBits>
std::bitset<kBits> ToStdBitsetUsingIterator(
const CSSBitsetBase<kBits>& bitset) {
std::bitset<kBits> ret;
for (CSSPropertyID id : bitset) {
size_t bit = static_cast<size_t>(id);
DCHECK(!ret.test(bit));
ret.set(bit);
}
return ret;
}
// Creates a CSSBitsetBase with kBits capacity, sets the specified bits via
// CSSBitsetBase::Set, and then verifies that the correct bits are observed
// via both CSSBitsetBase::Has, and CSSBitsetBase::begin()/end().
template <size_t kBits>
void AssertBitset(const size_t* begin, const size_t* end) {
std::bitset<kBits> expected;
CSSBitsetBase<kBits> actual;
EXPECT_FALSE(actual.HasAny());
for (const size_t* b = begin; b != end; b++) {
actual.Set(static_cast<CSSPropertyID>(*b));
expected.set(*b);
}
EXPECT_EQ(expected, ToStdBitsetUsingHas(actual));
EXPECT_EQ(expected, ToStdBitsetUsingIterator(actual));
}
template <size_t kBits>
void AssertBitset(std::initializer_list<size_t> bits) {
AssertBitset<kBits>(bits.begin(), bits.end());
}
} // namespace
TEST(CSSBitsetTest, BaseBitCount0) {
static_assert(CSSBitsetBase<0>::kChunks == 0, "Correct chunk count");
AssertBitset<0>({});
}
TEST(CSSBitsetTest, BaseBitCount1) {
static_assert(CSSBitsetBase<1>::kChunks == 1u, "Correct chunk count");
AssertBitset<1>({});
AssertBitset<1>({0});
}
TEST(CSSBitsetTest, BaseBitCount63) {
static_assert(CSSBitsetBase<63>::kChunks == 1u, "Correct chunk count");
AssertBitset<63>({});
AssertBitset<63>({0});
AssertBitset<63>({1});
AssertBitset<63>({13});
AssertBitset<63>({62});
AssertBitset<63>({0, 1});
AssertBitset<63>({0, 62});
AssertBitset<63>({61, 62});
AssertBitset<63>({0, 1, 13, 61, 62});
}
TEST(CSSBitsetTest, BaseBitCount64) {
static_assert(CSSBitsetBase<64>::kChunks == 1u, "Correct chunk count");
AssertBitset<64>({});
AssertBitset<64>({0});
AssertBitset<64>({1});
AssertBitset<64>({13});
AssertBitset<64>({63});
AssertBitset<64>({0, 1});
AssertBitset<64>({0, 63});
AssertBitset<64>({62, 63});
AssertBitset<64>({0, 1, 13, 62, 63});
}
TEST(CSSBitsetTest, BaseBitCount65) {
static_assert(CSSBitsetBase<65>::kChunks == 2u, "Correct chunk count");
AssertBitset<65>({});
AssertBitset<65>({0});
AssertBitset<65>({1});
AssertBitset<65>({13});
AssertBitset<65>({63});
AssertBitset<65>({64});
AssertBitset<65>({0, 1});
AssertBitset<65>({0, 64});
AssertBitset<65>({63, 64});
AssertBitset<65>({0, 1, 13, 63, 64});
}
TEST(CSSBitsetTest, BaseBitCount127) {
static_assert(CSSBitsetBase<127>::kChunks == 2u, "Correct chunk count");
AssertBitset<127>({});
AssertBitset<127>({0});
AssertBitset<127>({1});
AssertBitset<127>({13});
AssertBitset<127>({125});
AssertBitset<127>({126});
AssertBitset<127>({0, 1});
AssertBitset<127>({0, 126});
AssertBitset<127>({125, 126});
AssertBitset<127>({0, 1, 13, 125, 126});
}
TEST(CSSBitsetTest, BaseBitCount128) {
static_assert(CSSBitsetBase<128>::kChunks == 2u, "Correct chunk count");
AssertBitset<128>({});
AssertBitset<128>({0});
AssertBitset<128>({1});
AssertBitset<128>({13});
AssertBitset<128>({126});
AssertBitset<128>({127});
AssertBitset<128>({0, 1});
AssertBitset<128>({0, 127});
AssertBitset<128>({126, 127});
AssertBitset<128>({0, 1, 13, 126, 127});
AssertBitset<128>({0, 1, 13, 63, 64, 65, 126, 127});
}
TEST(CSSBitsetTest, BaseBitCount129) {
static_assert(CSSBitsetBase<129>::kChunks == 3u, "Correct chunk count");
AssertBitset<129>({});
AssertBitset<129>({0});
AssertBitset<129>({1});
AssertBitset<129>({13});
AssertBitset<129>({127});
AssertBitset<129>({128});
AssertBitset<129>({0, 1});
AssertBitset<129>({0, 128});
AssertBitset<129>({127, 128});
AssertBitset<129>({0, 1, 13, 127, 128});
AssertBitset<129>({0, 1, 13, 63, 64, 65, 127, 128});
}
TEST(CSSBitsetTest, AllBits) {
std::vector<size_t> all_bits;
for (size_t i = 0; i < numCSSProperties; ++i)
all_bits.push_back(i);
AssertBitset<1>(all_bits.data(), all_bits.data() + 1);
AssertBitset<2>(all_bits.data(), all_bits.data() + 2);
AssertBitset<63>(all_bits.data(), all_bits.data() + 63);
AssertBitset<64>(all_bits.data(), all_bits.data() + 64);
AssertBitset<65>(all_bits.data(), all_bits.data() + 65);
AssertBitset<127>(all_bits.data(), all_bits.data() + 127);
AssertBitset<128>(all_bits.data(), all_bits.data() + 128);
AssertBitset<129>(all_bits.data(), all_bits.data() + 129);
}
TEST(CSSBitsetTest, NoBits) {
size_t i = 0;
AssertBitset<1>(&i, &i);
AssertBitset<2>(&i, &i);
AssertBitset<63>(&i, &i);
AssertBitset<64>(&i, &i);
AssertBitset<65>(&i, &i);
AssertBitset<127>(&i, &i);
AssertBitset<128>(&i, &i);
AssertBitset<129>(&i, &i);
}
TEST(CSSBitsetTest, SingleBit) {
for (size_t i = 0; i < 1; ++i)
AssertBitset<1>(&i, &i + 1);
for (size_t i = 0; i < 2; ++i)
AssertBitset<2>(&i, &i + 1);
for (size_t i = 0; i < 63; ++i)
AssertBitset<63>(&i, &i + 1);
for (size_t i = 0; i < 64; ++i)
AssertBitset<64>(&i, &i + 1);
for (size_t i = 0; i < 65; ++i)
AssertBitset<65>(&i, &i + 1);
for (size_t i = 0; i < 127; ++i)
AssertBitset<127>(&i, &i + 1);
for (size_t i = 0; i < 128; ++i)
AssertBitset<128>(&i, &i + 1);
for (size_t i = 0; i < 129; ++i)
AssertBitset<129>(&i, &i + 1);
}
TEST(CSSBitsetTest, Default) {
CSSBitset bitset;
for (auto id : CSSPropertyIDList())
EXPECT_FALSE(bitset.Has(id));
EXPECT_FALSE(bitset.HasAny());
}
TEST(CSSBitsetTest, SetAndHas) {
CSSBitset bitset;
EXPECT_FALSE(bitset.Has(CSSPropertyID::kVariable));
EXPECT_FALSE(bitset.Has(CSSPropertyID::kWidth));
EXPECT_FALSE(bitset.Has(CSSPropertyID::kHeight));
bitset.Set(CSSPropertyID::kVariable);
bitset.Set(CSSPropertyID::kWidth);
bitset.Set(CSSPropertyID::kHeight);
EXPECT_TRUE(bitset.Has(CSSPropertyID::kVariable));
EXPECT_TRUE(bitset.Has(CSSPropertyID::kWidth));
EXPECT_TRUE(bitset.Has(CSSPropertyID::kHeight));
}
TEST(CSSBitsetTest, Or) {
CSSBitset bitset;
EXPECT_FALSE(bitset.Has(CSSPropertyID::kWidth));
bitset.Or(CSSPropertyID::kWidth, false);
EXPECT_FALSE(bitset.Has(CSSPropertyID::kWidth));
bitset.Or(CSSPropertyID::kWidth, true);
EXPECT_TRUE(bitset.Has(CSSPropertyID::kWidth));
}
TEST(CSSBitsetTest, HasAny) {
CSSBitset bitset;
EXPECT_FALSE(bitset.HasAny());
bitset.Set(CSSPropertyID::kVariable);
EXPECT_TRUE(bitset.HasAny());
}
TEST(CSSBitsetTest, Reset) {
CSSBitset bitset;
EXPECT_FALSE(bitset.HasAny());
bitset.Set(CSSPropertyID::kHeight);
EXPECT_TRUE(bitset.HasAny());
EXPECT_TRUE(bitset.Has(CSSPropertyID::kHeight));
bitset.Reset();
EXPECT_FALSE(bitset.HasAny());
EXPECT_FALSE(bitset.Has(CSSPropertyID::kHeight));
}
TEST(CSSBitsetTest, Iterator) {
CSSBitset actual;
actual.Set(CSSPropertyID::kHeight);
actual.Set(CSSPropertyID::kWidth);
actual.Set(CSSPropertyID::kVariable);
std::bitset<numCSSProperties> expected;
expected.set(static_cast<size_t>(CSSPropertyID::kHeight));
expected.set(static_cast<size_t>(CSSPropertyID::kWidth));
expected.set(static_cast<size_t>(CSSPropertyID::kVariable));
EXPECT_EQ(expected, ToStdBitsetUsingIterator(actual));
}
TEST(CSSBitsetTest, Equals) {
CSSBitset b1;
CSSBitset b2;
EXPECT_EQ(b1, b2);
for (CSSPropertyID id : CSSPropertyIDList()) {
b1.Set(id);
EXPECT_NE(b1, b2);
b2.Set(id);
EXPECT_EQ(b1, b2);
}
}
TEST(CSSBitsetTest, Copy) {
EXPECT_EQ(CSSBitset(), CSSBitset());
CSSBitset b1;
for (CSSPropertyID id : CSSPropertyIDList()) {
CSSBitset b2;
b1.Set(id);
b2.Set(id);
EXPECT_EQ(b1, CSSBitset(b1));
EXPECT_EQ(b2, CSSBitset(b2));
}
}
TEST(CSSBitsetTest, InitializerList) {
for (CSSPropertyID id : CSSPropertyIDList()) {
CSSBitset bitset({CSSPropertyID::kColor, id});
EXPECT_TRUE(bitset.Has(CSSPropertyID::kColor));
EXPECT_TRUE(bitset.Has(id));
}
}
} // namespace blink
......@@ -21,12 +21,12 @@ inline void AddCustom(const CSSPropertyName& name,
result.stored_value->value = priority;
}
inline void AddNative(size_t index,
inline void AddNative(CSSPropertyID id,
CascadePriority priority,
CascadeMap::NativeMap& map) {
CascadePriority* p = map.Buffer() + index;
if (!map.Bits().test(index) || *p < priority) {
map.Bits().set(index);
CascadePriority* p = map.Buffer() + static_cast<size_t>(id);
if (!map.Bits().Has(id) || *p < priority) {
map.Bits().Set(id);
new (p) CascadePriority(priority);
}
}
......@@ -43,7 +43,7 @@ inline CascadePriority* FindNative(const CSSPropertyName& name,
CascadeMap::NativeMap& map) {
size_t index = static_cast<size_t>(name.Id());
DCHECK_LT(index, static_cast<size_t>(numCSSProperties));
return map.Bits().test(index) ? (map.Buffer() + index) : nullptr;
return map.Bits().Has(name.Id()) ? (map.Buffer() + index) : nullptr;
}
inline CascadePriority AtCustom(const CSSPropertyName& name,
......@@ -55,7 +55,7 @@ inline CascadePriority AtNative(const CSSPropertyName& name,
const CascadeMap::NativeMap& map) {
size_t index = static_cast<size_t>(name.Id());
DCHECK_LT(index, static_cast<size_t>(numCSSProperties));
return map.Bits().test(index) ? map.Buffer()[index] : CascadePriority();
return map.Bits().Has(name.Id()) ? map.Buffer()[index] : CascadePriority();
}
} // namespace
......@@ -129,17 +129,17 @@ void CascadeMap::Add(const CSSPropertyName& name, CascadePriority priority) {
high_priority_ |= (1ull << index);
if (origin <= CascadeOrigin::kUserAgent)
AddNative(index, priority, native_ua_properties_);
AddNative(id, priority, native_ua_properties_);
if (origin <= CascadeOrigin::kUser)
AddNative(index, priority, native_user_properties_);
AddNative(index, priority, native_properties_);
AddNative(id, priority, native_user_properties_);
AddNative(id, priority, native_properties_);
}
void CascadeMap::Reset() {
high_priority_ = 0;
native_properties_.Bits().reset();
native_ua_properties_.Bits().reset();
native_user_properties_.Bits().reset();
native_properties_.Bits().Reset();
native_ua_properties_.Bits().Reset();
native_user_properties_.Bits().Reset();
custom_properties_.clear();
custom_user_properties_.clear();
}
......
......@@ -5,9 +5,9 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RESOLVER_CASCADE_MAP_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_CSS_RESOLVER_CASCADE_MAP_H_
#include <bitset>
#include "third_party/blink/renderer/core/css/css_property_name.h"
#include "third_party/blink/renderer/core/css/css_property_names.h"
#include "third_party/blink/renderer/core/css/properties/css_bitset.h"
#include "third_party/blink/renderer/core/css/resolver/cascade_priority.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
......@@ -50,8 +50,8 @@ class CORE_EXPORT CascadeMap {
STACK_ALLOCATED();
public:
std::bitset<numCSSProperties>& Bits() { return bits_; }
const std::bitset<numCSSProperties>& Bits() const { return bits_; }
CSSBitset& Bits() { return bits_; }
const CSSBitset& Bits() const { return bits_; }
CascadePriority* Buffer() {
return reinterpret_cast<CascadePriority*>(properties_);
......@@ -62,9 +62,9 @@ class CORE_EXPORT CascadeMap {
private:
// For performance reasons, a char-array is used to prevent construction of
// CascadePriority objects. A companion std::bitset keeps track of which
// CascadePriority objects. A companion bitset keeps track of which
// properties are initialized.
std::bitset<numCSSProperties> bits_;
CSSBitset bits_;
alignas(CascadePriority) char properties_[numCSSProperties *
sizeof(CascadePriority)];
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment