编译过程经常需要判断字符的类型。
有些判断很简单,数字就是['0','9'],字幕就是['a','z']和['A','Z']。几个比较语句就搞定了。
有些比需要比较多的判断次数,因为他们是一个不连续的编码的字符集合。像最常见的空字符集合,{' ', '/t' ... }。
遇到这种集合就只能逐个比较。可以通过对集合排序,减少比较次数,但是比较的平均次数仍然和这个集合的大小成正比。(假设每个字符出现的概率一样)。
如果能引入一个方法,把比较次数缩小成1,那么整个扫描速度就可以提高。
v8引擎里有一个模板类,专门处理这个问题。
这个类通过cache,把比较次数缩小到1。而且把判断逻辑和cache的逻辑分离。可以适用任何字符集合判断。
template <class T, int size = 256>
class Predicate {
public:
inline Predicate() { }
inline bool get(uchar c);
private:
friend class Test;
bool CalculateValue(uchar c);
struct CacheEntry {
inline CacheEntry() : code_point_(0), value_(0) { }
inline CacheEntry(uchar code_point, bool value)
: code_point_(code_point),
value_(value) { }
uchar code_point_ : 21;
bool value_ : 1;
};
static const int kSize = size;
static const int kMask = kSize - 1;
CacheEntry entries_[kSize];
};
模板参数T是实现判断逻辑的类。size是cache大小,这个数字一定要是2的幂,默认值其实足够好了。
这个Predicte的工作方式很简单,先找找cache里有这个字符吗,有则返回cache,没有,判断一下,记录新的结果,记录到cache里。
template <class T, int s> bool Predicate<T, s>::get(uchar code_point) {
CacheEntry entry = entries_[code_point & kMask];
if (entry.code_point_ == code_point) return entry.value_;
return CalculateValue(code_point);
}
template <class T, int s> bool Predicate<T, s>::CalculateValue(
uchar code_point) {
bool result = T::Is(code_point);
entries_[code_point & kMask] = CacheEntry(code_point, result);
return result;
}
这里可以看出cache的分配是通过掩码进行的,实际上就是看这个多字节字符(uchar是int)的最后几位来 分配。掩码kMask是根据模板参数求得。如果size是256那掩码就是求最后一个字节。CacheEntry保存了最后一次比较信息。可以检查出冲突 (两个字符占用同一个cache),以最后一次比较优先记录。T类提供静态成员方法Is来判断字符是否属于这个集合。
整个方法可以看作是一个hash表开放地址发的变形。希望通过一次散列查找,替代集合运算。如果这个集合判断十分简单,比如数字,反而没必要这么做了。