学习
实践
活动
专区
工具
TVP
写文章
专栏首页移动应用开发专栏一文读懂 Xcode 代码索引原理
原创

一文读懂 Xcode 代码索引原理

本文转载自内部同事分享linkzhong(钟亮)

发表时间 2022年12月07日


导语:Xcode 作为 iOS 开发绕不开的 IDE 代码编辑功能很强大,但是在编辑大型工程时总是遇到代码高亮、代码提示失效,建立代码索引慢等问题。本文抽丝剥茧,介绍了 Xcode 代码索引的工作原理,并提出了一种跨设备共享代码索引的方案,在企微落地后优化了90%的全量索引耗时。

一、背景介绍

Xcode 作为 iOS 开发绕不开的 IDE,深受大家的“喜爱”,作为一款成熟的 IDE,大家对于它的期待还是挺高的。

Xcode 在面对体量巨大的工程时还是显得力不从心,你可能也有以下困惑:

  1. 正在修紧急 bug,Xcode 代码高亮怎么没了?代码提示、代码跳转统统失效,关键时刻掉链子;
  2. 面对海量代码,Xcode 的 Open Quickly 功能能够通过关键词迅速定位到想要找到的代码,背后原理究竟是什么?
  3. 代码索引总是耗时很长,在后台占用大量CPU,能不能提前预生成索引数据,跨设备共享。

带着上面的问题,笔者阅读了并整理了网上可以找到的相关资料,然后进行了大量的实验,最后完成了本文。本文基于 Xcode 14.0 (14A309) 进行研究(各个版本 Xcode 构建索引策略可能有所差异,但是思路是大体一致的),如有错误或者遗漏之处望各位大佬指正。

二、Xcode Index 工作流程

Xcode 的代码高亮、代码补全、代码跳转、查找调用链、重构、Open Quickly 等功能都是 Xcode Index 的一部分,打开 Xcode 工程可以在顶部 bar 看到 Index 的进度信息。

Xcode Index 是如何工作的呢?这就要引入一个新的工具 SourceKit,上述的 Xcode 代码操作相关功能,都是基于 SourceKit 实现的。SourceKit 和 Xcode 通过 XPC 进行通信,SourceKit 是 Xcode 代码索引功能的幕后主角,Xcode 是客户端,负责收集用户操作,转换成请求发给 SourceKit,最后展示计算结果;SourceKit 是后端,负责生成索引数据,计算 Xcode 请求。

整个工作流程如下图所示,Xcode 是前端,SourceKit 是驱动引擎,Clang 是实际产生索引数据的,索引数据存储在 Index Store。

Xcode 生成 Index Store 有两条路径:

路径一、Xcode 在闲时自动调用 SourceKit 在后台生成数据。SourceKit 最终调用 Clang 生成数据,使用编译参数 -index-store-path -fsyntax-only ,生成 Index 数据只需完成语法分析即可得到结果,不需要进行完整编译流程。

路径二、开启 Index-While-Building,如果将该配置项打开,会在编译过程中新增参数 -index-store-path,在编译时同时生成 Index 数据,由于编译时本来就需要进行词法分析、语法分析,因此中间产物是可以复用的。开启该功能会对编译速度产生影响,官方给出的数据是慢 2-5%。

三、SourceKit - 代码索引幕后主角

3.1 SKAgent

了解了整个工作流程,接下来我们来讲讲 SourceKit 的一些工作细节。运行 Xcode 时在活动监视器里可以看到一个进程 com.apple.dt.SKAgent ,SKAgent 是 SourceKit 的 XPC 服务,负责和Xcode 进行通信,它的路径是:/Applications/Xcode.app/Contents/SharedFrameworks/SourceKit.framework/Versions/A/XPCServices/com.apple.dt.SKAgent.xpc/Contents/MacOS/com.apple.dt.SKAgent。

3.2 SourceKit 通信协议探索

为了进一步探索 SourceKit 在背后究竟做了什么,我们将 Xcode 和 SourceKit 通信日志打印出来分析,通过以下命令启动 Xcode,可以将日志打印到指定文件。

SOURCEKIT_LOGGING=3 /Applications/Xcode.app/Contents/MacOS/Xcode &> ~/Downloads/xcode.log

SourceKit 支持哪些命令可以查看这个文件:

/Xcode.app/Contents/SharedFrameworks/SourceKit.framework/Versions/A/XPCServices/com.apple.dt.SKAgent.xpc/Contents/Frameworks/SKIPC.dylib

