Chromium源码阅读(7):了解WTF的静态字符串机制

news2024/11/10 7:23:09

在浏览器的实现中,处理HTML和CSS涉及大量的字符串操作,这些操作通常包括字符串的比较、查找和匹配。如果使用普通的字符串对这些进行操作,在面临大量DOM元素和CSS规则时会导致效率低下。
例如,当解析CSS时,属性名如colormarginpadding等在内部可以被转换为静态字符串。在后续的样式计算和匹配过程中,只需通过比较这些属性的ID,而不是一遍遍地比较完整的字符串。这种比较是通过简单的指针或整数比较来完成的,这要比字符串的字节级比较快得多。

WTF中的Static Strings

Chromium使用了Web Template Framework (WTF) 提供的一系列高效的字符串处理机制。
WTF模块是一组底层实用工具和类的集合,它为Chromium提供了包括字符串在内的基础设施支持。在字符串处理方面,WTF提供了一个特殊的类别,即静态字符串(Static Strings)。静态字符串是在编译时已知的字符串常量,它们在浏览器的整个生命周期中保持不变。

静态字符串机制的核心在于,它给每一个字符串分配了一个唯一的标识符(ID)。这些字符串在编译时就被收集和注册到一个全局的字符串表中。当代码需要使用这些字符串时,它不是直接操作原始的字符数据,而是通过这些唯一的ID来引用字符串。这种机制类似于字符串池或者字符串的符号表示(symbolic representation)。

如何计算字符串Hash

这个唯一的标识符(ID)本质上是根据字符串内容计算出来的hash。因此,优秀的hash算法可以最大程度避免冲突。在WTF中,hash计算代码如下:
在这里插入图片描述

  template <typename T, UChar Converter(T)>
  static unsigned ComputeHashAndMaskTop8Bits_internal(const unsigned char* data, unsigned length) {
    StringHasher hasher;
    hasher.AddCharactersAssumingAligned_internal<T, Converter>(data, length);
    return hasher.HashWithTop8BitsMasked();
  }
// StringHasher 的 AddCharactersAssumingAligned_internal实现如下:
  template <typename T, UChar Converter(T)>
  void AddCharactersAssumingAligned_internal(const unsigned char* data, unsigned length) {
    DCHECK(!has_pending_character_);

    static_assert(std::is_trivial_v<T> && std::is_standard_layout_v<T>,
                  "we only support hashing POD types");
    bool remainder = length & 1;
    length >>= 1;

    while (length--) {
      T data_converted[2];
      std::memcpy(data_converted, data, sizeof(T)*2);
      AddCharactersAssumingAligned(Converter(data_converted[0]), Converter(data_converted[1]));
      data += sizeof(T)*2;
    }

    if (remainder) {
      T data_converted;
      std::memcpy(&data_converted, data, sizeof(T));
      AddCharacter(Converter(data_converted));
    }
  }

// StringHasher 的 HashWithTop8BitsMasked实现如下:
  unsigned HashWithTop8BitsMasked() const {
    unsigned result = AvalancheBits();

    // Reserving space from the high bits for flags preserves most of the hash's
    // value, since hash lookup typically masks out the high bits anyway.
    result &= (1U << (sizeof(result) * 8 - kFlagCount)) - 1;

    // This avoids ever returning a hash code of 0, since that is used to
    // signal "hash not computed yet". Setting the high bit maintains
    // reasonable fidelity to a hash code of 0 because it is likely to yield
    // exactly 0 when hash lookup masks out the high bits.
    if (!result)
      result = 0x80000000 >> kFlagCount;

    return result;
  }
  // AvalancheBits 实现如下
  unsigned AvalancheBits() const {
    unsigned result = hash_;

    // Handle end case.
    if (has_pending_character_) {
      result += pending_character_;
      result ^= result << 11;
      result += result >> 17;
    }

    // Force "avalanching" of final 31 bits.
    result ^= result << 3;
    result += result >> 5;
    result ^= result << 2;
    result += result >> 15;
    result ^= result << 10;

    return result;
  }

