Skip to content

Commit

Permalink
css: speedup class matching
Browse files Browse the repository at this point in the history
Class name matching is slow. It's extremely slow when there are many
class selectors. Mitigate this problem by:

1. when parsing css, store class name's hash value
2. during css check, split class names and calculate their hash.
3. for class selectors compare integers first to avoid expensive
   class name matching for most rules.
  • Loading branch information
bbshelper committed Nov 1, 2023
1 parent bef375f commit 8bb1ec0
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 2 deletions.
2 changes: 2 additions & 0 deletions crengine/include/lvstring.h
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,9 @@ class lString32
value_type firstChar() { return empty() ? 0 : at(0); }

/// calculates hash for string
static lUInt32 getHash(const lChar32 *begin, const lChar32 *end);
lUInt32 getHash() const;

/// returns character at specified position, with index bounds checking, fatal error if fails
value_type & at( size_type pos ) { if ((unsigned)pos > (unsigned)pchunk->len) crFatalError(); return modify()[pos]; }
/// returns character at specified position, without index bounds checking
Expand Down
10 changes: 9 additions & 1 deletion crengine/include/lvstsheet.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,23 @@ class LVCssSelectorRule
lUInt16 _attrid;
LVCssSelectorRule * _next;
lString32 _value;
lUInt32 _valueHash = 0;
public:
LVCssSelectorRule(LVCssSelectorRuleType type)
: _type(type), _id(0), _attrid(0), _next(NULL)
{ }
LVCssSelectorRule( LVCssSelectorRule & v );
void setId( lUInt16 id ) { _id = id; }
void setAttr( lUInt16 id, const lString32 value ) { _attrid = id; _value = value; }
void setAttr( lUInt16 id, const lString32 value ) {
_attrid = id;
_value = value;
_valueHash = _value.getHash();
}
const LVCssSelectorRule * getNext() const { return _next; }
void setNext(LVCssSelectorRule * next) { _next = next; }
~LVCssSelectorRule() { if (_next) delete _next; }
// A fail-fast check, returning false to rule out a match.
bool quickCheck(const lUInt32 *classHashes, size_t size) const;
/// check condition for node
bool check( const ldomNode * & node, bool allow_cache=true ) const;
/// check next rules for node
Expand Down Expand Up @@ -178,6 +185,7 @@ class LVCssSelector {
bool parse( const char * &str, lxmlDocBase * doc );
lUInt16 getElementNameId() const { return _id; }
bool check( const ldomNode * node, bool allow_cache=true ) const;
bool quickCheck(const lUInt32 *classHashes, size_t size) const;
void applyToPseudoElement( const ldomNode * node, css_style_rec_t * style ) const;
void apply( const ldomNode * node, css_style_rec_t * style ) const
{
Expand Down
8 changes: 8 additions & 0 deletions crengine/src/lvstring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,14 @@ bool lString32::atod( double &d, char dp ) const {
}

#define STRING_HASH_MULT 31

lUInt32 lString32::getHash(const lChar32 *begin, const lChar32 *end) {
lUInt32 res = 0;
for (; begin < end; ++begin)
res = res * STRING_HASH_MULT + *begin;
return res;
}

lUInt32 lString32::getHash() const
{
lUInt32 res = 0;
Expand Down
43 changes: 42 additions & 1 deletion crengine/src/lvstsheet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4922,6 +4922,16 @@ lUInt32 LVCssSelectorRule::getWeight() const {
return 0;
}

bool LVCssSelectorRule::quickCheck(const lUInt32 *classHashes, size_t size) const {
if (_type != cssrt_class)
return true;
for (size_t i = 0; i < size; ++i) {
if (classHashes[i] == _valueHash)
return true;
}
return false;
}

bool LVCssSelectorRule::check( const ldomNode * & node, bool allow_cache ) const
{
if (!node || node->isNull() || node->isRoot())
Expand Down Expand Up @@ -5453,6 +5463,10 @@ bool LVCssSelector::check( const ldomNode * node, bool allow_cache ) const
return true;
}

bool LVCssSelector::quickCheck(const lUInt32 *classHashes, size_t size) const {
return !_rules || _rules->quickCheck(classHashes, size);
}

bool parse_attr_value( const char * &str, char * buf, bool &parse_trailing_i, char stop_char=']' )
{
int pos = 0;
Expand Down Expand Up @@ -5953,6 +5967,7 @@ LVCssSelectorRule::LVCssSelectorRule( LVCssSelectorRule & v )
: _type(v._type), _id(v._id), _attrid(v._attrid)
, _next(NULL)
, _value( v._value )
, _valueHash(v._valueHash)
{
if ( v._next )
_next = new LVCssSelectorRule( *v._next );
Expand Down Expand Up @@ -5988,6 +6003,21 @@ LVStyleSheet::LVStyleSheet( LVStyleSheet & sheet )
_selector_count = sheet._selector_count;
}

template<typename F>
static void for_each_split(const lChar32 *begin, F functor) {
const lChar32 *end = begin;
while (*end) {
if (*end == ' ') {
if (end > begin)
functor(begin, end);
begin = end + 1;
}
++end;
}
if (end > begin)
functor(begin, end);
}

void LVStyleSheet::apply( const ldomNode * node, css_style_rec_t * style ) const
{
if (!_selectors.length())
Expand Down Expand Up @@ -6019,14 +6049,25 @@ void LVStyleSheet::apply( const ldomNode * node, css_style_rec_t * style ) const
LVCssSelector * selector_0 = _selectors[0];
LVCssSelector * selector_id = id>0 && id<_selectors.length() ? _selectors[id] : NULL;

LVArray<lUInt32> class_hash_array;
bool class_hash_inited = false;

for (;;)
{
if (selector_0!=NULL)
{
if (!class_hash_inited) {
class_hash_inited = true;
const lString32 &v = node->getAttributeValue(attr_class);
for_each_split(v.c_str(), [&](const lChar32 *begin, const lChar32 *end) {
class_hash_array.add(lString32::getHash(begin, end));
});
}
if (selector_id==NULL || selector_0->getSpecificity() < selector_id->getSpecificity() )
{
// step by sel_0
selector_0->apply( node, style );
if (selector_0->quickCheck(class_hash_array.ptr(), class_hash_array.length()))
selector_0->apply( node, style );
selector_0 = selector_0->getNext();
}
else
Expand Down

0 comments on commit 8bb1ec0

Please sign in to comment.