3.2.1 索引进度更新

索引进度更新相关的命令主要有以下几条:

  • 更新索引进度:indexer.callback.on-is-indexing-workspace
  • 索引建立完毕:indexer.callback.on-did-index-workspace
  • 建立索引任务中断:indexer.callback.on-did-suspend-indexing-workspace
  • 建立索引任务恢复:indexer.callback.on-did-resume-indexing-workspace

下面是一个实际案例,命令为 indexer.callback.on-is-indexing-workspace ,在 key.indexer.callback.on-is-indexing-workspace.user-info 的 XML 结构中,包含了已经完成索引文件数量 :IDEIndexingFilesCompletedKey 、索引剩余文件数量:IDEIndexingFilesRemainingKey 等信息,Xcode 根据进度信息更新 UI 进度。

SourceKit-client: [2:notification:66619:169.3069] {
  key.notification: indexer.callback,
  key.indexer.arg.indexer-token: 1,
  key.indexer.callback.kind: indexer.callback.on-is-indexing-workspace,
  key.indexer.callback.on-is-indexing-workspace.user-info: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEIndexingFilesCompletedKey</key>\n\t<integer>8740</integer>\n\t<key>IDEIndexingFilesRemainingKey</key>\n\t<integer>8262</integer>\n\t<key>IDEIndexingHotFilesKey</key>\n\t<integer>0</integer>\n\t<key>IDEIndexingLoadingProgressKey</key>\n\t<real>0.0</real>\n\t<key>IDEIndexingQueueWidthKey</key>\n\t<integer>8</integer>\n\t<key>IDEIndexingWaitingForPrebuildKey</key>\n\t<false/>\n</dict>\n</plist>\n"
}

3.2.2 编辑文件

接下来的案例场景是打开文件 ViewController.mm 编辑,首先 Xcode 会发送命令 source.request.indexer.editor-moved-focus-to-file 告诉 SourceKit 用户正在编辑 ViewController.mm,优先响应该文件的请求。

SourceKit-client: [2:request:259:29.8311] [78] {
  key.request: source.request.indexer.editor-moved-focus-to-file,
  key.indexer.arg.indexer-token: 1,
  key.indexer.arg.occurrence.file: "/Users/link/Desktop/Demo/src/ViewController.mm"
}

然后 Xcode 会发送命令 source.request.document.symbol-occurrences ,获取当前文件的所有符号信息,包含符号名、符号类型、语言、代码行列等信息,Xcode 通过这些信息进行代码高亮。

SourceKit-client: [2:request:259:29.9688] [80] {
  key.request: source.request.document.symbol-occurrences,
  key.indexer.arg.indexer-token: 1,
  key.indexer.arg.query.doc-location: {
    key.indexer.arg.doc-loc.url: "file:///Users/link/Desktop/Demo/src/ViewController.mm",
    key.indexer.arg.doc-loc.start-line: 18,
    key.indexer.arg.doc-loc.start-col: 0,
    key.indexer.arg.doc-loc.end-line: 33,
    key.indexer.arg.doc-loc.end-col: 5,
    key.indexer.arg.doc-loc.range-loc: 233,
    key.indexer.arg.doc-loc.range-count: 324,
    key.indexer.arg.doc-loc.encoding: 0
  },
  key.indexer.arg.query.file-content: "<DATA>"
}

SourceKit-client: [2:response:75571:29.9700] [80] {
  key.symbols: [
    {
      key.symbol: {
        key.indexer.arg.symbol.name: "viewDidLoad",
        key.indexer.arg.symbol.kind: "Xcode.SourceCodeSymbolKind.InstanceMethod",
        key.indexer.arg.symbol.language: "Xcode.SourceCodeLanguage.Objective-C",
        key.indexer.arg.symbol.resolution: "c:objc(cs)UIViewController(im)viewDidLoad"
      },
      key.indexer.arg.occurrence.role: 5,
      key.indexer.arg.occurrence.location: {
        key.indexer.arg.doc-loc.url: "file:///Users/link/Desktop/Demo/src/ViewController.mm",
        key.indexer.arg.doc-loc.start-line: 18,
        key.indexer.arg.doc-loc.start-col: 5,
        key.indexer.arg.doc-loc.end-line: 18,
        key.indexer.arg.doc-loc.end-col: 10,
        key.indexer.arg.doc-loc.range-loc: 9223372036854775807,
        key.indexer.arg.doc-loc.range-count: 0,
        key.indexer.arg.doc-loc.encoding: 1
      },
      key.indexer.arg.occurrence.line: 0,
      key.indexer.arg.occurrence.col: 0,
      key.indexer.arg.occurrence.file: "/Users/link/Desktop/Demo/src/ViewController.mm",
      key.indexer.arg.symbol.display-name: "-viewDidLoad",
      key.indexer.arg.symbol.is-in-project: false,
      key.indexer.arg.symbol.is-virtual: true,
      key.indexer.arg.symbol.is-system: true,
      key.is-implicit: false
    },
    ...
  ]
}

