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

在浏览器的实现中,处理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;
      // ..... 略

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人工智能算法工程师(中级)课程9-PyTorch神经网络之全连接神经网络实战与代码详解

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能算法工程师(中级)课程9-PyTorch神经网络之全连接神经网络实战与代码详解。本文将给大家展示全连接神经网络与代码详解&#xff0c;包括全连接模型的设计、数学原理介绍&#xff0c;并从手写数字识别到猫狗识…

Neo4j安装

下载地址&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 1.安装jdk&#xff0c;Neo4j 3.0需要jdk8&#xff0c;2.3.0之前的版本建议jdk7。Neo4j最新版本5.21.2&#xff0c;对应jdk版本17 2.将下载的zip文件解压到合适路径。 3.设置环境变量NEO4J_H…

历年HW已公开漏洞合集!(目前漏洞库更新至84个,Goby持续更新...)

截至2024年7月11日&#xff0c;Goby红队版已扩充以下历年HW已公开漏洞库&#xff0c;本次更新84个&#xff1a; &#xff08;后续将持续更新…) 华天动力OA 华天动力 OA getHtmlContent 文件读取漏洞华天动力OA办公系统 /OAapp/bfapp/buffalo/TemplateService 文件读取漏洞华…

在亚马逊云科技AWS利用IaC(基础设施即代码)设计和搭建云原生架构(附免费学习教程和证书)

今天小李哥为大家介绍的是利用IaC(基础设施即代码)设计和搭建亚马逊云科技AWS云原生架构。本篇文章将会介绍如何在亚马逊云科技上搭建云原生Serverless服务&#xff0c;所使用的开发服务介绍&#xff0c;并展示搭建云原生架构的cdk代码。小李哥同时会给大家分享快速学习亚马逊云…

python:sympy 求解一元五次方程式

pip install sympy 或者 本人用的 anaconda 3 自带 sympy 在北大数学训练营&#xff0c;韦东奕 用卡丹公式 巧妙 求解一元五次方程式&#xff1a; \latex $x^510*x^320*x-4 0$ from sympy import *x symbols(x) expr x**5 10*x**3 20*x -4# 用卡丹公式 尝试化简 a sym…

冒泡排序与其C语言通用连续类型排序代码

冒泡排序与其C语言通用连续类型排序代码 冒泡排序冒泡排序为交换排序的一种&#xff1a;动图展示&#xff1a;冒泡排序的特性总结&#xff1a;冒泡排序排整型数据参考代码&#xff08;VS2022C语言环境&#xff09;&#xff1a; 冒泡排序C语言通用连续类型排序代码对比较的方式更…

MVC 控制器 中Action 不能同名,参数不一样,路由器寻找不到对应的,要加特性

//1 方法不可能完全相同&#xff0c;参数不同//2 那还需要特性吗&#xff1f;需要的&#xff0c;因为MVC选择方法时&#xff0c;不是按参数选择&#xff1a;http请求发送很多数据&#xff0c;其实没法识别&#xff0c;//因为mvc找方法是通过反射来的&#xff0c;GetMethods(nam…