以上代码的逻辑如下:

总体目标是用于计算字符串的哈希值,并在最终结果中屏蔽掉顶部的8位。使用了StringHasher类来逐步构建哈希值。
下面是逐步分析这个哈希计算过程:

ComputeHashAndMaskTop8Bits_internal

这是外部调用的模板函数。它接受一个指向数据的指针和数据的长度,然后通过调用StringHasherAddCharactersAssumingAligned_internal方法来添加字符数据,并最终调用HashWithTop8BitsMasked来获取哈希值并屏蔽掉顶部的8位。

AddCharactersAssumingAligned_internal

这个模板方法处理实际的字符数据。它的工作原理如下:

  1. 它首先检查has_pending_character_标志,确保没有挂起的字符。
  2. 它使用static_assert来确保模板类型T是平凡的(trivial)和具有标准布局(standard layout),这表示着T是一个简单的数据类型,可以安全地使用内存复制。
  3. 然后,它检查length是否是奇数,并将其保存在remainder中。length被除以2,因为我们一次处理两个字符。
  4. 在一个循环中,它每次处理两个字符。每次迭代中,它使用std::memcpy将两个字符的数据复制到data_converted数组中,然后使用Converter将字符转换,并使用AddCharactersAssumingAligned添加转换后的字符到哈希计算中。
  5. 如果有剩余的一个字符(即原始长度是奇数),则将其添加到哈希中。

HashWithTop8BitsMasked

这个方法返回最终的哈希值,并屏蔽掉顶部的8位。操作步骤如下:

  1. 它首先调用AvalancheBits来完成哈希计算。
  2. 然后,它屏蔽掉顶部的kFlagCount位,通常是8位。
  3. 它还确保哈希值不会是0,因为0被用来表示“尚未计算哈希值”。如果结果为0,则设置最高位为1。

AvalancheBits

这个方法处理最终阶段的哈希计算,以确保哈希值的每一位都能受到前面位的影响(这称为雪崩效应)。步骤如下:

  1. 如果有挂起的字符,它将其添加到哈希计算中,并进行一系列的位操作来混合位。
  2. 然后,它对结果进行一系列的位移和加法操作,以确保最终的哈希值拥有好的分布性和随机性。

这段代码使用了一种有效的方法来逐步计算字符串的哈希值,并通过一系列特定的位操作来确保哈希值的分布性和随机性。

在最后阶段,它屏蔽掉顶部的8位,并确保哈希值不为0。这种哈希机制在处理字符串时非常高效,因为它可以轻松地将字符串映射到一个较小的整数空间,同时减少冲突的可能性。

屏蔽掉顶部的8位通常是为了在哈希值中保留空间用于特定的标志或控制位。在一些数据结构中,需要在同一个整数值中存储哈希值和其他状态或标志信息。另外hash主要的离散空间集中在地位区域,因此屏蔽顶部8位不会造成过多的冲突。

如何让计算Hash更快

hash除了要避免冲突,更重要的是要足够快,否则我们最初希望用hash作为字符串ID来加速性能的想法反而达不到目的了。

关于这段Hash计算的深入设计,在源文件中有所介绍,这是 Paul Hsieh’s SuperFastHash的实现,具体的文章在这里: http://www.azillionmonkeys.com/qed/hash.html
为了方便读者,这篇文章使用AI总结的主要内容如下:

这篇文章讲述了哈希函数的研究和发展。作者在过去的工作中被要求研究哈希函数,并与老板就哈希函数的设计发生了争议。作者主张使用可根据表大小定制的LFSRs或CRCs,而老板则倾向于使用简单的取模质数操作,并引用了30年前Knuth的《计算机程序设计艺术》。尽管作者展示了取模质数方法存在严重碰撞的例子,但老板仍然坚持自己的观点。
争议最终由一位同事通过发现Bob Jenkins的哈希函数得以解决,该函数在碰撞分析方面基于更好的分析,并且性能优于两位的建议。作者在之后的项目中偶尔参考了这个网页,并注意到了“一次一个哈希”和“FNV哈希”这两种方法的添加。对于Bob Jenkins的函数,代码有些混乱,使用了许多神秘的常数,作者并不理解它们是如何构建的。而“一次一个哈希”和“FNV哈希”则非常简洁,魔法常数很少。
Bob Jenkins本人指出FNV哈希的性能超过了他自己的函数,因此作者一开始就接受了这一观点,并开始在所有情况下盲目使用FNV哈希。之后,作者在项目中真正测量了性能后,决定需要认真研究这个问题。
作者注意到,在Athlon XP系统上,Bob Jenkins的函数在性能上基本上超过了所有其他函数(包括FNV函数)。这与Bob Jenkins的说法相矛盾,解释是因为Bob Jenkins在Pentium上进行测量,而Pentium IV的移位操作很慢,这减慢了除了FNV以外的所有哈希函数的速度。而Opteron/Athlon 64架构有一个极大改进的整数乘法器,这表明FNV哈希在这个系统上也应该表现良好。
作者想要理解这些函数的真正性能限制,并看看是否可以通过重新编码来帮助提高性能。但是,由于Bob Jenkins的代码过于复杂,编译器或乱序CPU架构很难找到其中的并行性。相反,CRC和“一次一个哈希”是完全指令依赖的。因此,作者将输入数据分成奇偶字节,并行计算两个CRC和“一次一个哈希”,然后在最后将一个合并到另一个中,这显著提高了这些函数的性能,但仍然没有达到Bob Jenkins哈希的性能。
作者接着尝试了解这些函数的性能瓶颈,发现除了Bob Jenkins的函数外,其他函数都是基于每次消耗一个8位字节的输入数据,并以某种单向方式将每个字节混合到一个32位累加器中,然后可能经过后处理后直接输出。作者尝试使用更少的操作,并将输入片段的大小从8位增加到16位,这在x86架构上有很大的性能影响,因为这些架构支持非对齐的16位字访问。作者编写了一个程序来搜索在需要最终修正前提供最大雪崩效应的参数,并且在寻找可以完成对所有实际输入位的雪崩的参数集时添加了等效于在输入中填充固定数量零的内循环展开的指令。
作者惊讶地发现,在几个小时的工作后,轻易地找到了具有所有这些属性的哈希函数,并通过简单的统计测试验证了它具有与均匀随机映射等效的分布。
真相的时刻在性能测试中到来了——鉴于架构,这是一个预定的结论。作者的哈希函数比Bob Jenkin的函数快了大约66%。
文章还提到了一些更新和反馈,包括在IBM Z/OS(S/390主机)上的测试情况,以及在Power4基础的Linux机器上的测试结果。作者还对SuperFastHash进行了优化,以最大限度地利用现代CPU的管线,并使代码在整数处理上更加统一,从而在语义上更具可移植性。此外,还有关于SuperFastHash的增量版本的讨论。
作者指出,随着新一代CPU(能够进行64位计算)的出现,我们可以期待在未来几年内将有广泛的64位软件开发和工具可用性。作者还提到了内循环中的指令依赖性问题,并暗示通过奇偶字分裂和重组可能会带来显著的性能提升。
最后,作者提供了SuperFastHash的代码,并指出它已被苹果公司的开源WebKit(用于Safari浏览器)采用,并且可能最终会回到Konqueror浏览器中。此外,谷歌的Chrome浏览器基于WebKit,并继续使用这个哈希函数。作者还提供了一些对SuperFastHash进行增量更新的方法。

简而言之,如果想要加快Hash的计算,需要考虑内存对齐的影响、指令顺序无关的设计等因素。

Static Strings在Chromium的应用

Blink内核在初始化的时候,最主要的事情就是将各种关键字用Static Strings初始化。这段代码可以通过如下片段一窥端倪:
CoreInitializer::Initialize
在这里插入图片描述
这里面,各种xxx_names就是初始化静态字符串,我们随便挑一个进去看看:
在这里插入图片描述
而且在代码中,字符串的hash值、长度值都提前算好了直接hardcode到代码里,进一步提高初始化速度。