四、Index Store 解析

接下来我们看看 Index Store 是怎么存储的,如下图所示,主要由两个目录,DataStore(records + units)、UniDB。DataStore 存储了 Clang 编译的产物,是索引原始数据,UniDB 是为了加速查询建立的表,存储了经过处理后的信息。

Index Store 存储路径 Xcode14:~/Library/Developer/Xcode/DerivedData/project-xxx/Index.noindex Xcode13:~/Library/Developer/Xcode/DerivedData/project-xxx/Index

4.1 DataStore

units 记录了源码文件的路径、依赖的文件路径、依赖的 records 文件的路径,它的命名规则是 test.o-hash (Hash of output file path),如果文件名、路径等不变化文件名则不会变化。

records 记录了每个源码文件由哪些符号构成,它主要由 Symbol、Occurence 两部分构成。它的命名规则是 test.m-hash (Hash of output file path),如果代码变更文件名就会变化。

我们可以借助 LLVM 的工具 c-index-test 打印它们的数据结构:

# 打印 unit 数据结构
c-index-test core --print-unit /path/DataStore/v5/units/ModelA.o-2D1ATXD2A198H
# 打印 record 数据结构
c-index-test core --print-record /records/D4/ModelA.m-1S1A5O0O7K4D4

接下来看一个实际的案例,有一个源码文件 ModelA.mm,代码如下:

#import "ModelA.h"
@implementation ModelA
- (void)sayHello {
    [super sayHello];
    NSLog(@"ModelA %@", self.name);
}
@end

Unit 数据结构如下:

provider: clang-1400.0.29.102
is-system: 0
is-module: 0
module-name: <none>
has-main: 1
main-path: /path/Demo2/src/ModelA.m
work-dir: /path/Demo2
out-file: /SourceKitDemo.build/Debug-iphoneos/Demo.build/Objects-normal/arm64/ModelA.o
target: arm64-apple-ios13.0.0
is-debug: 1
DEPEND START
Unit | system | Foundation | /Users/link/Library/Developer/Xcode/DerivedData/ModuleCache.noindex/1SU4CTGFJJUAV/Foundation-A3SOD99KJ0S9.pcm | Foundation-A3SOD99KJ0S9.pcm-BVRSB7PKB109
Record | user | /path/Demo2/src/ModelA.m | ModelA.m-1S1A5O0O7K4D4
Record | user | /path/Demo2/src/ModelA.h | ModelA.h-1F980T5AVPZPP
Record | user | /path/Demo2/src/BaseModel.h | BaseModel.h-UWC84R5719GY
File | system | /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.0.sdk/System/Library/Frameworks/Foundation.framework/Modules/module.modulemap
File | system | 
...省略部分
DEPEND END (40)
INCLUDE START
/path/Demo2/src/ModelA.m:8 | /path/Demo2/src/ModelA.h
/path/Demo2/src/ModelA.h:9 | /path/Demo2/src/BaseModel.h
INCLUDE END (2)

Record 数据结构如下:

class/ObjC | ModelA | c:objc(cs)ModelA | <no-cgname> | Def - RelChild
instance-method/ObjC | sayHello | c:objc(cs)ModelA(im)sayHello | <no-cgname> | Def,Dyn,RelChild,RelOver - RelCall,RelCont
instance-method/ObjC | sayHello | c:objc(cs)BaseModel(im)sayHello | <no-cgname> | Ref,Call,RelCall,RelCont - RelOver
function/C | NSLog | c:@F@NSLog | <no-cgname> | Ref,Call,RelCall,RelCont - 
instance-property/ObjC | name | c:objc(cs)BaseModel(py)name | <no-cgname> | Ref,RelCont - 
instance-method/acc-get/ObjC | name | c:objc(cs)BaseModel(im)name | <no-cgname> | Ref,Call,Dyn,Impl,RelRec,RelCall,RelCont - 
class/ObjC | ModelA | c:objc(cs)ModelA | <no-cgname> |  - RelRec
------------
10:17 | class/ObjC | c:objc(cs)ModelA | Def | rel: 0
12:9 | instance-method/ObjC | c:objc(cs)ModelA(im)sayHello | Def,Dyn,RelChild,RelOver | rel: 2
	RelChild | c:objc(cs)ModelA
	RelOver | c:objc(cs)BaseModel(im)sayHello