基于jeecgboot-vue3的Flowable流程-集成仿钉钉流程(六)仿钉钉流程的转bpmn流程图

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 1、转bpmn流程图接口 /*** 转为bpmn xml格式* param processModel* throws IOException*/PostMapping("/ddtobpmnxml")public Result<?> ddToBpmnXml(RequestBody Proce…

int类型变量表示范围的计算原理

文章目录 1. 了解2. 为什么通常情况下int类型整数的取值范围是-2147483648 ~ 21474836473. int类型究竟占几个字节4. 推荐 1. 了解 通常情况下int类型变量占4个字节&#xff0c;1个字节有8位&#xff0c;每位都有0和1两种状态&#xff0c;所以int类型变量一共可以表示 2^32 种状…

Javascript[ECMAScript] 新特性—1

背景 JS1.1&#xff08;1997&#xff09; 第一版基于Netscape Navigator 3.0中实现的JAVASCRIPT 1.1 JS1.2&#xff08;1999&#xff09; 基于Netscape Navigator 4.0中实现的JavaScript 1.2。添加了正则表达式、更好的字符串处理、新的控制语句、Try/Catch异常处理、更严格…

Qt/C++项目积累: 2.主机监控器 - 2.2 历史功能实现

修订历史&#xff1a; 20240711&#xff1a;初始表设计&#xff0c;采用sqlite 正文&#xff1a; 关于历史数据存储&#xff0c;考虑的是用数据库来完成&#xff0c;目前考虑使用Sqlite和mysql&#xff0c;先用sqlite来实现&#xff0c;设计表过程如下&#xff1a; 机器总览…

SpringBoot 3.3 【一】手把手讲解-使用Eclipse创建第一个SpringBoot应用程序

简单动作&#xff0c;深刻联结。在这技术海洋&#xff0c;我备好舟&#xff0c;等你扬帆。启航吧&#xff01; &#x1f31f;点击【关注】&#xff0c;解锁定期的技术惊喜&#xff0c;让灵感与知识的源泉不断涌动。 &#x1f44d;一个【点赞】&#xff0c;如同心照不宣的默契&a…

FL Studio21.9.821最新中文版来啦!附带永久免费下载地址

【音乐制作新宠&#xff0c;FL Studio21中文版来啦&#xff01;&#x1f389;】 嘿&#xff0c;音乐爱好者们&#x1f3a7;&#xff0c;今天要给大家种草一个超酷炫的宝贝儿——FL Studio21中文版&#xff01;这货简直就是音乐制作界的小可爱加实力派&#xff0c;让我彻底沦陷了…

Dify中的RAG和知识库

一.RAG 基本架构 当用户提问 “美国总统是谁&#xff1f;” 时&#xff0c;系统并不是将问题直接交给大模型来回答&#xff0c;而是先将用户问题在知识库中进行向量搜索&#xff0c;通过语义相似度匹配的方式查询到相关的内容&#xff08;拜登是美国现任第46届总统…&#xff0…

2024年7月2日~2024年7月8日周报

目录 一、前言 二、完成情况 2.1 吴恩达机器学习系列课程 2.1.1 分类问题 2.1.2 假说表示 2.1.3 判定边界 2.2 学习数学表达式 2.3 论文写作情况 2.3.1 题目选取 2.3.2 摘要 2.3.3 关键词 2.3.4 引言部分 2.3.4 文献综述部分 三、下周计划 3.1 存在的问题 3.2 …

Java高级重点知识点-24-函数式接口

文章目录 函数式接口函数式编程常用函数式接口 函数式接口 有且仅有一个抽象方法的接口。 格式&#xff1a; 修饰符 interface 接口名称 {public abstract 返回值类型 方法名称(可选参数信息);// 其他非抽象方法内容 }public interface MyFunctionalInterface {void myMethod…

企事业网站需要做软件测试吗?包括哪些测试内容和好处?

在这个数字化时代&#xff0c;企事业网站已经成为宣传和交流的重要平台&#xff0c;它的稳定性、安全性和用户体验对于企业形象和业务发展至关重要。因此&#xff0c;为了确保企事业网站的良好运行&#xff0c;对其进行软件测试是至关重要的。那么网站测试具体有哪些好处?又包…

分享一款嵌入式开源LED指示灯控制代码框架cotLed

一、工程简介 cotLed是一款轻量级的LED控制软件框架&#xff0c;可以十分方便地控制及自定义LED的各种状态&#xff0c;移植方便&#xff0c;无需修改&#xff0c;只需要在初始化时实现单片机硬件GPIO初始化&#xff0c;同时为框架接口提供GPIO写函数即可。 框架代码工程地址&a…

【电子通识】无源元件与有源元件的定义和区别是什么?

当提到构成电路的电子器件时,许多人可能会想到晶体管、电容器、电感器和电阻器等器件。一般情况下,我们使用的电子器件分为两大类,即“有源元件”和“无源元件”。 有源元件是主动影响(如放大、整流、转换等)所供给电能的元件。 无源元件是对所供给的电能执行被动…

Linux编程第三篇:Linux简介,开源软件简介(Linux是否安全?参考TESEC指标)

业精于勤荒于嬉&#xff0c;行成于思毁于随。 今天这篇算是Linux的正式学习&#xff0c;废话不多说&#xff0c;我们开始吧 第三篇 一、UNIX与Linux发展史1.1、UNIX发展历史和发行版本1.2、UNIX主要发行版本1.3、Linux发展历史1.4、Linux内核版本1.5、Linux主要发行版本 二、开…