这段代码更直观:

void Init() {
  static bool is_loaded = false;
  if (is_loaded) return;
  is_loaded = true;

  struct NameEntry {
    const char* name;
    unsigned hash;
    unsigned char length;
  };

  static const NameEntry kNames[] = {
    { "anonymous", 4545318, 9 },
    { "async", 2556481, 5 },
    { "auto", 4834735, 4 },
    { "circle", 1709685, 6 },
    { "close", 3222970, 5 },
    { "closed", 5707365, 6 },
    { "col", 12850806, 3 },
    { "colgroup", 3733719, 8 },
    { "decimal", 15005416, 7 },
    { "disc", 6260783, 4 },
    { "disclosure-closed", 7859367, 17 },
    { "disclosure-open", 5814334, 15 },
    { "done", 11685723, 4 },
    { "eager", 9356754, 5 },
    { "email", 13948917, 5 },
    // ....
  };

  for (size_t i = 0; i < std::size(kNames); ++i) {
    StringImpl* impl = StringImpl::CreateStatic(kNames[i].name, kNames[i].length, kNames[i].hash);
    void* address = reinterpret_cast<AtomicString*>(&names_storage) + i;
    new (address) AtomicString(impl);
  }
}

其他加速查找的技巧

除了用hash,使用状态机也能大大减少搜索空间,加快搜索速度。例如这个函数,根据字符串长度划分为几个case,再根据不同长度划分为新的case,总之,可以快速实现从字符串到Tag枚举的转换:

CORE_EXPORT html_names::HTMLTag lookupHTMLTag(
    const UChar* data,
    unsigned length) {
  DCHECK(data);
  DCHECK(length);
  switch (length) {
  case 1:
    switch (data[0]) {
    case 'a':
      return html_names::HTMLTag::kA;
    case 'b':
      return html_names::HTMLTag::kB;
    case 'i':
      return html_names::HTMLTag::kI;
    case 'p':
      return html_names::HTMLTag::kP;
    case 'q':
      return html_names::HTMLTag::kQ;
    case 's':
      return html_names::HTMLTag::kS;
    case 'u':
      return html_names::HTMLTag::kU;
    }
    break;
  case 2:
    switch (data[0]) {
    case 'b':
      if (data[1] == 'r') {
        return html_names::HTMLTag::kBr;
      }
      break;
    case 'd':
      switch (data[1]) {
      case 'd':
        return html_names::HTMLTag::kDd;
      case 'l':
        return html_names::HTMLTag::kDl;
      case 't':
        return html_names::HTMLTag::kDt;
      }
      break;
    case 'e':
      if (data[1] == 'm') {
        return html_names::HTMLTag::kEm;
      }
      break;
    case 'h':
      switch (data[1]) {
      case '1':
        return html_names::HTMLTag::kH1;
      case '2':
        return html_names::HTMLTag::kH2;
      case '3':
        return html_names::HTMLTag::kH3;
      case '4':
        return html_names::HTMLTag::kH4;
      case '5':
        return html_names::HTMLTag::kH5;
      case '6':
        return html_names::HTMLTag::kH6;
      case 'r':
        return html_names::HTMLTag::kHr;
      }
      break;
    case 'l':
      if (data[1] == 'i') {
        return html_names::HTMLTag::kLi;
      }
      break;
    case 'o':
      if (data[1] == 'l') {
        return html_names::HTMLTag::kOl;
      }
      break;
    case 'r':
      switch (data[1]) {
      case 'b':
        return html_names::HTMLTag::kRb;
      case 'p':
        return html_names::HTMLTag::kRp;
      case 't':
        return html_names::HTMLTag::kRt;
      }
      break;
    case 't':
      switch (data[1]) {
      case 'd':
        return html_names::HTMLTag::kTd;
      case 'h':
        return html_names::HTMLTag::kTh;
      case 'r':
        return html_names::HTMLTag::kTr;
      case 't':
        return html_names::HTMLTag::kTt;
      }
      break;
      // ..... 略

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1919382.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

05_TypeScript 中的数据类型

TypeScript 中的数据类型 一、概述二、详解布尔类型&#xff08;boolean&#xff09; true / false数字类型&#xff08;number&#xff09;字符串类型&#xff08;string&#xff09;数组类型&#xff08;array&#xff09;元组类型&#xff08;tuple&#xff09; 属于数组的一…

【Qt 初识 Test】用图形化和代码的方式实现简单的Qt程序

文章目录 1. 通过图形化的方式实现&#x1f34e;2. 通过代码的方式实现 1. 通过图形化的方式实现&#x1f34e; 在界面创建出一个控件&#xff0c;显示 hello world&#xff0c;通过拖拽的方式实现&#xff1b; widget.ui文件如下&#xff1a;&#x1f50d; 生成的 ui_widget.…

【Java16】多态

向上类型转换 对于引用变量&#xff0c;在程序中有两种形态&#xff1a;一种是编译时类型&#xff0c;这种引用变量的类型在声明它的时候就决定了&#xff1b;另一种则是运行时类型&#xff0c;这种变量的类型由实际赋给它的对象决定。 当一个引用变量的编译时类型和运行时类…

mybatis日志记录方案

首先对指定表进行监控 对表进行监控,那么就要使用的是statementInterceptor 拦截器 使用拦截器那么就要写intercepts写拦截条件进行拦截 监控只对与增删改 查询不进行监控 对于字段的监控,是谁修改了字段,那么就进行报警,或者提醒 消息提醒使用钉钉机器人进行消息提醒 P…

AGI 之 【Hugging Face】 的【文本摘要】的 [评估PEGASUS ] / [ 微调PEGASUS ] / [生成对话摘要] 的简单整理

AGI 之 【Hugging Face】 的【文本摘要】的 [评估PEGASUS ] / [ 微调PEGASUS ] / [生成对话摘要] 的简单整理 目录 AGI 之 【Hugging Face】 的【文本摘要】的 [评估PEGASUS ] / [ 微调PEGASUS ] / [生成对话摘要] 的简单整理 一、简单介绍 二、文本摘要 三、在CNN/Daily…

【接口设计】如何设计统一 RESTful 风格的数据接口

如何设计统一 RESTful 风格的数据接口 1.版本控制1.1 通过 URL1.2 通过自定义请求头1.3 通过 Accept 标头 2.过滤信息3.确定 HTTP 的方法4.确定 HTTP 的返回状态5.定义统一返回的格式 近年来&#xff0c;随着移动互联网的发展&#xff0c;各种类型的客户端层出不穷。如果不统一…

347. 前 K 个高频元素(中等)

347. 前 K 个高频元素 1. 题目描述2.详细题解3.代码实现3.1 Python3.2 Java 1. 题目描述 题目中转&#xff1a;347. 前 K 个高频元素 2.详细题解 寻找出现频率前 k k k高的元素&#xff0c;因此需要先统计各个元素出现的次数&#xff0c;该步骤时间复杂度为 O ( n ) O(n) O(n)…

javaweb中的请求与响应--基于postman工具的应用(附带postman的详细安装步骤)

一、前言 后端的第一天感觉难度就上来了&#xff0c;可能是基础太过薄弱了吧。目前看视频已经有点跟不上了&#xff0c;果然15天想要拿下还是太勉强了点。30天还差不多。不知道读者们有没有好好的去学这方面的知识&#xff0c;没有什么是学不会的&#xff0c;关键是坚持。 Po…

【C语言】C语言-学生籍贯信息记录系统(源码+论文)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

C#Winform窗体中嵌入exe文件

1&#xff0c;效果以嵌入Modbus Slave为例&#xff1a; 2&#xff0c;代码&#xff1a; public partial class Form1 : Form{//设置嵌入exe的常量private const int nIndex -16;private const int dwNewLong 0x10000000;Process m_AppProcess;public Form1(){InitializeCompo…

了解AsyncRotationController

概述 基于android 15.0, 以从强制横屏App上滑退回桌面流程来分析 frameworks/base/services/core/java/com/android/server/wm/AsyncRotationController.javaAsyncRotationController 是一种控制器&#xff0c;用于处理设备显示屏旋转时非活动窗口的异步更新。这种控制器通过…

【记录】CSS|Tailwind 的主题定义的颜色的使用方法(--color啥的)

文章目录 【记录】CSS&#xff5c;Tailwind 的主题定义的颜色的使用方法&#xff08;--color 啥的&#xff09;省流版GPT 详细解释版Tailwind CSS 配置文件示例使用自定义颜色定义 CSS 变量总结 附赠个 Tips 【记录】CSS&#xff5c;Tailwind 的主题定义的颜色的使用方法&#…

【Python】已解决:ModuleNotFoundError: No module named ‘sklearn.cross_validation

文章目录 一、问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 &#xff08;机器学习分割数据问题&#xff09;解决“ModuleNotFoundError: No module named ‘sklearn.cross_validation’” 一、问题背景 在机器学习的实践中&#xff0c;数据分割是…

聚鼎装饰画:现在做装饰画能不能相信

在艺术的殿堂中&#xff0c;装饰画以其多变的风格和独特的魅力占据了一席之地。它们或清新淡雅&#xff0c;或浓烈奔放&#xff0c;总能为现代家居带来一丝生气与美感。然而&#xff0c;在这美丽的背后&#xff0c;却隐藏着一个令人困惑的问题&#xff1a;现在做装饰画&#xf…

[EasilyOpenJCL] 使用 Java 调用显卡 计算 和Java调用 CPU 进行计算 的基准测试!

设备环境介绍 easily-openJCL 是一个轻量级的 Java 语言下的 GPU 显卡 计算库&#xff0c;它提供了一套简单易用的 API&#xff0c;让用户能够轻松实现 GPU 计算操作。 通过 Java 调用 GPU 计算的一个库&#xff0c;使用非常简单的API就可以轻松应付 Java 数据类型在 GPU 中的…

程序使用多进程,打包.exe后,程序陷入死循环

最近写了一个深度学习程序&#xff0c;用cxfreezee打包exe后&#xff0c;在本地运行突然出现死循环&#xff0c;明明在pycharm运行一切正常。 排查了问题&#xff0c;怀疑是多进程的原因&#xff0c;解决办法&#xff1a; 在你的主程序前添加一行代码&#xff1a; if __name_…

Typescript 模块小知识-global scope

问题表现 在编写ts代码的时候遇到一个问题, 表现为, 如果在某个ts工程中, 如果多个文件里面没有任何导出export或者是export default, 那么这些文件如果有const或者是let定义相同的声明都会报错如下 无法重新声明块范围变量 a/a.ts 和 index.ts 和 index2.ts 都没有进行expor…

C++系列-Vector(一)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” Vector的介绍及使用 Vector的介绍 当vector构建的参数类型为char类型时&#xff0c;它是和string是极其类似的&#xff0c;但是二者之间也有不同&#xff0c;比如&#xff0c…

人工智能时代,零基础学IT,我首推Python作为你编程入门语言!

人工智能时代为什么将 Python 称为第一语言&#xff1f; 因为python适应了人工智能时代&#xff1a; 人工智能时代对于代码的简便性有很大要求&#xff0c;像传统的C/CPP/Java学习较为复杂&#xff0c;学习路线长&#xff0c;对于很多零基础的人入门困难。python的兼容性&…

24下软考高级-系统架构设计师100条知识点速记!

11月系统架构设计师现在准备真的太早了吗&#xff1f; 不不不~对于0基础和打工人&#xff0c;留给我们备考的时间其实已经不多了&#xff01; 想开始不晓得从哪里开始&#xff1f;这里给大家整理了24下系统架构设计师知识点100条&#xff0c;符合最新版教材和考试大纲&#x…