13:12 | instance-method/ObjC | c:objc(cs)BaseModel(im)sayHello | Ref,Call,RelCall,RelCont | rel: 1
	RelCall,RelCont | c:objc(cs)ModelA(im)sayHello
14:5 | function/C | c:@F@NSLog | Ref,Call,RelCall,RelCont | rel: 1
	RelCall,RelCont | c:objc(cs)ModelA(im)sayHello
14:30 | instance-property/ObjC | c:objc(cs)BaseModel(py)name | Ref,RelCont | rel: 1
	RelCont | c:objc(cs)ModelA(im)sayHello
14:30 | instance-method/acc-get/ObjC | c:objc(cs)BaseModel(im)name | Ref,Call,Dyn,Impl,RelRec,RelCall,RelCont | rel: 2
	RelCall,RelCont | c:objc(cs)ModelA(im)sayHello
	RelRec | c:objc(cs)ModelA

4.2 Unit 与 Record 关系

下图通过一个案例来展示 Unit 与 Record 之间的关系,有两个源码文件,first.c 依赖了 things.h、header.h、feature.h。second.c 依赖了 header.h、feature.h。在 things.h 定义了一个宏,header.h 会判断是否定义宏展开部分代码。 建立索引完成后,会生成 2 个 Unit 和 6 个 Record 文件,由于编译 first.o、second.o 时宏定义不一样,导致 header.h 展开内容不一样,所以会产生两份 header.h。

4.3 Symbol 与 Occurence

Record 主要由 Symbol、Occurence 两部分构成,Symbol 由 USR、Language、Kind 等元素构成,每个符号对应一个 Symbol。Occurence 由 Symbol、Location、Roles、Relations 等元素构成,它表示了每个 Symbol 被使用的位置。

下图展示了一个案例,1 到 12 行定义了类 Polygon,14 到 26 行定义了 Polygon 的子类 RegularPolygon

Record 是怎么表示类定义和子类继承关系的呢?首先图中所示的两个 Symbol,Polygon、RegularPolygon 分别为两个类的符号信息,Symbol 通过 USR(Unified Symbol Resolution)来唯一标识,USR 的官方描述:

USR: A Unified Symbol Resolution is a string that identifies a particular entity (function, class, variable, etc.) within a program. USRs can be compared across translation units to determine, e.g., when references in one translation refer to an entity defined in another translation unit.

这两个 Symbol 一共出现了 3 次,对应 3 个 Occurrence,其中 Polygon 出现了两次,一次是出现在 1:7 位置,角色是类定义,第二次出现在 14:31,角色是被继承;RegularPolygon 出现了一次位置在 14:7,角色是类定义。

4.4 UniDB

了解了 Unit 和 Record 的数据结构和用途,我们就可以推导出 Xcode 实现一些功能的原理,例如有这么一个场景,我们需要找到 Polygon 的所有子类,可以这么实现:

  1. 遍历所有 Record 的 Occurrence,找到 Roles 为 RelationBaseOf 对应 Symbol 是 Polygon 的 Occurrence;
  2. 通过步骤一找到的 Occurrence 就可以找到所有定义 Polygon 子类的 Occurrence,从而找到 Polygon 子类的 Symbol;
  3. 最后结合 Unit 可以定位到我们要找的子类的行号、列号;

但是线性遍历的效率较低,Xcode 为了优化查询效率引入了 LMDB 来存储中间数据结构。LMDB 全称为 Lightning Memory-Mapped Database,是高性能的内存映射型数据库,它有以下优点:

  1. 数据读写速度快,基于内存映射方式访问文件;
  2. 使用轻量,文件结构简单,包含一个数据文件和一个锁文件,数据随意复制,读写引用代码很小的 LMDB 库即可完成

它由两个文件组成:data.mdb、lock.mdb,为了探索 Xcode 在 LMDB 里存储了什么数据,我们可以用 python 的 lmdb 库解析。

import lmdb

if __name__ == '__main__':
		env = lmdb.open("path", max_dbs=14)
    txn = env.begin()
    for key, value in txn.cursor():
        print(key, value)

解析结果如下图所示,可以看到它由多个表构成,很多表下还有子表。

还是用刚刚的案例:查询 Polygon 的所有子类,Xcode 通过下面的 key-value 表来加速查询过程:

  1. 建立一个 key-value 表,key 是 USR,value 是 USR 出现过的 Record、Roles;
  2. 计算 Polygon 的 USR,通过 USR 查找到它出现过的 Records,筛选 Roles 包含 RelationBaseOf,定位到哪些 Record 内包含 Polygon 子类定义;
  3. 在 Record 文件中可以查询具体子类的信息;

还有一些其它用的表:

  1. Search symbols by name:记录了 Symbol Name 和 USR 的对应关系,方便通过关键词搜索代码,Open Quickly 就是基于该表实现的;
  2. Remove data from unreferenced records:记录了 Record 文件被哪些 Unit 使用,如果没有 Unit 使用了,该 Record 就会被清理;
  3. Units to re-index when header changes:记录了头文件被哪些 unit 使用,用于查询头文件变化后,哪些 unit 需要重新生成索引数据;

五、Index Store 跨设备共享

5.1 方案可行性验证

了解 Index Store 的数据结构之后,不难发现只要源码、编译选项一致,产生的 Record 其实是一样的,企微工程完整进行一次代码索引耗时 24 分钟,我们是否可以提前预生成 Unit、Record,开发直接下载产物加速代码索引。

我们先用一个 Demo 工程来验证我们的猜想,工程很简单,结构如下所示:

我们将同样的工程拷贝两份,分别为:Demo1、Demo2,最终目标是在 Demo1 工程可以复用 Demo2 工程生成的 Index Store。

首先在 Demo2 的 Other C Flags 配置编译参数 -index-store-path /path/DataStore ,在编译时生成 DataStore 数据到指定目录。

首先删除 Demo1 的 DataStore、UniDB 目录,将 Demo2 产生的 DataStore 拷贝到 Demo1 的 DerivedData 目录

DataStore 存放路径:~/Library/Developer/Xcode/DerivedData/Demo1-xxx/Index.noindex

在命令行输入以下命令打开 Xcode Index 日志,可以确认 Xcode 对哪些文件进行了索引。

defaults write com.apple.dt.Xcode IDEIndexShowLog -bool YES

打开 Demo1 工程,观察日志发现还是会重新建立索引,说明复用失败。查看 Demo2 Unit 信息,可以看到它存储了 Demo2 工程的绝对路径信息,要替换成 Demo1 工程的路径。

provider: clang-1400.0.29.102
is-system: 0
is-module: 0
module-name: <none>
has-main: 1
main-path: /path/Demo2/src/ModelA.m
work-dir: /path/Demo2
...

可以使用 index-import 工具(https://github.com/MobileNativeFoundation/index-import)来完成替换操作。

index-import \
    -remap "/path/Demo2=/path/Demo" \
    "/path/DataStore" \
    "/path/DataStore2"

替换后再次查看 unit 信息,可以看到路径已经被修改。再次替换 Demo1 工程 DataStore,发现复用成功。

provider: clang-1400.0.29.102
is-system: 0
is-module: 0
module-name: <none>
has-main: 1
main-path: /path/Demo1/src/ModelA.m
work-dir: /path/Demo1

5.2 企微落地方案

在 Demo 工程我们验证了方案可行,于是想通过这种方式提升开发本地索引效率,要让方案顺利落地,需要让整个流程自动化,并且让开发同学使用尽量简单,最终我们落地的流程如下图所示:

  1. 在流水线上使用构建机自动构建最新代码的索引,构建完成后上传到存储服务;
  2. 开发在本机触发更新索引,从存储服务下载最新的索引数据;
  3. 清理历史索引数据,进行 remap 操作,将路径修改为本地路径,然后替换 DerivedData 的 DataStore;

经过测试,在 M1 Max 机器上,使用索引数据缓存后,企微工程建立全量索引耗时从 24 分钟优化到了 1.5 分钟。

参考资料

  1. https://www.youtube.com/watch?v=jGJhnIT-D2M
  2. https://github.com/bazel-ios/rules_ios/blob/master/docs/index_while_building.md
  3. https://github.com/MobileNativeFoundation/index-import
  4. https://levelup.gitconnected.com/uncovering-xcode-indexing-8b3f3ff82551

原创声明,本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

登录 后参与评论
0 条评论

相关文章

  • 一文读懂 MySQL 索引 B+树原理!

    MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BTree索引,哈希索引,全文索引等等。

    终码一生
  • 一文读懂数据库索引原理及优化

    本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题。特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MyS...

    秃头哥编程
  • 一文读懂 MySQL 索引

    从上面定义中我们可以分析出索引本质是一个数据结构,他的作用是帮助我们高效获取数据,在正式介绍索引前,我们先来了解一下基本的数据结构

    说故事的五公子
  • 一文读懂 Kubernetes APIServer 原理

    作者杜杨浩,腾讯云高级工程师,热衷于开源、容器和Kubernetes。目前主要从事Kubernetes集群高可用&备份还原,以及边缘计算相关研发工作 前言 整个...

    腾讯云原生
  • 一文读懂Go Modules原理

    点击上方“腾讯云TStack”关注我们 获取最in云端资讯和海量技术干货 本文作者 / 阿杜 腾讯云高级工程师 热衷于开源、容器和Kubernetes 目前...

    腾讯云TStack
  • 一文读懂 | Cache的原理

    我们可以看到,随着工艺和设计的演进,CPU 计算性能其实发生了翻天覆地的变化,但是DRAM存储性能的发展没有那么快。

    用户7686797
  • 一文读懂Go Http Server原理

    hello大家好呀,我是小楼,这是系列文《Go底层原理剖析》的第二篇,依旧是分析 Http 模块,话不多说,开始。

    龟仙老人
  • 一文读懂 select count(*) 底层原理

    “SELECT COUNT( * ) FROM TABLE” 是个再常见不过的 SQL 需求了。在 MySQL 的使用规范中,我们一般使用事务引擎 InnoDB...

    Python数据科学
  • 一文读懂 | Linux共享内存原理

    在Linux系统中,每个进程都有独立的虚拟内存空间,也就是说不同的进程访问同一段虚拟内存地址所得到的数据是不一样的,这是因为不同进程相同的虚拟内存地址会映射到不...

    混说Linux
  • 一文读懂Innodb MVCC实现原理

    • 不可重复读:事物A同样的查询条件,查询多次,读出的数据不一样,不一样的侧重点在于 update和delete

    公众号-Java编程大本营
  • 一文读懂视频编解码原理[通俗易懂]

    谈到视频的编解码,我们会自然地想到H.264、HEVC/H.265这些权威的视频编解码标准;谈到标准,有人觉得这个是有专门机构去研究的,我们关心应用就好;即使有...

    全栈程序员站长
  • 一文读懂 React 组件渲染核心原理

    相信大家对 React 都已经非常熟悉了,像 React,Vue 这样的现代前端框架已经是我们日常开发离不开的工具了,这篇文章主要是从源码的角度剖析 React...

    Tecvan
  • 一文读懂 | Linux延时队列工作原理

    当进程要获取某些资源(例如从网卡读取数据)的时候,但资源并没有准备好(例如网卡还没接收到数据),这时候内核必须切换到其他进程运行,直到资源准备好再唤醒进程。

    用户7686797
  • 一文读懂SpringBoot持久层开发原理

    石的三次方
  • 原创 | 一文读懂 BERT 源代码

    文:陈之炎 本文约4400字,建议阅读10+分钟本文对BERT模型预训练任务的源代码进行了详细解读,在Eclipse开发环境里,对BERT 源代码的各实现步骤分...

    数据派THU
  • 一文读懂MySQL的索引结构及查询优化

    (同时再次强调,这几篇关于MySQL的探究都是基于5.7版本,相关总结与结论不一定适用于其他版本)

    Python之道
  • 一文读懂eBPF|即时编译(JIT)实现原理

    在《eBPF实现原理》一文中,我们介绍了 eBPF 的实现原理,这篇文章我们主要介绍 eBPF 运行加速器 JIT(Just In Time)的实现原理。

    用户7686797
  • 一文读懂 HugePages(大内存页)的原理

    在介绍 HugePages 之前,我们先来回顾一下 Linux 下 虚拟内存 与 物理内存 之间的关系。

    用户7686797
  • HTML5(十二)——一文读懂 WebSocket 原理

    WebSocket 是一个持久化的协议,通过第一次 HTTP Request 建立连接之后,再把通信协议升级成 websocket,保持连接状态,后续的数据交换...

    呆呆

扫码关注腾讯云开发者

领取腾讯云代金券