被 AppArmor 击杀的 Dockge

Debian 自从 Debian10 后开始默认启用了 AppArmor,这是另外一个和SELinux 类似的 MAC(Mandatory Access Control)实现,但他有更容易被人类所接受的配置颗粒度,并且由社区开发,而且很容易关掉(不推荐你关掉) 在默认情况下 Debian 的 AppArmor运行情况是不会干扰用户的正常操作的,但是在有些时候他会突然给你一拳告诉你你不能这样做,这样违反了约束。 好吧,我遇到了。在 Debian13 上如果不去新增 AppArmor 配置就会无法运行Dockge 这个还挺不错的 Compose 管理器。 除此之外,还有 lscr.io/linuxserver/qbittorrent这个容器也受到了影响——可能更多,但我没有继续测试。 初探 我们通过命令 dmesg | rg apparmor | rg denied得到了输出 1 2 3 4 5 6 [ 2661.149627] audit: type=1400 audit(1759760495.520:84): apparmor="DENIED" operation="create" class="net" info="failed protocol match" error=-13 profile="docker-default" pid=15527 comm="node" family="unix" sock_type="stream" protocol=0 requested="create" denied="create" addr=none [ 2674.109757] audit: type=1400 audit(1759760508.479:85): apparmor="DENIED" operation="create" class="net" info="failed protocol match" error=-13 profile="docker-default" pid=15712 comm="node" family="unix" sock_type="stream" protocol=0 requested="create" denied="create" addr=none [ 2699.872327] audit: type=1400 audit(1759760534.241:86): apparmor="DENIED" operation="create" class="net" info="failed protocol match" error=-13 profile="docker-default" pid=15897 comm="node" family="unix" sock_type="stream" protocol=0 requested="create" denied="create" addr=none [ 2751.237225] audit: type=1400 audit(1759760585.607:87): apparmor="DENIED" operation="create" class="net" info="failed protocol match" error=-13 profile="docker-default" pid=16081 comm="node" family="unix" sock_type="stream" protocol=0 requested="create" denied="create" addr=none [ 2811.399220] audit: type=1400 audit(1759760645.767:88): apparmor="DENIED" operation="create" class="net" info="failed protocol match" error=-13 profile="docker-default" pid=16269 comm="node" family="unix" sock_type="stream" protocol=0 requested="create" denied="create" addr=none [ 2871.560585] audit: type=1400 audit(1759760705.928:89): apparmor="DENIED" operation="create" class="net" info="failed protocol match" error=-13 profile="docker-default" pid=16472 comm="node" family="unix" sock_type="stream" protocol=0 requested="create" denied="create" addr=none 接下来诡异的事情来了:我没有在任何地方发现任何一个叫作docker-default 的配置文件,完全不存在于标准的/etc/apparmor.d/ 中: 1 2 ➜ dockge ls /etc/apparmor.d/ | rg docker ➜ dockge 怎么办? 再探 我觉得这个问题的关键词应该是 Docker,AppArmor。于是我的第一反应是像 GPT求助——很可惜它没有给我预期响应。 于是我直接使用搜索引擎进行搜索,第一项便是如下链接: Docker的 AppArmor 安全配置文件 很好,这就是我想要的。但也不完全是,因为这里面没有说解决方案,相反给了个巨复杂的文件 再做搜寻,我看到了这个 如何在Debian 上禁用 docker-default 他的解决方案是想办法让 Docker 不再动态生成docker-default这个配置。我觉得这个已经很接近我想要的答案了,但我觉得不够完美 终曲 我们现在得到了两个方案: 调整 Docker 的安全配置 关闭 AppArmor 对于 Docker 的管控 调整 Docker 的安全配置 我这里列出 compose.yml (也就是 docker-compose.yml的高版本别名)中如何调整相关设定: 在修改之前: 1 2 3 4 5 6 7 8 9 10 11 12 13 services: dockge: image: louislam/dockge:nightly restart: unless-stopped ports: - 5001:5001 volumes: - ./data:/app/data - /pool/striping/dockge/stacks:/pool/striping/dockge/stacks - /var/run/docker.sock:/var/run/docker.sock environment: - DOCKGE_ENABLE_CONSOLE=true - DOCKGE_STACKS_DIR=/pool/striping/dockge/stacks 在修改之后,请注意新添加的 security_opt 字段 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 services: dockge: image: louislam/dockge:nightly restart: unless-stopped ports: - 5001:5001 volumes: - ./data:/app/data - /pool/striping/dockge/stacks:/pool/striping/dockge/stacks - /var/run/docker.sock:/var/run/docker.sock environment: - DOCKGE_ENABLE_CONSOLE=true - DOCKGE_STACKS_DIR=/pool/striping/dockge/stacks security_opt: - apparmor:unconfined 关闭 AppArmor 对于 Docker的管控 TODO

2025/10/13
articleCard.readMore

AI 时代的自我

自从 GPT3 腾空出世以来已经过了快三年了,大家对 GPT3 这些 LLM的态度也产生了变化:它什么都做不了、它好像能做好一些事情、它要取代我了、它不是什么都能做好。 这十分符合技术成熟度曲线,我想我们,或者说我,已经到达了第四个阶段,对于LLM 的态度和看法也发生了些微的变化。 作为一个程序员、工程师,我的作品和工程对我而言意义丰厚:它是我的经验结晶,我的心智模型的具象化,是我对这个任务抽象后又具象化的结果。而LLM正在尝试取代我:它有着人类几乎所有电子化记录下来的经验、知识,它的水平远超于我,而我还依然坚持着我自己写代码,只是利用LLM 作为帮手、知识库。这和当前的潮流背道而驰:我没有将 LLM纳入我的工作流,它只是个旁观者。 但它没有取代我,并且在长远的未来也取代不了我。 在这里我无意讨论技术问题,但要知道一个基础概念:运算资源不是无限的,而计算机的运作很大程度是基于很多个"假如"来运行: 假如这个东西会在多久后被替代:UNIX 时间戳,新千年虫问题 假如进程不会超过多少个:Linux 内核进程管理从位图更换为基树 我作为一个工程师需要长期学习,不断的学习建模方式,强化我对工程的理解,最终我对工程的理解、对世界的构建会成为我的常识,成为我无意识就能做到的事情。但LLM 做不到,我们传递给 LLM的心智模型只会是一些碎片。莫拉维克悖论告诉我们,计算机模拟人类所独有的高阶智慧不需要很多的资源即可完成计算,但模拟人类无意识做出的行为反而需要巨大的算力。 这正好说明了一个现象:我在创造新模式的时候只需要花费大量的时间,因为需要总结我之前的经验,但LLM在这方面并不需要太多时间,他们有十足丰厚的知识来做到这点;但在维护已有模式时LLM 反而表现得不如人类,即使 LLM的知识水平可以说远超过人类。不如人类不是说 LLM的质量不如人类,而是说在同一个质量的情况下和 LLM合作完成一件事所花费的时间是大于熟练的人类工程师,因为熟练的人类工程师在无意识的情况下完成了诸多的工作,这是非常快速且无意识的 这和我在开源社区积累下来的感受也相符合:优质的 PR往往小而完整,他运作在已经构建好的框架之上,符合我们当初建立的心智模型。我的导师经常提示我我应该将一个PR 拆分成为多个 PR,这样每次提交 PR导师们都能轻轻松松的审查这些新增的代码,使其符合项目的心智模型。并且在这个过程中我也会加深对于项目的理解,将实际构建出来的项目转换成为我对于工程的理解,并且建立属于我自己的心智模型。 但 Vibe Coding 的出现打乱了这个良好的循环:我可以说大多数通过Vibe Coding 得到的 PR 并不符合这个良好的循环模式。为什么? 为什么Vibe Coding 打破了建立心智模型的良好的循环模式? 在提交 PR之前需要对项目有一定程度的了解,即使这个了解是错误的。这和培养人才、工程师的线路是一致的,需要他们对于项目有一定的了解。但Vibe Coding,或者说依赖 LLM 来做这件事很显然是一种"偷懒"。 当有开发能力的用户萌发出一个想法,或者他觉得他有能力解决一个开源软件的缺陷、有能力贡献出一个新功能,那么他就必须阅读相关的代码。当今LLM 可以代替他做到这一点,替他完成阅读并且修补缺陷、实现新功能 然后压力就来到我们维护者身上了:我们需要知道这些代码到底是不是符合我们的、项目的心智模型。想象一下,一个PR出色的完成了他所述的任务,但你仔细看代码,你发现了不少代码晦涩难懂,于是你作为维护者需要询问贡献者这些代码为什么、能不能有更好的写法,但贡献者贴上来一段LLM 生成的解释,此时你是什么想法? 好的结果,就是你这些代码你理解了并且有能力改写出更简单的代码,这些代码符合了工程的模型。 坏结果,这些代码依然晦涩难懂,并且提交代码的贡献者再也没有了回信,PR进入了 miss-assignment 状态。 很遗憾的是,在当前 LLM 或者说 Vibe Coding的水平之下,后者是常态。 在阅读他人博客的时候,有幸读到一篇十分有价值的论文,Programming as TheoryBuilding programming properly should be regarded as an activity by which theprogrammers form or achieve a certain kind of insight, a theory, of thematters at hand 正确的编程应当被视为一种活动,通过它,程序员形成或获得对所处理问题的某种洞察、一种理论。 我想这是大多数程序员、学生都应该要有的一种基础意识。Vibe Coding的出现打破了这个健康的循环模式,甚至使得不少程序员也失去了这个基础意识。 但事情并不是绝对的:开源项目可以看作是我自己的一亩三分地,我愿意花费巨量的心血去维护;但我还有工作,而工作只在乎我的产出,因此需要我提高效率。 回到我刚刚提到的一点,工程师在接手一个项目的时候很难快速建立正确的完整的心智模型,但是在LLM 的帮助下却能快速建立一个简单的、大概正确的心智模型:通过询问LLM的方式了解这一部分代码的工作,在单元测试和回归测试中验证对这部分代码的修改是否正确。 在互联网上,Vibe Coding 只是 LLM开始大规模应用的一种表现,而有趣的是,还有另外一种表现。 "@grok, check fact." 在推特的一些消息类型、历史讲解的一些帖子之下我们经常能看到有人向 Grok查询事实,我觉得这是一个很有趣的现象:这样和 Vibe Coding本质是一件事,只是不 coding 而已。 让 Grok 来搜索数据,整合数据,然后告诉用户结论,这何尝不也是一种vibe? LLM 有提高我的效率吗? 毋庸置疑地讲,要分开来讨论: 是否是我熟悉的领域? LLM 是否在这方面有十分丰厚的知识积累? LLM 的知识是否过时 很多时候我是在我熟悉的领域工作,这样的时候在编写代码和设计时几乎是无意识的行为,我会利用我熟悉地再熟悉不过的方式来进行建模和编写代码。 但如果我在这个时候插入 Vibe Coding 环节,我需要花费大量的时间去查看LLM写出的代码是否正确;但我对我自己亲手编写的代码有充分自信他几乎是正确的——只要能通过单元测试和回归测试。 我现在还在做系统方面的开发,维护着 uutils 之下的procps,在这个领域 LLM没有帮助我太多忙,即使是我不熟悉的领域。 为什么?因为 LLM 在这方面的知识积累并不多,即使 procps的命令基本上已经存在了二十年左右了,但 LLM对这些命令还是知之甚少,而且还会因为幻觉问题开始考虑奇奇怪怪的边角情况,这些情况很难说能遇到,而我们向来是将这些情况等测试集不通过或者用户汇报有问题的时候再开始调查——这也不符合我们建立心智模型的路径。 换句话来讲,我接触的领域是: LLM 知识过时 LLM 并没有积累太多知识 的领域,所以很难说得上 LLM 在这些事情上有提高我的效率。 所以应该不使用 LLM? 不,这不是我的结论。实际上,我也会利用 LLM 在编程方面帮助我: 帮我编写 FFmpeg 命令行 帮我理解函数的作用 帮我确认注释是否易懂 在这些小方面,LLM 做得十分出色。甚至于在编程之外,我也会利用 LLM来帮我一些忙: 一篇论文给 LLM,使其帮我提炼大纲 看不懂的算法让他解释 但如果我真的想完全理解一个东西的时候,我会选择关闭 LLM。 一个很形象的例子:如果我要去往一个地方,我骑着电瓶车肯定比我骑着自行车更加快速。但如果我其实只想通过骑自行车锻炼自己,那么我骑电瓶车对我有什么好处吗?当然没有。 所以我认为,LLM对于快速探索某个话题,并且更快完成任务方面,他们绝对是无二的好帮手。而如果你想真正的掌握一项技能,你更需要的是去积累丰厚的经验,而不是去找一个捷径——相信我,在长期依赖LLM 之后一旦你失去了 LLM ,你会变得一无所知。 那如果利用 LLM 抹平学习路上的所有困难呢?我觉得利用 LLM抹平路上的一些边角上的困难是可以的,因为在解决这些困难的路上很难说得上学到对我们有使用价值的知识。但如果完全抹平我认为这或许不是一件好事。 人类在解决困难的路途中会不断积累经验,这些经验能构筑独一无二的你我,成为我们成长路上最宝贵的部分。但...如果让LLM替代我们做这件事,或许我们会感到大脑光滑十分爽快,在结束后我们如果回顾路途会感到一无所知和空虚:因为困难全部被LLM 解决了,我们几乎没有参与这件事本身。 但 LLM还有个优点也十分明显,毕竟它叫作大语言模型。 如果你和我一样,将 LLM 作为一个帮手、教科书,你会发现 LLM的一个巨大的优点:几乎所有的 LLM都是有无限的耐心,他们会一直陪着我们,也几乎不会有掉线一说。我能在深夜情绪很差的时候寻找LLM 聊天,说出我的想法;我能在周围没有人可以回答我的问题的时候向 LLM咨询一些事情;完全不擅长社交的我在和 LLM聊天的时候几乎感觉不到任何压力(即使我几乎很少和 LLM 闲聊) 这就是赛博生命(如果你能接受这种叫法的话,或者叫其他的类似的)的优点,我和他不在一个维度,我对他没有心理负担。 我在其他博客中看到个类似的观点: LLM 的出现和当初的 Google 出现有十分的相似之处,在 Google这种搜索引擎出现之前人们只能通过在图书馆中不断的查找书籍和不断的询问教师教授等方式获取资料,但Google出现后改变了这一点:现在人们可以在深夜在互联网上毫无顾虑地查找资料,不需要担心打扰到谁,也能节约大量的时间不再需要去图书馆。 或许一年后我对 LLM 的看法又会变了呢,毕竟离 GPT3横空出世,到现在也不过 2 年。 谨以此文告慰此时于 LLM 时代找到"自我"的我。

2025/8/23
articleCard.readMore

支持删除的布隆过滤器

一般情况下布隆过滤器只能填入不能删除,有些特别的需求比如支持读写删的系统就会需要支持删除的布隆过滤器 原始布隆过滤器 我们先看看原始的布隆过滤器是个怎么回事 前提假设 原始的布隆过滤器有几个很重要的点,其实是个非常简单的东西 一个哈希函数:\(hash(T)\) 注意,该哈希函数最好稳定不需要种子,摘要哈希最好。 一个可以被位操作的数据类型 \(T\)(也可以叫做向量?毕竟实际上是由一个十分长的位组成的东西) 一个统计有多少位相同的算法:\(clac(T,T)\) 原始的布隆过滤器使用多个位来作为其内部数据表示,我们这里使用u8 作为 \(T\)的实际类型 1 0000_0000 我们使用上文的 \(hash()\)函数来对输入 \(input\) 做如下处理 \[ result=hash(input) \] 则我们同样得到了一个类型为u8 的 \(result\) ,其内容为0000_0001 判断是否存在 在得到哈希结果后,我们利用这个结果来进行判断 1 2 I: 0000_0001 B: 0000_0000 我们使用的数据类型总共有 8 位,其中相等的位数为7 位,并不完全相等,那么我们可以认为这个元素肯定不存在 写入数据 我们将这个数组写入到这个我们的过滤器里,这样我们的过滤器里的内容就变成了如下内容 1 0000_0001 假如有新的数据,我们就继续按照判断是否存在来判断是否存在于过滤器中 如果需要写入数据,我们就按照上文来写入数据 假阳性(FalsePositive)与误判率 假阳性(误判) 考虑到一种情况,我们的布隆过滤器满了 1 1111_1111 这个时候无论对什么数据进行哈希,得到的结果都会被布隆过滤器判定为存在。那么这里就产生了很重要的问题:误判 误判率 我们做如下假设 布隆过滤器的总位数为 \(m\) 函数 \(hash\) 产生的结果为 \(result\) \(result\) 的位数为 \(k\) 则在插入时布隆过滤器中的某一个特定的位没有被操作的概率是 \[ 1-\frac{1}{m} \] 在完全插入\(result\) 后某一位仍然为0 的概率为 \[ (1-\frac{1}{m})^{k} \] 在执行了 \(n\)次插入后,某一位仍然为 0 的概率就为 \[(1-\frac{1}{m})^{kn}\] 那么很容易得出,某一位为 1 的概率是 \[1-(1-\frac{1}{m})^{kn}\] 我们假设过滤器中所有位均被设置为1,那么认为某一个原本不应该在集合中的元素却被误判的概率则为 \[P=(1-[1-\frac{1}{m}]^{kn})^k\]则我们得到的误判率计算公式为 \(P\)。对于该公式,我们假设 \(m\)趋近于无限,则我们可以进行进一步的处理:令 \(L=(1-\frac{1}{m})^{kn}\),我们对\(L\)进行极限,根据极限的定义我们可以得到如下的结果 \[\lim_{m\to\infty}(L)\approx{e^{-1}}^{\frac{kn}{m}}\] 什么?忘了指数函数的极限了? 简单回忆一下一般形式的指数函数的极限: \[\lim_{m\to\infty}(1-\frac{x}{m})^m=e^{-x}\] 那么我们可以得到其误判率大概为 \[P\approx(1-e^{-x})^k\]

2025/7/29
articleCard.readMore

基于栈的虚拟机与基于寄存器的虚拟机

经常听到 LuaJIT 和 JVM分别是基于寄存器的虚拟机和基于栈的虚拟机,那么这两种虚拟机究竟有什么区别? 最主要的区别 既然都叫作虚拟机了,那么实际上这些 Runtime的最终目的是模仿实体机提供一个可以跨平台运行的环境。 基于寄存器的操作 读过计组和 CSAPP 的朋友们都知道,我们的 x86 机器指令存在一个东西叫作N 地址指令,他看起来像是这样 N=3: ADD a, b, c N=2: ADD a, b ADD 指令后的地址就是目标的地址,并且 ADD命令在完成后会在某个寄存器/内存里放入这次运算的值。经典的 x86采用的是二地址指令,会在某个寄存器里放入这个值 基于栈的操作 那么如果我们的 N 地址 N=0会发生什么?看起来我们没有任何地址可以存放读取。 但如果我们引入一个数据结构来储存值,操作数仅仅能操作当前最新的值会发生什么?基于栈的虚拟机就产生了。 求值栈 我们引入一个栈式结构叫作求值栈,并且假定有如下的命令 1 2 3 4 iconst_1   iconst_2   iadd   istore_0 在 iconst_1 和 iconst_2中我们压入了两个整型值:1,2 然后我们使用了 iadd 命令,iadd命令将会弹出栈顶的两个整型值,并且将其相加后压入栈顶,这样我们的求值栈就只有一个值2 了 在完成以上求值后,我们使用了 istore命令将求值栈最终的值放到局部变量区的 0 号位 istore, istore_<n>: Anistore instruction with operand Index is type safeand yields an outgoing type state NextStackFrame, if astore instruction with operand Index and typeint is type safe and yields an outgoing type stateNextStackFrame. iadd: An iadd instruction is type safe iff onecan validly replace types matching int and inton the incoming operand stack with int yielding theoutgoing type state. 这样看起来就非常的跨平台,因为到目前为止几乎完全不用和平台的寄存器交互,这几乎完全是模拟出来的行为 如果要用 x86 汇编的话,两条指令就可以了: 1 2 mov  eax, 1   add  eax, 2 请注意,求值栈(EvaluateStack)不是一个固定的名字,除了求值栈,还可以叫作表达式栈(ExpressionStack) 各有优劣? 简单列个表会更直观一些 请注意,设计模式并不是固定的,例如 JVM 的 JIT也会面向平台进行特化,这一步实际上也是走向基于寄存器的虚拟机的设计模式,因为这样很明显会更快 基于栈的虚拟机基于寄存器的虚拟机理由 可移植性高低基于寄存器的虚拟机由于需要考虑和本地机器的映射关系,因此需要花很大力气去整理映射关系,但基于栈的虚拟机不需要这层映射关系 实现难易度简单难同上 性能较慢快速基于寄存器的虚拟机由于存在和本地机器的寄存器映射关系,因此运行起来非常的高效,基于栈的虚拟机有额外开销 储存资源占用较低较高由于基于栈的虚拟机使用的是零地址指令,它的占用比其他任何指令都小并且更加紧凑。所以基于栈的虚拟机对于硬盘占用会更小 参照 虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 JavaVirtual Machine Specification: 4.10.1.9. Type CheckingInstructions

2025/7/20
articleCard.readMore

RVA23 包含了什么

Ubuntu计划在 25.10 的 RISC-V 架构中只支持 RVA23 配置文件,于是打算大概看下RVA23 有什么是比较重大的指令集成为了必选 For Ubuntu 25.10 release we plan to raise the required RISC-V ISAprofile family to RVA23. The ubuntu-release-upgrader should stop upgrades beyond Ubuntu 24.04on hardware that does not support the RVA23U64 profile. RVA23U64 [1] isthe profile relevant for user space. As there is no upgrade path from Ubuntu 25.04 Plucky for RVA20systems, we should also stop upgrading these RISC-V systems from Nobleto Plucky. Probably a warning is adequate here. RVA23 配置文件旨在规范 RISC-V 64位应用处理器的实现,使二进制软件生态系统能够依赖大量有保障的扩展功能和少量可发现的粗粒度选项。RVA23明确不以支持最小功能集和大量细粒度扩展来实现更大的硬件实现灵活性为目标。 指令集 该系列仅规定了用户模式(RVA23U64)和管理模式(RVA23S64)两种配置方案。 用户模式强制性基础 拓展解释备注 RV64IThe mandatory base ISA for RVA23U64 and islittle-endian.As per the unprivileged architecturespecification, the ECALL instruction causes a requested trap to theexecution environment. 用户模式强制性拓展 拓展解释备注 A原子指令 B位操作指令 C压缩指令 D双精度浮点指令 F单精度浮点指令 M整数乘除法指令 Zkt数据无关执行延迟控制 ZicsrCSR 控制状态寄存器指令(F扩展隐含此功能) Zihpm硬件性能计数器 Za64rs保留集必须为 64 字节连续自然对齐空间 Zfhmin半精度浮点运算 Zic64b缓存块必须为 64字节大小且在地址空间中自然对齐 Zicbom缓存块管理指令 Zicbop缓存块预取指令 Zicboz缓存块清零指令 Ziccif具有可缓存性和一致性的主存区域必须支持指令获取,且对自然对齐的2 的幂大小(最大 min(ILEN,XLEN),RVA23 为 32位)的指令获取必须是原子的 Zicclsm必须支持对具有可缓存性和一致性的主存区域进行非对齐加载/存储操作 Ziccrse具有可缓存性和一致性的主存区域必须支持RsrvEventual 特性 Zicntr基础计数器和定时器 Ziccamoa具有可缓存性和一致性的主存区域必须支持 A扩展中的所有原子操作 Zihintpause暂停提示指令 RVA23U64 新增了以下强制性扩展: 拓展解释备注 V向量扩展注:V 在 RVA22U64 中是可选扩展 Zcb额外压缩指令集 Zfa额外浮点指令集 Supm指针掩码功能,执行环境至少需支持 PMLEN=0和 PMLEN=7 的设置 Zawrs等待保留集指令 Zcmop压缩型可能操作指令 Zimop可能操作指令 Zvbb向量基础位操作指令 Zvkt向量数据无关执行延迟控制 Zicond整数条件操作指令 Zvfhmin向量最小半精度浮点运算 Zihintntl非临时局部性提示指令 用户模式可选拓展 拓展解释备注 Zbc标量无进位乘法 Zfh标量半精度浮点运算包含在 RVA22U64 标准中 Zvbc向量无进位乘法 Zvfh向量半精度浮点运算 Zabha字节与半字原子内存操作 Zacas比较并交换指令 Zvkng带 GCM 的向量密码学 NIST 算法标量密码扩展 Zkn/Zks已移除;向量密码变为强制要求且性能显著提升;仅包含 GCM版本以优化性能 Zvksg带 GCM 的向量密码学商密算法同 Zvkng Zama16b未跨越 16字节边界的非对齐加载/存储/原子内存操作保持原子性实现非对齐原子粒度(Sm1p13);将加入特权架构的PMA 章节 Zfbfmin标量 BF16 格式转换 Ziccamoc具有可缓存性/一致性 PMA的主存区域必须支持AMOCASQ级别的 PMA确保支持 CAS 指令;将加入特权架构的 PMA章节 Zicfilp着陆垫机制 Zicfiss影子栈 Zvfbfmin向量 BF16 格式转换 Zvfbfwma向量 BF16 扩展乘加运算 管理模式强制性拓展 RVA23S64 除了包含所有 RVA23U64的所有强制性拓展外还包含了如下的强制性拓展 拓展解释备注 Ss1p13监管者架构版本 1.13Ss1p13 取代了 Ss1p12。 Ssccptr具有可缓存性和一致性 PMA 的主存区域必须支持硬件页表读取 Sscofpmf计数器溢出和基于模式的过滤 Sscounterenw对于任何非只读零的hpmcounter,scounteren中的相应位必须可写 Ssnpm指针掩码,senvcfg.PMM和henvcfg.PMM至少支持PMLEN=0 和 PMLEN=7 的设置 Sstc监管者模式定时器中断Sstc 在 RVA22 中是可选的 Sstvala对于加载、存储和指令页面错误、访问错误及不对齐异常,以及由非EBREAK或C.EBREAK指令执行引起的断点异常,stval必须写入引发错误的虚拟地址。对于虚拟指令和非法指令异常,stval必须写入引发错误的指令 Sstvecdstvec.MODE必须能够保存值0(直接模式)。当stvec.MODE=Direct时,stvec.BASE必须能够保存任何有效的四字节对齐地址 Ssu64xlsstatus.UXL必须能够保存值 2(即必须支持 UXLEN=64)Ssu64xl 在 RVA22 中是可选的 Sv39基于页的 39 位虚拟内存系统 Svade当 A 位清零时访问页面或 D 位清零时写入页面会引发页面错误异常 Svbare必须支持satp模式的 Bare Svinval细粒度地址转换缓存无效化 SvnapotNAPOT 转换连续性Svnapot 在 RVA22 中是可选的 Svpbmt基于页的内存类型 Zifencei指令获取屏障Zifencei 是 RVA23应用处理器中支持指令缓存一致性的唯一标准方式,因此被强制要求。新的指令缓存一致性机制正在开发中(暂定名为Zjid),未来可能作为可选功能加入。 其中 Sha 拓展包含了如下拓展 拓展解释备注 H虚拟机监控程序扩展 Shcounterenw对于任何非只读零的hpmcounter,hcounteren中的相应位必须可写 Shgatpa对于satp中支持的每个虚拟内存方案 SvNN,必须支持相应的hgatp SvNNx4 模式。同时必须支持hgatp的 Bare 模式增强型虚拟机监控程序扩展(完全等同于 Sha)在 RVA22 中是可选的 Shtvala在 ISA允许的所有情况下,htval必须写入引发错误的客户机物理地址 Shvsatpasatp中支持的所有转换模式都必须在vsatp中支持 Shvstvala在上述所有stval描述的情况下,都必须写入vstval Shvstvecdvstvec.MODE必须能够保存值0(直接模式)。当vstvec.MODE=Direct时,vstvec.BASE必须能够保存任何有效的四字节对齐地址 Ssstateen状态使能扩展的监管者模式视图。必须提供监管者模式(sstateen0-3)和虚拟机监控程序模式(hstateen0-3)的状态使能寄存器 管理模式可选拓展 拓展解释备注 Sdtrig调试触发器 Sspm监管者模式指针掩码,执行环境至少需支持 PMLEN=0 和 PMLEN=7的设置 Ssstrict不存在非标准扩展。尝试执行未实现的指令或访问标准/保留编码空间中未实现的CSR 将触发非法指令异常Ssstrict 是新的配置文件定义扩展,用于限制保留编码空间的行为。注1:Ssstrict 不规定自定义编码空间或 CSR 的行为。注 2:Ssstrict定义适用于声称兼容 RVA23的执行环境,该环境必须包含虚拟机监控程序扩展。 Sv48基于页的 48 位虚拟内存系统 Sv57基于页的 57 位虚拟内存系统 Svadu硬件自动更新 A/D 位 Svvptc从无效 PTE 到有效 PTE的转换将在有限时间内可见,无需显式内存管理屏障 Zkr熵 CSR 观测 对于 RVA23 来讲,最显著的是之前 RVA22 的 V拓展现在是强制性的,我想这也是面向 AI 时代的一个必然。如果一个 CPU没有向量加速相关的拓展那么这个 CPU 的效率是无法达到现代的标准的。 除此之外,Sha 现在也成为了必选,这是在 RISC-V上运行虚拟机的基础 这也是 RVA23 的主打卖点: Vector Extension: The Vector extension accelerates math-intensiveworkloads, including AI/ML, cryptography, and compression /decompression. Vector extensions yield better performance in mobile andcomputing applications with RVA23 as the baseline requirement for theAndroid RISC-V ABI. Hypervisor Extension: The Hypervisor extension will enablevirtualization for enterprise workloads in both on-premises server andcloud computing applications. This will accelerate the development ofRISC-V-based enterprise hardware, operating systems, and softwareworkloads. The Hypervisor extension will also provide better securityfor mobile applications by separating secure and non-securecomponents. 和 x86_64v4 的区别 由于我用的是 Zen4 系列的 Ryzen9 7945HX,所以以我的 CPU 为标准 包含如下指令集 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov patpse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gbrdtscp lm constant_tsc rep_good amd_lbr_v2 nopl xtopology nonstop_tsccpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lmcmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetchosvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpextperfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba perfmon_v2 ibrsibpb stibp ibrs_enhanced vmmcall fsgsbase bmi1 avx2 smep bmi2 ermsinvpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushoptclwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsavescqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local user_shstk avx512_bf16clzero irperf xsaveerptr rdpru wbnoinvd cppc arat npt lbrv svm_locknrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilterpfthreshold avic vgif x2avic v_spec_ctrl vnmi avx512vbmi umip pku ospkeavx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalgavx512_vpopcntdq rdpid overflow_recov succor smca fsrm flush_l1damd_lbr_pmc_freeze 可以注意到,x86_64v4上甚至有能效控制相关的指令集:apic, cppc,rapl, monitor。但是 RVA23 是没有的 以上只是非常主观的观测,指令集的指令还有更细致的区别但是因为太多了(而且我不太懂)就算了 不足之处? 指令集只是硬件层面的一部分,在硬件之上的软件支持也十分重要。这一点很依赖编译器和上游软件的实现,很可惜的是截至当前编译器和上游软件对RVA23 的支持并不明朗,请查看 Yangyu Chen 的研究,原因讲的非常详细

2025/7/12
articleCard.readMore

Rust 的边界检查是否已经有很大的进步

最近又听到有人说 Rust做不了高性能程序的原因是内插的边界检查会降低程序运行速度,我想这么多年了应该Rust开发团队不会不知道这个问题,因此做了个简单的实验来测试一下是不是这方面已经有了长足的进步 源码 我们有如下代码,该代码为N 皇后问题的算法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 use std::time::{SystemTime, UNIX_EPOCH}; const N: i32 = 13; fn clock_realtime() -> i64 { let start = SystemTime::now(); let since_the_epoch = start .duration_since(UNIX_EPOCH) .expect("Time went backwards"); since_the_epoch.as_millis() as i64 } fn array_check(array: &[i32], row: i32) -> bool { if row == 0 { true } else { let x0 = array[row as usize]; for y in 0..row { let x = array[y as usize]; if x == x0 { return false; } else if x - x0 == row - y { return false; } else if x0 - x == row - y { return false; } } true } } fn queen() -> i32 { let mut array = [0; N as usize]; let mut found = 0; let mut row = 0; let mut done = false; while !done { if array_check(&array, row) { if row == N - 1 { found += 1; } else { row += 1; array[row as usize] = 0; continue; } } array[row as usize] += 1; while array[row as usize] >= N { row -= 1; if row >= 0 { array[row as usize] += 1; } else { done = true; break; } } } found } fn main() { queen(); let ts = clock_realtime(); let found = queen(); let dt = clock_realtime() - ts; println!("found={} time={} ms", found, dt); } 我们需要查看汇编,看看 panic 相关的汇编是不是变少 从汇编角度分析 在 Rust 1.88 得到的涉及到边界检查的汇编如下 1 2 3 4 .LBB6_40: leaq.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.7(%rip), %rdx movl$13, %esi callq*_ZN4core9panicking18panic_bounds_check17hda0827d94e974e71E@GOTPCREL(%rip) 在 Rust 1.42 得到的涉及到边界检查的汇编如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 .LBB4_19: .cfi_def_cfa_offset 64 leaq.L__unnamed_2(%rip), %rdi movl$13, %esi movl$13, %edx callq*_ZN4core9panicking18panic_bounds_check17h09b793daa6d169ffE@GOTPCREL(%rip) ud2 .LBB4_25: leaq.L__unnamed_3(%rip), %rdi movl$13, %edx callq*_ZN4core9panicking18panic_bounds_check17h09b793daa6d169ffE@GOTPCREL(%rip) ud2 .LBB4_17: leaq.L__unnamed_4(%rip), %rdi movl$13, %edx callq*_ZN4core9panicking18panic_bounds_check17h09b793daa6d169ffE@GOTPCREL(%rip) ud2 .LBB4_15: leaq.L__unnamed_5(%rip), %rdi movl$13, %edx callq*_ZN4core9panicking18panic_bounds_check17h09b793daa6d169ffE@GOTPCREL(%rip) ud2 .LBB4_9: leaq.L__unnamed_6(%rip), %rdi movl$13, %edx callq*_ZN4core9panicking18panic_bounds_check17h09b793daa6d169ffE@GOTPCREL(%rip) ud2 可以看到很明显是从五个减少到了一个,说明 Rust 本身也在不断进步 附录 1.完整的 Rust 1.88.0 输出汇编 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 .file"boundtest.375cfcca86286996-cgu.0" .section.text._ZN3std2rt10lang_start17h3c102020d822bd07E,"ax",@progbits .hidden_ZN3std2rt10lang_start17h3c102020d822bd07E .globl_ZN3std2rt10lang_start17h3c102020d822bd07E .p2align4 .type_ZN3std2rt10lang_start17h3c102020d822bd07E,@function _ZN3std2rt10lang_start17h3c102020d822bd07E: .cfi_startproc pushq%rax .cfi_def_cfa_offset 16 movl%ecx, %r8d movq%rdx, %rcx movq%rsi, %rdx movq%rdi, (%rsp) leaq.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.0(%rip), %rsi movq%rsp, %rdi callq*_ZN3std2rt19lang_start_internal17ha8ef919ae4984948E@GOTPCREL(%rip) popq%rcx .cfi_def_cfa_offset 8 retq .Lfunc_end0: .size_ZN3std2rt10lang_start17h3c102020d822bd07E, .Lfunc_end0-_ZN3std2rt10lang_start17h3c102020d822bd07E .cfi_endproc .section".text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h93d859901d9a3bf1E","ax",@progbits .p2align4 .type_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h93d859901d9a3bf1E,@function _ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h93d859901d9a3bf1E: .cfi_startproc pushq%rax .cfi_def_cfa_offset 16 movq(%rdi), %rdi callq_ZN3std3sys9backtrace28__rust_begin_short_backtrace17h8132116926a6755eE xorl%eax, %eax popq%rcx .cfi_def_cfa_offset 8 retq .Lfunc_end1: .size_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h93d859901d9a3bf1E, .Lfunc_end1-_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h93d859901d9a3bf1E .cfi_endproc .section.text._ZN3std3sys9backtrace28__rust_begin_short_backtrace17h8132116926a6755eE,"ax",@progbits .p2align4 .type_ZN3std3sys9backtrace28__rust_begin_short_backtrace17h8132116926a6755eE,@function _ZN3std3sys9backtrace28__rust_begin_short_backtrace17h8132116926a6755eE: .cfi_startproc pushq%rax .cfi_def_cfa_offset 16 callq*%rdi #APP #NO_APP popq%rax .cfi_def_cfa_offset 8 retq .Lfunc_end2: .size_ZN3std3sys9backtrace28__rust_begin_short_backtrace17h8132116926a6755eE, .Lfunc_end2-_ZN3std3sys9backtrace28__rust_begin_short_backtrace17h8132116926a6755eE .cfi_endproc .section".text._ZN42_$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$3fmt17haf17d8060b4e03d4E","ax",@progbits .p2align4 .type_ZN42_$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$3fmt17haf17d8060b4e03d4E,@function _ZN42_$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$3fmt17haf17d8060b4e03d4E: .cfi_startproc movq(%rdi), %rdi jmpq*_ZN57_$LT$core..time..Duration$u20$as$u20$core..fmt..Debug$GT$3fmt17h7281b74b0c14c846E@GOTPCREL(%rip) .Lfunc_end3: .size_ZN42_$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$3fmt17haf17d8060b4e03d4E, .Lfunc_end3-_ZN42_$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$3fmt17haf17d8060b4e03d4E .cfi_endproc .section".text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17hfb43ae5ef4974b72E","ax",@progbits .p2align4 .type_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17hfb43ae5ef4974b72E,@function _ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17hfb43ae5ef4974b72E: .cfi_startproc pushq%rax .cfi_def_cfa_offset 16 movq(%rdi), %rdi callq_ZN3std3sys9backtrace28__rust_begin_short_backtrace17h8132116926a6755eE xorl%eax, %eax popq%rcx .cfi_def_cfa_offset 8 retq .Lfunc_end4: .size_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17hfb43ae5ef4974b72E, .Lfunc_end4-_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17hfb43ae5ef4974b72E .cfi_endproc .section".text._ZN63_$LT$std..time..SystemTimeError$u20$as$u20$core..fmt..Debug$GT$3fmt17hdc81834098293439E","ax",@progbits .p2align4 .type_ZN63_$LT$std..time..SystemTimeError$u20$as$u20$core..fmt..Debug$GT$3fmt17hdc81834098293439E,@function _ZN63_$LT$std..time..SystemTimeError$u20$as$u20$core..fmt..Debug$GT$3fmt17hdc81834098293439E: .cfi_startproc pushq%rax .cfi_def_cfa_offset 16 movq%rsi, %rax movq%rdi, (%rsp) leaq.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.3(%rip), %rsi leaq.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.2(%rip), %r8 movq%rsp, %rcx movl$15, %edx movq%rax, %rdi callq*_ZN4core3fmt9Formatter25debug_tuple_field1_finish17h06aa1c014801bdacE@GOTPCREL(%rip) popq%rcx .cfi_def_cfa_offset 8 retq .Lfunc_end5: .size_ZN63_$LT$std..time..SystemTimeError$u20$as$u20$core..fmt..Debug$GT$3fmt17hdc81834098293439E, .Lfunc_end5-_ZN63_$LT$std..time..SystemTimeError$u20$as$u20$core..fmt..Debug$GT$3fmt17hdc81834098293439E .cfi_endproc .section.text._ZN9boundtest4main17ha95ec5995628f7dfE,"ax",@progbits .p2align4 .type_ZN9boundtest4main17ha95ec5995628f7dfE,@function _ZN9boundtest4main17ha95ec5995628f7dfE: .cfi_startproc pushq%rbp .cfi_def_cfa_offset 16 pushq%r14 .cfi_def_cfa_offset 24 pushq%rbx .cfi_def_cfa_offset 32 subq$112, %rsp .cfi_def_cfa_offset 144 .cfi_offset %rbx, -32 .cfi_offset %r14, -24 .cfi_offset %rbp, -16 xorps%xmm0, %xmm0 movaps%xmm0, 32(%rsp) movaps%xmm0, 16(%rsp) movaps%xmm0, (%rsp) movl$0, 48(%rsp) xorl%edx, %edx movq%rsp, %rax jmp.LBB6_1 .p2align4 .LBB6_3: movl$1, %edx movl$1, %ecx movl$0, (%rsp,%rcx,4) .LBB6_1: movslq%edx, %rdi movl%edi, %ecx jmp.LBB6_2 .p2align4 .LBB6_10: incl%esi movl%esi, (%rsp,%rcx,4) cmpl$13, %esi jge.LBB6_14 .LBB6_2: testl%edx, %edx je.LBB6_3 cmpl$12, %ecx ja.LBB6_40 movl(%rsp,%rdi,4), %esi movq%rax, %r8 movq%rcx, %r10 movq%rcx, %r9 .p2align4 .LBB6_6: subq$1, %r9 jb.LBB6_11 movl(%r8), %r11d movl%r11d, %ebx subl%esi, %ebx je.LBB6_10 cmpl%ebx, %r10d je.LBB6_10 movl%esi, %ebx subl%r11d, %ebx addq$4, %r8 cmpl%ebx, %r10d movq%r9, %r10 jne.LBB6_6 jmp.LBB6_10 .p2align4 .LBB6_11: cmpl$12, %edx jne.LBB6_17 incl48(%rsp) movl(%rsp,%rcx,4), %esi cmpl$13, %esi jl.LBB6_2 .p2align4 .LBB6_14: subq$1, %rcx jb.LBB6_19 movl(%rsp,%rcx,4), %edx incl%edx movl%edx, (%rsp,%rcx,4) cmpl$12, %edx jg.LBB6_14 movl%ecx, %edx jmp.LBB6_1 .LBB6_17: incl%ecx movl%ecx, %edx movl$0, (%rsp,%rcx,4) jmp.LBB6_1 .LBB6_19: callq*_ZN3std4time10SystemTime3now17h1481857dbd8e3482E@GOTPCREL(%rip) movq%rax, 64(%rsp) movl%edx, 72(%rsp) xorl%ebx, %ebx movq%rsp, %rdi leaq64(%rsp), %rsi xorl%edx, %edx xorl%ecx, %ecx callq*_ZN3std4time10SystemTime14duration_since17hf6dc8eb19471745bE@GOTPCREL(%rip) cmpl$1, (%rsp) je.LBB6_39 movq8(%rsp), %r14 movl16(%rsp), %ebp xorps%xmm0, %xmm0 movaps%xmm0, 32(%rsp) movaps%xmm0, 16(%rsp) movaps%xmm0, (%rsp) movl$0, 48(%rsp) xorl%eax, %eax jmp.LBB6_21 .p2align4 .LBB6_22: movl$1, %ecx .LBB6_37: movl$0, (%rsp,%rcx,4) incl%eax .LBB6_21: testl%eax, %eax je.LBB6_22 movslq%eax, %rdi cmpl$12, %eax ja.LBB6_40 movl%edi, %ecx movl(%rsp,%rdi,4), %edx movl%eax, %edi xorl%esi, %esi .p2align4 .LBB6_25: cmpq%rsi, %rcx je.LBB6_30 movl(%rsp,%rsi,4), %r8d movl%r8d, %r9d subl%edx, %r9d je.LBB6_29 cmpl%r9d, %edi je.LBB6_29 incq%rsi movl%edx, %r9d subl%r8d, %r9d leal-1(%rdi), %r8d cmpl%r9d, %edi movl%r8d, %edi jne.LBB6_25 .LBB6_29: incl%edx movl%edx, (%rsp,%rcx,4) cmpl$13, %edx jl.LBB6_21 .p2align4 .LBB6_33: subq$1, %rcx jb.LBB6_38 movl(%rsp,%rcx,4), %eax incl%eax movl%eax, (%rsp,%rcx,4) cmpl$12, %eax jg.LBB6_33 movl%ecx, %eax jmp.LBB6_21 .p2align4 .LBB6_30: cmpl$12, %eax je.LBB6_31 leal1(%rax), %ecx jmp.LBB6_37 .LBB6_31: incl%ebx incl48(%rsp) movl(%rsp,%rcx,4), %edx cmpl$13, %edx jl.LBB6_21 jmp.LBB6_33 .LBB6_38: movl%ebx, 60(%rsp) callq*_ZN3std4time10SystemTime3now17h1481857dbd8e3482E@GOTPCREL(%rip) movq%rax, 64(%rsp) movl%edx, 72(%rsp) movq%rsp, %rdi leaq64(%rsp), %rbx movq%rbx, %rsi xorl%edx, %edx xorl%ecx, %ecx callq*_ZN3std4time10SystemTime14duration_since17hf6dc8eb19471745bE@GOTPCREL(%rip) cmpl$1, (%rsp) je.LBB6_39 movl%ebp, %eax imulq$1125899907, %rax, %rax shrq$50, %rax movq8(%rsp), %rcx subq%r14, %rcx movl16(%rsp), %edx imulq$1125899907, %rdx, %rdx shrq$50, %rdx subq%rax, %rdx imulq$1000, %rcx, %rax addq%rdx, %rax movq%rax, 64(%rsp) leaq60(%rsp), %rax movq%rax, 80(%rsp) movq_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$i32$GT$3fmt17h863d77ac4b43588eE@GOTPCREL(%rip), %rax movq%rax, 88(%rsp) movq%rbx, 96(%rsp) movq_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$i64$GT$3fmt17hde631ae64c57a835E@GOTPCREL(%rip), %rax movq%rax, 104(%rsp) leaq.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.11(%rip), %rax movq%rax, (%rsp) movq$3, 8(%rsp) movq$0, 32(%rsp) leaq80(%rsp), %rax movq%rax, 16(%rsp) movq$2, 24(%rsp) movq%rsp, %rdi callq*_ZN3std2io5stdio6_print17h915f3273edec6464E@GOTPCREL(%rip) addq$112, %rsp .cfi_def_cfa_offset 32 popq%rbx .cfi_def_cfa_offset 24 popq%r14 .cfi_def_cfa_offset 16 popq%rbp .cfi_def_cfa_offset 8 retq .LBB6_39: .cfi_def_cfa_offset 144 movq8(%rsp), %rax movl16(%rsp), %ecx movq%rax, 80(%rsp) movl%ecx, 88(%rsp) leaq.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.4(%rip), %rdi leaq.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.1(%rip), %rcx leaq.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.6(%rip), %r8 leaq80(%rsp), %rdx movl$19, %esi callq*_ZN4core6result13unwrap_failed17h727108008d9f4c9bE@GOTPCREL(%rip) .LBB6_40: leaq.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.7(%rip), %rdx movl$13, %esi callq*_ZN4core9panicking18panic_bounds_check17hda0827d94e974e71E@GOTPCREL(%rip) .Lfunc_end6: .size_ZN9boundtest4main17ha95ec5995628f7dfE, .Lfunc_end6-_ZN9boundtest4main17ha95ec5995628f7dfE .cfi_endproc .section.text.main,"ax",@progbits .globlmain .p2align4 .typemain,@function main: .cfi_startproc pushq%rax .cfi_def_cfa_offset 16 movq%rsi, %rcx movslq%edi, %rdx leaq_ZN9boundtest4main17ha95ec5995628f7dfE(%rip), %rax movq%rax, (%rsp) leaq.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.0(%rip), %rsi movq%rsp, %rdi xorl%r8d, %r8d callq*_ZN3std2rt19lang_start_internal17ha8ef919ae4984948E@GOTPCREL(%rip) popq%rcx .cfi_def_cfa_offset 8 retq .Lfunc_end7: .sizemain, .Lfunc_end7-main .cfi_endproc .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.0,@object .section.data.rel.ro..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.0,"aw",@progbits .p2align3, 0x0 .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.0: .asciz"\000\000\000\000\000\000\000\000\b\000\000\000\000\000\000\000\b\000\000\000\000\000\000" .quad_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17hfb43ae5ef4974b72E .quad_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h93d859901d9a3bf1E .quad_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17h93d859901d9a3bf1E .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.0, 48 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.1,@object .section.data.rel.ro..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.1,"aw",@progbits .p2align3, 0x0 .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.1: .asciz"\000\000\000\000\000\000\000\000\020\000\000\000\000\000\000\000\b\000\000\000\000\000\000" .quad_ZN63_$LT$std..time..SystemTimeError$u20$as$u20$core..fmt..Debug$GT$3fmt17hdc81834098293439E .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.1, 32 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.2,@object .section.data.rel.ro..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.2,"aw",@progbits .p2align3, 0x0 .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.2: .asciz"\000\000\000\000\000\000\000\000\b\000\000\000\000\000\000\000\b\000\000\000\000\000\000" .quad_ZN42_$LT$$RF$T$u20$as$u20$core..fmt..Debug$GT$3fmt17haf17d8060b4e03d4E .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.2, 32 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.3,@object .section.rodata..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.3,"a",@progbits .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.3: .ascii"SystemTimeError" .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.3, 15 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.4,@object .section.rodata..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.4,"a",@progbits .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.4: .ascii"Time went backwards" .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.4, 19 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.5,@object .section.rodata..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.5,"a",@progbits .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.5: .ascii"src/main.rs" .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.5, 11 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.6,@object .section.data.rel.ro..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.6,"aw",@progbits .p2align3, 0x0 .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.6: .quad.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.5 .asciz"\013\000\000\000\000\000\000\000\t\000\000\000\n\000\000" .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.6, 24 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.7,@object .section.data.rel.ro..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.7,"aw",@progbits .p2align3, 0x0 .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.7: .quad.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.5 .asciz"\013\000\000\000\000\000\000\000\021\000\000\000\022\000\000" .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.7, 24 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.8,@object .section.rodata..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.8,"a",@progbits .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.8: .ascii"found=" .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.8, 6 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.9,@object .section.rodata..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.9,"a",@progbits .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.9: .ascii" time=" .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.9, 6 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.10,@object .section.rodata.cst4,"aM",@progbits,4 .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.10: .ascii" ms\n" .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.10, 4 .type.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.11,@object .section.data.rel.ro..Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.11,"aw",@progbits .p2align3, 0x0 .Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.11: .quad.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.8 .asciz"\006\000\000\000\000\000\000" .quad.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.9 .asciz"\006\000\000\000\000\000\000" .quad.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.10 .asciz"\004\000\000\000\000\000\000" .size.Lanon.577bbe129332a5e2b73bb2ccd2cfb5e2.11, 48 .ident"rustc version 1.88.0 (6b00bc388 2025-06-23)" .section".note.GNU-stack","",@progbits 2.完整的 Rust 1.42.0 输出汇编 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 .text .file"boundtest.6clktnjp-cgu.0" .section.text._ZN3std2rt10lang_start17h9aebbcf43540bf0fE,"ax",@progbits .hidden_ZN3std2rt10lang_start17h9aebbcf43540bf0fE .globl_ZN3std2rt10lang_start17h9aebbcf43540bf0fE .p2align4, 0x90 .type_ZN3std2rt10lang_start17h9aebbcf43540bf0fE,@function _ZN3std2rt10lang_start17h9aebbcf43540bf0fE: .cfi_startproc pushq%rax .cfi_def_cfa_offset 16 movq%rdx, %rcx movq%rsi, %rdx movq%rdi, (%rsp) leaq.L__unnamed_1(%rip), %rsi movq%rsp, %rdi callq*_ZN3std2rt19lang_start_internal17h9cf8802361ad86c2E@GOTPCREL(%rip) popq%rcx .cfi_def_cfa_offset 8 retq .Lfunc_end0: .size_ZN3std2rt10lang_start17h9aebbcf43540bf0fE, .Lfunc_end0-_ZN3std2rt10lang_start17h9aebbcf43540bf0fE .cfi_endproc .section".text._ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hfcc61cdeabb58ae0E","ax",@progbits .p2align4, 0x90 .type_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hfcc61cdeabb58ae0E,@function _ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hfcc61cdeabb58ae0E: .cfi_startproc pushq%rax .cfi_def_cfa_offset 16 callq*(%rdi) xorl%eax, %eax popq%rcx .cfi_def_cfa_offset 8 retq .Lfunc_end1: .size_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hfcc61cdeabb58ae0E, .Lfunc_end1-_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hfcc61cdeabb58ae0E .cfi_endproc .section".text._ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h85cd4d27ce61189fE","ax",@progbits .p2align4, 0x90 .type_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h85cd4d27ce61189fE,@function _ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h85cd4d27ce61189fE: .cfi_startproc pushq%rax .cfi_def_cfa_offset 16 callq*(%rdi) xorl%eax, %eax popq%rcx .cfi_def_cfa_offset 8 retq .Lfunc_end2: .size_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h85cd4d27ce61189fE, .Lfunc_end2-_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h85cd4d27ce61189fE .cfi_endproc .section.text._ZN4core3ptr13drop_in_place17h8206b4e5dd5bef15E,"ax",@progbits .p2align4, 0x90 .type_ZN4core3ptr13drop_in_place17h8206b4e5dd5bef15E,@function _ZN4core3ptr13drop_in_place17h8206b4e5dd5bef15E: .cfi_startproc retq .Lfunc_end3: .size_ZN4core3ptr13drop_in_place17h8206b4e5dd5bef15E, .Lfunc_end3-_ZN4core3ptr13drop_in_place17h8206b4e5dd5bef15E .cfi_endproc .section.text._ZN9boundtest5queen17h2a40cedb5ec97973E,"ax",@progbits .p2align4, 0x90 .type_ZN9boundtest5queen17h2a40cedb5ec97973E,@function _ZN9boundtest5queen17h2a40cedb5ec97973E: .cfi_startproc subq$56, %rsp .cfi_def_cfa_offset 64 xorps%xmm0, %xmm0 movaps%xmm0, 32(%rsp) movaps%xmm0, 16(%rsp) movaps%xmm0, (%rsp) movl$0, 48(%rsp) xorl%eax, %eax xorl%r8d, %r8d testl%r8d, %r8d jne.LBB4_4 .p2align4, 0x90 .LBB4_2: movl$1, %r8d movl$1, %esi .LBB4_3: movl$0, (%rsp,%rsi,4) .LBB4_1: testl%r8d, %r8d je.LBB4_2 .LBB4_4: movslq%r8d, %rsi cmpl$12, %r8d ja.LBB4_17 movl(%rsp,%rsi,4), %r9d movl%r8d, %r10d xorl%edi, %edi .p2align4, 0x90 .LBB4_6: cmpq%rsi, %rdi jge.LBB4_7 cmpq$13, %rdi je.LBB4_19 movl(%rsp,%rdi,4), %ecx movl%ecx, %edx subl%r9d, %edx je.LBB4_14 cmpl%edx, %r10d je.LBB4_14 addq$1, %rdi movl%r9d, %edx subl%ecx, %edx leal-1(%r10), %ecx cmpl%edx, %r10d movl%ecx, %r10d jne.LBB4_6 .LBB4_14: movq%rsi, %rcx cmpl$12, %r8d ja.LBB4_15 addl$1, (%rsp,%rcx,4) cmpl$13, (%rsp,%rsi,4) jl.LBB4_1 .p2align4, 0x90 .LBB4_22: addl$-1, %r8d js.LBB4_16 movslq%r8d, %rsi cmpl$12, %esi ja.LBB4_25 movl(%rsp,%rsi,4), %ecx addl$1, %ecx movl%ecx, (%rsp,%rsi,4) cmpl$12, %ecx jg.LBB4_22 jmp.LBB4_1 .p2align4, 0x90 .LBB4_7: cmpl$12, %r8d je.LBB4_20 addl$1, %r8d movslq%r8d, %rsi cmpl$13, %esi jb.LBB4_3 jmp.LBB4_9 .LBB4_20: addl$1, %eax movl$12, %ecx addl$1, (%rsp,%rcx,4) cmpl$13, (%rsp,%rsi,4) jl.LBB4_1 jmp.LBB4_22 .LBB4_16: addq$56, %rsp .cfi_def_cfa_offset 8 retq .LBB4_19: .cfi_def_cfa_offset 64 leaq.L__unnamed_2(%rip), %rdi movl$13, %esi movl$13, %edx callq*_ZN4core9panicking18panic_bounds_check17h09b793daa6d169ffE@GOTPCREL(%rip) ud2 .LBB4_25: leaq.L__unnamed_3(%rip), %rdi movl$13, %edx callq*_ZN4core9panicking18panic_bounds_check17h09b793daa6d169ffE@GOTPCREL(%rip) ud2 .LBB4_17: leaq.L__unnamed_4(%rip), %rdi movl$13, %edx callq*_ZN4core9panicking18panic_bounds_check17h09b793daa6d169ffE@GOTPCREL(%rip) ud2 .LBB4_15: leaq.L__unnamed_5(%rip), %rdi movl$13, %edx callq*_ZN4core9panicking18panic_bounds_check17h09b793daa6d169ffE@GOTPCREL(%rip) ud2 .LBB4_9: leaq.L__unnamed_6(%rip), %rdi movl$13, %edx callq*_ZN4core9panicking18panic_bounds_check17h09b793daa6d169ffE@GOTPCREL(%rip) ud2 .Lfunc_end4: .size_ZN9boundtest5queen17h2a40cedb5ec97973E, .Lfunc_end4-_ZN9boundtest5queen17h2a40cedb5ec97973E .cfi_endproc .section.text._ZN9boundtest4main17h261b80ccf5b33e42E,"ax",@progbits .p2align4, 0x90 .type_ZN9boundtest4main17h261b80ccf5b33e42E,@function _ZN9boundtest4main17h261b80ccf5b33e42E: .cfi_startproc pushq%rbp .cfi_def_cfa_offset 16 pushq%r14 .cfi_def_cfa_offset 24 pushq%rbx .cfi_def_cfa_offset 32 subq$112, %rsp .cfi_def_cfa_offset 144 .cfi_offset %rbx, -32 .cfi_offset %r14, -24 .cfi_offset %rbp, -16 callq_ZN9boundtest5queen17h2a40cedb5ec97973E callq*_ZN3std4time10SystemTime3now17hb1b51de2cdb13891E@GOTPCREL(%rip) movq%rax, 16(%rsp) movq%rdx, 24(%rsp) leaq32(%rsp), %rdi leaq16(%rsp), %rsi xorl%edx, %edx xorl%ecx, %ecx callq*_ZN3std4time10SystemTime14duration_since17he01043b61d61d851E@GOTPCREL(%rip) cmpq$1, 32(%rsp) je.LBB5_3 movq40(%rsp), %rbx movl48(%rsp), %ebp callq_ZN9boundtest5queen17h2a40cedb5ec97973E movl%eax, 12(%rsp) callq*_ZN3std4time10SystemTime3now17hb1b51de2cdb13891E@GOTPCREL(%rip) movq%rax, 16(%rsp) movq%rdx, 24(%rsp) leaq32(%rsp), %rdi leaq16(%rsp), %r14 movq%r14, %rsi xorl%edx, %edx xorl%ecx, %ecx callq*_ZN3std4time10SystemTime14duration_since17he01043b61d61d851E@GOTPCREL(%rip) cmpq$1, 32(%rsp) je.LBB5_3 movl%ebp, %eax imulq$1125899907, %rax, %rax shrq$50, %rax movq40(%rsp), %rcx subq%rbx, %rcx movl48(%rsp), %edx imulq$1125899907, %rdx, %rdx shrq$50, %rdx subq%rax, %rdx imulq$1000, %rcx, %rax addq%rdx, %rax movq%rax, 16(%rsp) leaq12(%rsp), %rax movq%rax, 80(%rsp) movq_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$i32$GT$3fmt17h765415089818841eE@GOTPCREL(%rip), %rax movq%rax, 88(%rsp) movq%r14, 96(%rsp) movq_ZN4core3fmt3num3imp52_$LT$impl$u20$core..fmt..Display$u20$for$u20$i64$GT$3fmt17h6c2dbda1d476d957E@GOTPCREL(%rip), %rax movq%rax, 104(%rsp) leaq.L__unnamed_7(%rip), %rax movq%rax, 32(%rsp) movq$3, 40(%rsp) movq$0, 48(%rsp) leaq80(%rsp), %rax movq%rax, 64(%rsp) movq$2, 72(%rsp) leaq32(%rsp), %rdi callq*_ZN3std2io5stdio6_print17h7e1d4022dd9ebaeaE@GOTPCREL(%rip) addq$112, %rsp .cfi_def_cfa_offset 32 popq%rbx .cfi_def_cfa_offset 24 popq%r14 .cfi_def_cfa_offset 16 popq%rbp .cfi_def_cfa_offset 8 retq .LBB5_3: .cfi_def_cfa_offset 144 movq40(%rsp), %rax movl48(%rsp), %ecx movq%rax, 80(%rsp) movl%ecx, 88(%rsp) leaq.L__unnamed_8(%rip), %rdi leaq.L__unnamed_9(%rip), %rcx leaq.L__unnamed_10(%rip), %r8 leaq80(%rsp), %rdx movl$19, %esi callq*_ZN4core6result13unwrap_failed17h44d0943ece29c280E@GOTPCREL(%rip) ud2 .Lfunc_end5: .size_ZN9boundtest4main17h261b80ccf5b33e42E, .Lfunc_end5-_ZN9boundtest4main17h261b80ccf5b33e42E .cfi_endproc .section.text.main,"ax",@progbits .globlmain .p2align4, 0x90 .typemain,@function main: .cfi_startproc pushq%rax .cfi_def_cfa_offset 16 movq%rsi, %rcx movslq%edi, %rdx leaq_ZN9boundtest4main17h261b80ccf5b33e42E(%rip), %rax movq%rax, (%rsp) leaq.L__unnamed_1(%rip), %rsi movq%rsp, %rdi callq*_ZN3std2rt19lang_start_internal17h9cf8802361ad86c2E@GOTPCREL(%rip) popq%rcx .cfi_def_cfa_offset 8 retq .Lfunc_end6: .sizemain, .Lfunc_end6-main .cfi_endproc .type.L__unnamed_1,@object .section.data.rel.ro..L__unnamed_1,"aw",@progbits .p2align3 .L__unnamed_1: .quad_ZN4core3ptr13drop_in_place17h8206b4e5dd5bef15E .quad8 .quad8 .quad_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hfcc61cdeabb58ae0E .quad_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hfcc61cdeabb58ae0E .quad_ZN4core3ops8function6FnOnce40call_once$u7b$$u7b$vtable.shim$u7d$$u7d$17h85cd4d27ce61189fE .size.L__unnamed_1, 48 .type.L__unnamed_9,@object .section.data.rel.ro..L__unnamed_9,"aw",@progbits .p2align3 .L__unnamed_9: .quad_ZN4core3ptr13drop_in_place17h8206b4e5dd5bef15E .quad16 .quad8 .quad_ZN63_$LT$std..time..SystemTimeError$u20$as$u20$core..fmt..Debug$GT$3fmt17hc5da7e82d27f47abE .size.L__unnamed_9, 32 .type.L__unnamed_8,@object .section.rodata..L__unnamed_8,"a",@progbits .L__unnamed_8: .ascii"Time went backwards" .size.L__unnamed_8, 19 .type.L__unnamed_11,@object .section.rodata..L__unnamed_11,"a",@progbits .L__unnamed_11: .ascii"src/main.rs" .size.L__unnamed_11, 11 .type.L__unnamed_10,@object .section.data.rel.ro..L__unnamed_10,"aw",@progbits .p2align3 .L__unnamed_10: .quad.L__unnamed_11 .asciz"\013\000\000\000\000\000\000\000\007\000\000\000\033\000\000" .size.L__unnamed_10, 24 .type.L__unnamed_4,@object .section.data.rel.ro..L__unnamed_4,"aw",@progbits .p2align3 .L__unnamed_4: .quad.L__unnamed_11 .asciz"\013\000\000\000\000\000\000\000\021\000\000\000\022\000\000" .size.L__unnamed_4, 24 .type.L__unnamed_2,@object .section.data.rel.ro..L__unnamed_2,"aw",@progbits .p2align3 .L__unnamed_2: .quad.L__unnamed_11 .asciz"\013\000\000\000\000\000\000\000\023\000\000\000\025\000\000" .size.L__unnamed_2, 24 .type.L__unnamed_6,@object .section.data.rel.ro..L__unnamed_6,"aw",@progbits .p2align3 .L__unnamed_6: .quad.L__unnamed_11 .asciz"\013\000\000\000\000\000\000\000+\000\000\000\021\000\000" .size.L__unnamed_6, 24 .type.L__unnamed_5,@object .section.data.rel.ro..L__unnamed_5,"aw",@progbits .p2align3 .L__unnamed_5: .quad.L__unnamed_11 .asciz"\013\000\000\000\000\000\000\000/\000\000\000\t\000\000" .size.L__unnamed_5, 24 .type.L__unnamed_3,@object .section.data.rel.ro..L__unnamed_3,"aw",@progbits .p2align3 .L__unnamed_3: .quad.L__unnamed_11 .asciz"\013\000\000\000\000\000\000\0003\000\000\000\021\000\000" .size.L__unnamed_3, 24 .type.L__unnamed_12,@object .section.rodata..L__unnamed_12,"a",@progbits .L__unnamed_12: .ascii"found=" .size.L__unnamed_12, 6 .type.L__unnamed_13,@object .section.rodata..L__unnamed_13,"a",@progbits .L__unnamed_13: .ascii" time=" .size.L__unnamed_13, 6 .type.L__unnamed_14,@object .section.rodata.cst4,"aM",@progbits,4 .L__unnamed_14: .ascii" ms\n" .size.L__unnamed_14, 4 .type.L__unnamed_7,@object .section.data.rel.ro..L__unnamed_7,"aw",@progbits .p2align3 .L__unnamed_7: .quad.L__unnamed_12 .asciz"\006\000\000\000\000\000\000" .quad.L__unnamed_13 .asciz"\006\000\000\000\000\000\000" .quad.L__unnamed_14 .asciz"\004\000\000\000\000\000\000" .size.L__unnamed_7, 48 .section".note.GNU-stack","",@progbits

2025/7/8
articleCard.readMore

使用 Rust 编写操作系统:Barebones

这是 Philipp Oppermann的操作系统系列博客的第一大章节,主讲如何搭建起一个基本的操作系统框架。 独立的 Rust二进制文件 Rust 在 Rust 1.88.0 引入了一个叫作 bare-function的特性用于强化 no_std的开发体验,因此我们基于这个新特性来改进原文中的一些过时之处 panic_impl duplicate 在写完这部分的代码后你会发现一个很奇怪的问题:Rust Analyzer总是提示你你的 panic_impl 实现重复了,因为test 依赖于 std 并且它已经在 std中实现了 found duplicate lang item panic_impl the lang item is first defined in crate std (which test dependson) 这个 ERROR实际上不影响编译,只是会显得很碍眼,可以使用一个简单的方式把他关掉:不使用test 就好了 1 #![cfg(not(test))] Link args 在这一章节提到了编译时需要链接参数,我们可以在.cargo/config.toml 里写上传递给 rustc的参数 1 2 3 4 5 6 7 8 9 10 [target.'cfg(target_os="windows")'] rustflags = [ "-C", "link-args=/ENTRY:_start", "-C", "link-args=/SUBSYSTEM:console", ] [target.'cfg(target_os="linux")'] rustflags = ["-C", "link-arg=-nostartfiles"] 注意,由于该系列的文章是面向 x64 的,所以无法通过基于 Apple Silicon的 macOS 的编译,因此暂时关闭了 macOS 的编译也暂时不支持他。

2025/7/3
articleCard.readMore

使用 Rust 编写操作系统:引言

最近在互联网上冲浪的时候找到了这个博客(其实是朋友发的),感觉很有意思于是决定做一下。 Philipp Oppermann is a freelance Rust developer from Germany. He isworking on the Dora robotic framework and on various projects related tooperating system development in Rust. Philipp is the author of the"Writing an OS in Rust" blog and the main editor of the "This Month inRust OSDev" newsletter. 嗯,看起来挺权威的 XD 梗概 这一系列博客文章算是能覆盖最基础最基础的操作系统概念,根据他的归类可以做如下大纲: Barebones: 基础 独立的 Rust 二进制文件 最小化的 Rust 内核 VGA 字符模式 内核测试 中断 CPU 异常处理 Double Faults 硬件中断 内存管理 内存分页初探 分页实现 堆分配 内存分配器设计 多任务 Async/Await 可以看到这个系列的博客探讨的是一个操作系统的最最基础的几个要素,其他在此之上构建的一些模块并没有包含: 文件系统 IPC 程序链接与执行 它更像是一个跑在单片机上的应用程序。但,我们真实世界的操作系统从脱离单片机进入现代设计也不过三四十年,因此还是有不少可以参考的设计 打算做的一些改进 Philipp Oppermann 在编写博客的时候是 2018 年,那个时候最新也就 Rust2018 Edition,而现在已经是 2024 年了,所以我打算基于最新的 Rust 2024Edition 做些改进 TO BE CONTINUED

2025/7/1
articleCard.readMore

编译器笔记:rope

在实现 paradoxical的时候注意到很多语言服务器或者编辑器都会使用一个叫作 rope的东西来保存对文件的操作结果,因此简单记录下这个神奇的东西。 rope 这个概念最开始是在 1995 年由 Hans-J.Boehm 、 Russ Atkinson 和Michael Plass提出的[0]。如果你想简单了解的话,你可以直接去看xi-editor的相关文档[1],写得非常不错 十分感谢他们的贡献 :) 简单介绍 Rope(绳索字符串)就是把一整段文本切成许多小块,用一棵树把这些块串起来;这样随机插入、删除或定位位置时,只需改动或遍历对数级别的节点,而不是线性扫描整段文本。 请注意,而不是线性扫描整段文本这段话非常重要,这也是和使用String 储存文件内容的最大区别 操作? 对于一个编辑器或者文本解析,我们常常使用行列的概念来操作他。 答案是,对于 rope 我们也可以使用行列来操作他! 为什么 rope 可以实现高效编辑 请注意,rope的主要目的是实现高效的编辑,对于读取他的速度和线性的String 相比是略微慢了一点点的。 但就这个数量级是常数,非常不明显 从储存结构上优化 rope最重要的一点是,他不是线性储存文本数据,转而使用了一棵树来维护储存文本数据。 让我们来看看这棵树长什么样子 1 2 3 4 5 6 ┌─── weight = 23 ───┐ │ │ [左子树] [右子树] weight = 10 weight = 7 │ │ │ │ "片段A" "片段B..." "片段C" "片段D..." 简单解释就是: 左子树(LeftSubtree):按照逻辑顺序(即文本顺序)排在当前节点之前的全部内容 右子树(RightSubtree):排在当前节点(或其左子树)之后、紧接着的那部分内容 请注意,节点内储存的权重不仅仅只有字符数,也包含行数等信息 在接下来我们定义权重分为以下几种 lines: 行权重 chars: 字符权重 偏移 当我们需要快速定位光标的时候,权重就显得非常重要了:它保存了左子树累计长度,若要找第\(k\) 个字符,算法会看 \(k\)是否小于该权重,按照如下逻辑来搜索: 是 : 目标一定在左子树。 否 : 目标必定在右子树,并将 \(k\)减去左权重后继续向下搜索。 除此之外我们还有行列偏移的需求:我们在编辑时经常需要行列->偏移,或者偏移->行列,那么rope 是怎么做的? 行列->偏移 输入行号 \(L\);从根节点开始。 比较 \(L\) 与当前节点的左子树行数: 若 \(L\)小于该权重,进入左子树; 否则 \(L = L -left\_lines\),转入右子树。 重复直到到达叶节点(叶片约 4–8 KB大小)。在叶内部用已缓存的换行表(二分搜索)把剩余行数定位到字节/字符位置。 通过这样的设计,这棵树就把线性扫描的复杂度降低到了 \(O(logN)\) 偏移->行列 从偏移转换到行列也是个很常见的需求,不过这种转换相比行列转换为偏移更加简单 将目标字符索引 \(K\)与字符权重做同样的左/右子树递归 到叶后用换行表计算它之前的换行符数量,加上在路径中累积的 \(left\_lines\) 即得行号。 换行符数量也是可以作为缓存项的,所以这种操作依然非常快速 对 CPU 缓存的充分利用 上文提到,叶片的大小一般是 4-8KB,这样的大小有利于 CPU缓存。 基于 CPU的缓存机制,我们可以使用链表或者其他顺序结构将叶子之间链接起来,在遍历的时候就不需要重走整棵树。 关于为什么这样的大小可以更好的利用 CPU 缓存,本文不做叙述 简答地讲,对于遍历文件内容的本身的时候有如下优化手段 利用链表链接所有叶子节点,在遍历的时候通过链表对叶子节点进行遍历而不是通过爬树来执行访问 利用 CPU会预缓存内存内容的特性使得访问内存的频率减少,降低遍历时使用其他结构因为miss 的原因访问内存 这样既得到了廉价的编辑行为也获得了不俗的遍历性能,代价即为直接读取内容稍慢而已 插入删除等局部更新 在刚刚提到了读取操作,以上的解释注重于解释为什么 rope在读取方面依然性能十分出众但并没有解释为什么 rope在编辑方面的性能远大于使用 String[u8] 等线性数据结构 定位 对于定位,请看 偏移 操作 在到达指定位置后就可以执行对叶片的操作了。对于叶片的操作核心在于需要合适的时机分裂叶片使其成为新的叶片并且添加新的节点 先将光标内的内容插入到这个叶片的缓冲区里,然后做如下判断 如果叶片大于4-8KB:叶片将会分裂成为两个(或者多个)新的叶片 在完成分裂后对叶片添加新的父节点 如果叶片小于 4-8KB:正常插入即可 在完成插入操作后需要回溯所有祖先节点并且完成对祖先节点的权重变更,这样就完成了插入操作 在正常编辑下(只添加一个字符)最多只需要触摸 2 个叶片和 \(O(logN)\)个节点,在特殊情况下(例如剪切板粘贴)会有更复杂的操作,暂且不提(简单讲就是对粘贴内容生成rope 树并且将树挂载到指定位置上) xi-editor 和 ropey在这方面实现上有区别,不介绍两个实现上所使用的树的具体区别 0.Ropes:an Alternative to Strings↩︎ 1.xi-editor↩︎

2025/5/22
articleCard.readMore

编译器笔记:CST

AST(Abstract Syntax Tree)倒是想做编译器的人、不想做编译器的人都会知道一点,但是 CST(ConcreteSyntax Tree) 倒是很少提到,睡不着就简单记录一下吧 有了 AST 为啥还要 CST? AST侧重于表达内容核心结构,他会忽略基本上所有除了内容核心结构以外的所有内容包括: 空格 tag 类型 各种括号 各种引号 关键字 分号 只会保存核心内容,由于抛弃了以上内容,基于 AST可以做我们更耳熟能详并且高级的操作: 语义分析 优化 代码生成 等等等,还有更多。 那么他的缺陷是什么?由于忽略了空格,我们没有办法从AST 直接还原文本————我们失去了原先的对应上文本的信息。 那么基于我们需要还原文本这个前提条件,我们就需要引入完整保存信息了的新的结构,他既可以还原成为原文本,也可以降级(Lowering) 成为 AST便于后续处理 这个时候,CST 就出现了。 CST,为什么能还原文本? 为了简单,我们直接画两张图来说明区别就好 我们假定有如下的表达式 1 a = (b + c) * d; 那么我们的 AST 长这样: 1 2 3 4 5 6 Assign └── LHS: "a" └── RHS: Multiply ├── Add("b", "c") └── "d" 我们的 CST 就长这样: 1 2 3 4 5 6 7 8 9 10 11 Assign └── LHS: Identifier("a") └── Operator("=") └── RHS: │ Multiply │ ├── Parentheses │ │ └── Add │ │ ├── Identifier("b") │ │ └── Identifier("c") │ └── Identifier("d") └── Semicolon 可以清晰的看到,CST 多了很多东西,他是可以被Lowering 到 AST 的 不过为了画起来方便我没画空格,空格实际上也是包含在其中的 :)

2025/5/19
articleCard.readMore

Paradoxical 札记

最近把 neorg 作为笔记和规划系统有点上头,但苦于没一个好用的Language Server,自己又对编译器前端方面略有了解于是决定写下这篇大概会持续更新的札记 我们的最终目的是实现一个不错的 Language Server并且可以在 neovim/helix 上跑起来 为什么是 neovim/helix 呢?因为我暂时不会继续用 VSCode了 流水线设计 考虑到我希望 paradoxical 有如下两个功能 重新格式化 neorg 文档 良好的补全 因此我决定采用如下流水线设计: 1 2 Text → Lexer (RawTokenKind) → CST → Typed AST/HIR → Semantic Analysis → LSP Server → Editor | src | Lazy Lexer | CST | AST | 需要着重说明的是,我们使用 rope来保存和编辑源文件,这样我们所有的操作的时间复杂度都是 \(O(logN)\) 目标 阶段 0 该阶段起始于 2025-05-16 在阶段 0 需要实现最基本的功能:解析 但由于 neorg 的格式内容较多所以在这个阶段只考虑实现一个子集 标题 有序/无序列表 文本段 文本类型 Paragraph:这种类型允许使用 neorg 的修饰符例如{} // $||$等,这些修饰符可以正常执行其对应的功能 逐字类型 VerbatimParagrah: 这种类型的文本段不会处理neorg 的修饰符,所有输入的修饰符都应该原样输出 标签 Tag 请注意,由于 neorg设计上要求的非歧义解析,所有解析失败的内容都应该回归为文本类型 Paragraph Token 设计 基于 Token 的功能和现在的目标,我设计出如下的RawTokenKind 1 2 3 4 5 6 7 8 9 10 11 12 13 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum RawTokenKind { Punct(char), NormalChar(char), Tag, Linending(LinendingKind), } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum LinendingKind { LF, CRLF, } 以上的代码省略了对于 Display trait 的实现 Token 解析规则 我们使用 nom这个库来实现解析,因为 neorg的文档风格非常适合使用组合子解析器的方式进行解析。 不自己编写递归下降解析器是因为 neorg 的规范实际上比较简单,而且 neorg的二义性情况很容易被判断,因此我们直接使用组合子解析器来实现解析就好 由于我们需要保留 Token 在文档里的位置信息,因此基于RawTokenKind 派生出 RawToken,添加span 字段保留当前 Token的在文档里的位置信息(以偏移表示) 1 2 3 4 5 #[derive(Debug, Clone)] pub struct RawToken { pub kind: RawTokenKind, pub span: Range<usize>, } 接下来我们开始编写对于几个类型 Token 的组合子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 pub const AVAILIABLE_PUNCT: &[char] = &['(', ')', '-', '~', '*', '/', '{', '}', '[', ']']; fn parse_punct(input: Span) -> IResult<Span, RawToken> { let (input, pos) = position(input)?; let (input, matched) = nom::character::complete::one_of(AVAILIABLE_PUNCT)(input)?; let token = RawToken { kind: RawTokenKind::Punct(matched), span: pos.location_offset()..(pos.location_offset() + matched.len_utf8()), }; Ok((input, token)) } fn parse_normal_char(input: Span) -> IResult<Span, RawToken> { let (input, pos) = position(input)?; let (input, matched) = nom::character::complete::satisfy(|c| { !AVAILIABLE_PUNCT.contains(&c) && c != '@' && c != '\n' && c != '\r' })(input)?; let token = RawToken { kind: RawTokenKind::NormalChar(matched), span: pos.location_offset()..(pos.location_offset() + matched.len_utf8()), }; Ok((input, token)) } fn parse_tag(input: Span) -> IResult<Span, RawToken> { let (input, pos) = position(input)?; let (input, matched) = nom::character::complete::char('@')(input)?; let token = RawToken { kind: RawTokenKind::Tag, span: pos.location_offset()..(pos.location_offset() + matched.len_utf8()), }; Ok((input, token)) } fn parse_linending(input: Span) -> IResult<Span, RawToken> { let (input, pos) = position(input)?; let (input, ending) = nom::branch::alt(( nom::combinator::map(nom::bytes::complete::tag("\n"), |_| LinendingKind::LF), nom::combinator::map(nom::bytes::complete::tag("\r\n"), |_| LinendingKind::CRLF), )) .parse(input)?; let token = RawToken { kind: RawTokenKind::Linending(ending), span: pos.location_offset()..(pos.location_offset() + ending.to_string().len()), }; Ok((input, token)) } 对于 RawTokenKind::Tag 我们直接提取 @字符就好 对于 RawTokenKind::Linending我们需要判断并且保留文档里的换行符类型 \n:UNIX 平台所使用的换行符 \r\n:Windows 平台所使用的换行符 对于其他的标点符号,我们引入一个常量 AVAILIABLE_PUNCT表示可以出现在 RawTokenKind::Punct内的字符(相当于白名单) 如果在以上情况之外,则归类为RawTokenKind::NormalChar 因此,我们可以总结出如下的 lexer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 let rope = Rope::from_str(source); let mut tokens = Vec::new(); let mut input = Span::new(source); let mut parsers = nom::branch::alt(( Self::parse_tag, Self::parse_linending, Self::parse_punct, Self::parse_normal_char, )); while !input.fragment().is_empty() { let Ok((rest, token)) = parsers.parse(input) else { return Err(error::Error::Lexer); }; tokens.push(token); input = rest; } Ok(Self { text: rope, tokens }) 单元测试也是不可少的一部分,因为包括对于中文的解析是略微麻烦也需要验证的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 #[cfg(test)] mod tests { use super::*; use crate::token::{LinendingKind, RawTokenKind}; #[test] fn test_raw_document_parsing() { let input = "@hello\n-world\r\n"; let doc = RawDocument::new(input).expect("Failed to parse document"); let expected = vec![ RawToken { kind: RawTokenKind::Tag, span: 0..1, }, RawToken { kind: RawTokenKind::NormalChar('h'), span: 1..2, }, RawToken { kind: RawTokenKind::NormalChar('e'), span: 2..3, }, RawToken { kind: RawTokenKind::NormalChar('l'), span: 3..4, }, RawToken { kind: RawTokenKind::NormalChar('l'), span: 4..5, }, RawToken { kind: RawTokenKind::NormalChar('o'), span: 5..6, }, RawToken { kind: RawTokenKind::Linending(LinendingKind::LF), span: 6..7, }, RawToken { kind: RawTokenKind::Punct('-'), span: 7..8, }, RawToken { kind: RawTokenKind::NormalChar('w'), span: 8..9, }, RawToken { kind: RawTokenKind::NormalChar('o'), span: 9..10, }, RawToken { kind: RawTokenKind::NormalChar('r'), span: 10..11, }, RawToken { kind: RawTokenKind::NormalChar('l'), span: 11..12, }, RawToken { kind: RawTokenKind::NormalChar('d'), span: 12..13, }, RawToken { kind: RawTokenKind::Linending(LinendingKind::CRLF), span: 13..15, }, ]; assert_eq!(doc.tokens, expected); } #[test] fn test_raw_document_parsing_with_chinese() { let input = "@你好\n-世界\r\n"; let doc = RawDocument::new(input).expect("Failed to parse document"); let expected = vec![ RawToken { kind: RawTokenKind::Tag, span: 0..1, }, RawToken { kind: RawTokenKind::NormalChar('你'), span: 1..4, }, RawToken { kind: RawTokenKind::NormalChar('好'), span: 4..7, }, RawToken { kind: RawTokenKind::Linending(LinendingKind::LF), span: 7..8, }, RawToken { kind: RawTokenKind::Punct('-'), span: 8..9, }, RawToken { kind: RawTokenKind::NormalChar('世'), span: 9..12, }, RawToken { kind: RawTokenKind::NormalChar('界'), span: 12..15, }, RawToken { kind: RawTokenKind::Linending(LinendingKind::CRLF), span: 15..17, }, ]; assert_eq!(doc.tokens, expected); } }

2025/5/13
articleCard.readMore

简单的相似去重算法(基于向量)

在工作的时候遇到一个对图片进行去重的需求,简单记录一下 算法 当前提供两种算法,两种算法都依赖于向量数据库。并且由于架构设计,现在两种算法在执行时几乎只需要向量数据库 层级递进 这种算法可以在每次产生新数据时对新数据进行处理,如果新数据里已经出现过之前就已经被查找到的相似图片那么该数据就会被剔除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func (d *Deduplicator) _algorithmV0(trainedUuid string, fuzz float32) { if d.searchedUuids.Contains(trainedUuid) { return } if res, err := d.vdb.SearchByNearOjWithDistance(trainedUuid, fuzz); err != nil { logrus.Errorf("while try to fetching result of `%s`: %s", trainedUuid, err) } else if len(res) > 1 { currentResult := x.NewList[ProcessedID]() for _, file := range res { partial, exist := d.fileIdMapping[file.FileId] if exist && !d.searchedUuids.Contains(partial.UUID) { currentResult.Append(ProcessedID{ID: file.FileId, Distance: file.Additional.Distance, Time: partial.TakeTime}) } d.searchedUuids.Add(partial.UUID) } if currentResult.Len() > 1 { tmp := d.postproc(currentResult) d.result.Append(tmp) } } } 后处理 在完成结果获取后我们还需要一次过滤操作,我们令该操作为 filter filter 函数接受一个 float64类型的参数,根据这个参数过滤掉不需要的值只保留需要的值 全量关联合并 这种算法实现起来就非常简单,相当于把上一种算法的去重步骤给放到提取结果的时候。 但这个算法会对每组都进行查找,假设有三十万个向量数据则会查找三十万次 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func (d *Deduplicator) _algorithmV1(trainedUuid string, fuzz float32) { if res, err := d.vdb.SearchByNearOjWithDistance(trainedUuid, fuzz); err != nil { logrus.Errorf("while try to fetching result of `%s`: %s", trainedUuid, err) } else { currentResult := x.NewList[ProcessedID]() for _, file := range res { partial, exist := d.fileIdMapping[file.FileId] if exist { currentResult.Append(ProcessedID{ID: file.FileId, Distance: file.Additional.Distance, Time: partial.TakeTime}) } d.searchedUuids.Add(partial.UUID) } if currentResult.Len() > 1 { d.result.Append(d.postproc(currentResult)) } } } 合并 我们令以上算法得到的结果为 res ,并且新引入一个函数 merging 令我们最终得到的结果为 y,相比于层级递进算法我们可以更加动态的计算结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func merging(input [][]ProcessedID) [][]ProcessedID { walked := mapset.NewSet[uint]() result := make([][]ProcessedID, 0) for _, group := range input { tmp := slices.DeleteFunc(group, func(id ProcessedID) bool { return walked.Contains(id.ID) }) result = append(result, tmp) for _, t := range tmp { walked.Add(t.ID) } } return result } 这样做,和层级递进最大的区别就在于全量运算,并且在获取结果的时候进行关联合并 后处理 全量关联合并的后处理和层级递进的后处理步骤一致 算法缺陷 层级递进算法的问题 旧算法的复杂度为 \(O(n)\) 到 \(O(n^2)\),计算量比较小速度快但是缺点也非常明显 计算结果只能在向量空间里一次性使用,假如在二维向量空间里则圆缩小后无法从缩小前和缩小后之间的区域重新规划出集合,也就是存在并查集问题 因为上述原因,去重的结果只在单次有效 全量关联合并算法的问题 该算法的主要问题在于计算缓慢,对于正常用户环境的三十万条向量数据在懒猫微服上计算需要十分钟 但我们可以选择将其作为后台运行的任务并且将其计算结果作为缓存来处理,这样就能扬长避短了

2025/5/13
articleCard.readMore

更加现代的 PaperMC Minecraft 插件设计指南

你还在为你的大型 PaperMC 插件性能差劲而烦恼吗?来看看这些 tips吧。 常见卡顿原因 在 Minecraft的设计里,你的插件所有逻辑都是串行的,当你插件的某部分逻辑无法在指定时间内完成相应的任务则会造成卡顿。 我们一般使用 TPS(Tick PerSecond)来衡量服务器是否能按照预期的执行速度来执行整个游戏内加载好的逻辑,默认情况下这个数值是20,当你的服务器当前 TPS 低于20那么意味着你的服务器已经过载(Overloaded)了。 计算瓶颈 实体过多导致的过载 算法缺陷 加载项过多 设计失误 IO 瓶颈 主线程访问数据库 主线程访问外部网络 缓存过载 可以使用 PaperMC 自带的 Spark 简单分析瓶颈出现在什么地方。 但,这些都是有 tradeoff的:我们需要牺牲一些实时性来利用更强大的计算资源利用 优化手段 优化将会围绕两个重要的点进行:优化计算瓶颈,还是优化 IO 瓶颈? 计算瓶颈 对于计算瓶颈而言除了优化算法以外还可以优化架构等,优化算法算是个很小的切入点 分离计算到其他线程 假设我们可以从原始架构访问到基础类型,那么我们可以通过值传递的方式构造一个函数\(y=f(x)\),令 \(y\)为我们预期的副作用或者预期的值,使得该副作用可以以干净的方式包装起来应用到目标上,这样就实现了封装副作用的目的 举个例子 1 2 3 4 5 6 7 8 9 // 以下代码是在主线程外进行的 // // 通过各种包装使得 f 是一个可以安全地在主线程外计算的函数 var someHeavyComputing = f(); Bukkit.getScheduler().runTask(pluginInstance, () -> { // 在完成计算后以同步的方式应用回计算结果,使得副作用可控 applyHeavyComputingResult(someHeavyComputing); }); 使用 Singleflight 如果对于一个表达式会反复求值,并且其输入和输出可以遇见地一致,那么可以使用Singleflight等方式缓存下来该表达式的结果,在接下来的运算中将有机会节约该次运算的计算损耗。 当前 Java 生态并没有公认的 Singleflight 实现,因此请参考 Golang 的Singleflight 实现自己实现一个 任务队列 可以通过任务队列的方式限制执行数量,每 tick 只取指定数量任务执行 由于任务队列处理器可以感知处理任务消耗的时间,因此甚至可以实现智能截断防止任务过多使得服务器过载 因此可以实现以下几种任务执行策略: 溢出式 该策略允许执行固定数量的任务,比如 1024 个任务、 执行器只会执行小于等于 1024 个任务,多出来的任务本轮将不执行 自动截断 由于执行器可以感知已消耗的时间,因此可以在本轮任务即将超时或者已经超时时停止继续执行任务 抛弃式 只执行 1024 个任务,多余任务将会被抛弃 针对性缓存 可以利用精度缩减等方法来粗化搜索的粒度,并且实现针对性的缓存使得可以构建一个高效的缓存系统 精度缩减是一个很笼统的说法,举个例子: 将坐标归一化到方块中心,以方块中心为中心点开始搜索实体 由于已经归一化到方块中心并且搜索所有实体,该步骤可以缓存下来,本次tick 都可以利用该缓存 对于这种很耗时的搜索,同时可以归约一下搜索范围为统一的几个值使得缓存可以被更高效利用:例如规定代码内使用的搜索范围为3, 6, 8 等几个值 IO 瓶颈 很喜闻乐见的,有不少插件作者贪图简单选择直接在主线程里使用非常耗时的IO 请求,这是难以避免的 分离 IO 逻辑到其他线程 该部分做法和上文提到的分离计算到其他线程做法一致,都是封装副作用在线程外计算,随后在主线程内应用。 不过你应该结合 Java 的异步基建来实现这一点:例如Future

2025/5/7
articleCard.readMore

简单的 CFG 语法分析方法

有些时候吧人就是贱,想写点吃力不讨好的东西。今天就写点上下文无关文法(Context-FreeGrammar, CFG)的两个算法吧。 请注意,这部分不是我擅长的领域,我只是因为好奇这方面的事情而自学过。如有纰漏,还请海涵。 本篇内容偏多偏杂,请善用右侧的 ToC 来快速导航 :) 文法(Grammar) 首先先明确一下,文法(Grammar)和语法(Syntax)是两个东西。 文法是形式化的约定,使用一套符号系统来进行严格定义,为编译器或者解析器所使用的形式化规则 语法则为语言设计者所定义的语言规范 我们在这里要做的是讨论解析器/编译器所使用的形式化规则,那么我们的重点则是文法而不是语法。 文法的形式化表达 先放一个例子在这里,免得无聊吧 E → T E' E' → + T E' | ε T → F T' T' → * F T' | ε F → ( E ) | id 无聊是不无聊了,但是看到符号就食欲不振。让我们更加深入一些: 终结符 Terminal Symbols 如其所述,终结符意味着终结,代表着该符号不可被再分解,例如* ( ) id等等等。(这里的 id代指标识符,例如变量名或者关键字等) 以我们上文所给的例子: id:标识符(如变量名、数字等)。 +, *:运算符。 (, ):括号。 $:输入结束符(过程中添加的标记,表示输入的末尾)。 再回去看上文的例子,就感觉清晰不少了不是吗?好,我们继续深入这个例子中各项的意义。 非终结符 Non-TerminalSymbols 非终结符是文法中需要进一步推导的符号,通常使用大写符号来表示:例如例子中的ET,它们代表语法结构中的抽象概念,如表达式、项、因子等。 继续以我们上文提到的例子: E:表达式 Expression 。 E':表达式后缀(用于消除左递归)。 T:项 Term。 T':项后缀(用于消除左递归)。 F:因子 Factor。 其他符号 要是你仔细点的话,会看到这里还有其他符号: → 推导关系,左侧是非终结符,右侧是可能产生的表达式 如果不方便编写的话写作 := 也是可以的 | 表达或关系,意味着该非终结符可以被推导为多个可能的符号序列 例子:E' → + T E' | ε 即 E' 可以被推导为+ T E' 或者 ε ε 空推导,意义为可以被推导为什么都不做 还是以刚刚的例子,E' → ε 就可以认为 E'什么都不做,则 E' 可以直接消失 产生式 Productions 基于上述规则,我们可以组合出更复杂的东西:产生式。 产生式定义了对于非终结符号的定义规则,我们来看一些产生式例子: E → T E' 该产生式由一个项 T 和一个后缀构成 E' T 处理项(如乘法、除法等) E' 处理加法或减法的后缀 F → ( E ) | id 因子 F 可以是括号内的表达式 E 或标识符id E' → + T E' | ε 后缀 E' 可以是 + T E' 也可以是ε 等等,E, T, F又是什么东西?! 文法符号 Grammar Symbol 请注意,接下来有些概念可能会产生混淆:接下来的概念基本上都属于数学表达式的分层结构,而不是数字运算的优先级顺序 因子,F,Factor 原子单元,他们无法被进一步分解,例如id,(E) 项,T,Term 由 F 乘法除法组成的单元,例如T → F * F 表达式,E,Expression 由 T 通过加法减法组成的单元,例如E → T + T 特别的,id是在我们当前的语境下引入的新原子单元,称之为标识符identifier 等等,好像混进来奇怪的东西?(E) 又是啥玩意儿? 考虑到一个表达式可以被分解成由 F构造的产生式,我们将分解这个步骤使用 ()进行封装成为一个整体,这样可以减轻各方面的压力 如果再允许 (E)进行分解,那么很可能导致文法中出现左递归和更复杂的结构 举个例子单独说明 F → (E)的情况,在这种情况下不能认为包含了 E所以可以无限推导下去,因为 E必须被完全推导为具体的符号: 1 F → ( E ) → ( T E' ) → ( F T' E' ) → ( id * F T' E' ) → ... → ( id * id ) 以上的例子最终将 F → ( E )分解成为了具体的符号序列作为因子的实例

2025/4/28
articleCard.readMore

简单地使用 Caddy 实现 CORS 配置

其实可以在后端实现 CORS 配置,但是在后端实现 CORS不算是很方便管理。既然已经使用了 Caddy,那为什么不利用强大的 Caddy 实现CORS 配置? 查阅文档发现 Caddy 本身不支持直接写 CORS 的配置,但是 CORS基本上是使用 HTTP Header来实现的[0],所以我们应该只需要写对应的HTTP Header 就行了: 1 2 3 4 5 6 7 8 :80{ header { Access-Control-Allow-Origin * Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE" Access-Control-Allow-Headers "Content-Type" } ... } 然后我发现了个更奇妙的写法:Caddy 有snippet[1],利用 snippet一样可以做到这样的效果并且可移植性更好(大概?) 下面这段粘贴复制出去就可以用了,非常方便 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 (cors) { @cors_preflight method OPTIONS @cors header Origin {args.0} handle @cors_preflight { header Access-Control-Allow-Origin "{args.0}" header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE" header Access-Control-Allow-Headers "Content-Type" header Access-Control-Max-Age "3600" respond "" 204 } handle @cors { header Access-Control-Allow-Origin "{args.0}" header Access-Control-Expose-Headers "Link" } } 然后在站点配置里如下引用: 1 2 3 4 5 6 7 :80 { import cors * handle_path /* { reverse_proxy localhost:8080 } } 0.MDN↩︎ 1.Caddysnippet ↩︎

2025/4/28
articleCard.readMore

让 OpenCV 可以被静态链接

在 Alpine的环境里,需要尽可能让程序被静态链接,否则程序还需要安装巨大的 glibc和其他动态库,不符合 Alpine 的原则,也不太方便被部署。但 OpenCV并不是那么容易被静态链接,应该怎么办? OpenCV没有那么容易被静态链接,主要还是因为它的依赖里有很难被静态链接的库:GTK,Qt,FFMpeg所以需要重新考虑编译参数。我们有一个很好的参考,GoCV的仓库里有静态链接的Dockerfile,根据Dockerfile我们获取新的编译参数即可: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 cmake -D CMAKE_BUILD_TYPE=RELEASE \ -D WITH_IPP=OFF \ -D WITH_OPENGL=OFF \ -D WITH_QT=OFF \ -D WITH_FREETYPE=ON \ -D CMAKE_INSTALL_PREFIX=/usr/local \ -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-${OPENCV_VERSION}/modules \ -D OPENCV_ENABLE_NONFREE=ON \ -D WITH_JASPER=OFF \ -D WITH_TBB=ON \ -D BUILD_JPEG=ON \ -D WITH_SIMD=ON \ -D ENABLE_LIBJPEG_TURBO_SIMD=ON \ -D BUILD_DOCS=OFF \ -D BUILD_EXAMPLES=OFF \ -D BUILD_TESTS=OFF \ -D BUILD_PERF_TESTS=ON \ -D BUILD_opencv_java=NO \ -D BUILD_opencv_python=NO \ -D BUILD_opencv_python2=NO \ -D BUILD_opencv_python3=NO \ -D OPENCV_GENERATE_PKGCONFIG=ON .. 使用这份编译参数就可以获取一份可以被静态链接的 OpenCV 了 放弃了,毁灭吧。这个世界就不存在可以被静态链接的 OpenCV4。

2025/2/12
articleCard.readMore

攒下自己的Neovim配置

终于用了点时间重新整理了一下 Neovim的配置,攒配置路上的经验记录在此 按键规划 原则 原则上 Vim 更推荐用 <leader>来做自定义按键,但是难免得我们用 Ctrl会更顺手,因此我自己的按键规划原则如下: 会对整个工作区产生副作用的使用 Ctrl 不会产生副作用的使用 <leader> 规划 我的规划如下 <leader>l 为所有 lsp提供的功能相关的操作的前缀 <C-> 为所有立刻产生副作用的操作的前缀 <C-A-> 为修饰所有立刻产生副作用的操作的前缀 LSP 部分 Inlay hints Inlay hint就像是一种另类的悬浮提示一样,不过它是出现在具体的编辑框(Buffer)的。Neovim使用 virtual text 实现这个功能。 1 vim.lsp.inlay_hint.enable(true) 可以在任意的地方开启,但最好是在 nvim-lspconfig的配置部分开启

2025/1/2
articleCard.readMore

图片搜索笔笺

这篇文章只是简单笔记,具体实现方式不作细致讨论 相似图合并实现 相似图合并组合了如下几种方案 通过向量数据库搜索相似图 通过文件哈希搜索相似图片 通过灰度哈希搜索相似图片 通过向量数据库搜索相似图 该方案已应用于相似图搜索,因此该方案为主要搜索手段 在使用向量数据库搜索相似图片时涉及到向量距离的问题,我们可以认为两张图的特征向量距离越近则越相似。 该方案可以提取一张图的所有内容相似的图片。对于连续相似的 100张图片序列而言,假设其每张图片向量特征距离为0.01,那么在向量距离为 0.1之下则均会被搜索出来 原理解释 在下文我们使用该方式来表达一组序列:{A..n}(x)其中{A..n}代表图片的下标,(x)代表该图片距离当前序列的参考向量的距离。 假设我们这组序列总共有两百张图片(换句话来讲,数据库中总共 200张),并且每张图片之间的向量距离以 0.01 递增 1 A000(0.00) A001(0.01) A002(0.02) A003(0.03) A004(0.04) ... A100(0.10) ... A200(0.20) 考虑到我们的距离参数为 0.1,则以数据库中第一张图片A000 开始搜索,那么我们会得到如下结果: 1 2 [0]:{A001, A002, ..., A100}, [1]:{A101, A102, ..., A200}, 若我们将距离参数设置为 0.5,则以数据库中第一张图片A000 开始搜索,那么我们会得到如下结果: 1 2 3 4 [0]:{A001, A002, ..., A050}, [1]:{A051, A052, ..., A100}, [3]:{A101, A102, ..., A150}, [4]:{A151, A152, ..., A200}, 参与过的图片不会再参与搜索,他们会被排除掉。 问题 该方案依赖于数据库所有的图片均被训练,对于大批量数据极其有效并且快速。 但该方案需要图片已经被训练提取过特征,假如图片数量过大则需要等待图片训练完成才可能获得其向量,对用户的体验影响较为明显 因此需要引入一种效果略差但能提升用户体验的方案,以哈希为标准的查重方法。 通过文件哈希合并图片 该方案依赖于入库时计算对应的哈希,该方案能在用户关闭 AI训练的前提下提供一定的去重查重能力。 但该方法受到图片的编码、色彩、大小等影响,因此提供的查询有限。但依然可以作为一种查询方式。 通过图片灰度特征查找相似图片 通过图片灰度特征查找相似图片是一种较为传统的方式——该方式对性能要求较低但依然需要入库时计算该图片的灰度特征哈希。 在对图片处理得出一定大小的灰度图后处理得到灰度特征,然后存入数据库内保留以备以后使用。 缺陷 由于搜索图片的灰度特征需要使用汉明距离(HammingDistance),如果要对所有入库的图片进行高效搜索那么需要数据库提供对应的支持。使用SQLite 则完全不支持汉明距离。

2024/12/27
articleCard.readMore

电路板设计笔记-保护

在电路板设计当中,为了降低电路板在生命周期内意外挂掉的概率,需要一些保护性的措施来对电路板进行保护。 浪涌/静电保护 浪涌(Surge)主要体现在高电流的情景,在这种情况下具体表现为电路板中通过的电流瞬间变高,在这种情况下电流会超过元器件的可承受电流使其损坏。 静电(Electro-Static)的特征体现在瞬间高电压的情景,在这种情况下表现为电路板中的电压瞬间极高使得击穿元器件,使其损坏。 对于这两种意外情况,一般考虑是外部来源:例如 IO 口静电、IO口电流倒灌等情况。当然也有少部分,例如劣质电源产生浪涌,这些需要考虑具体环境具体设计。 对不太可能产生这种情况的消费级产品做这种保护,完全取决于你敢不敢下成本让产品更稳定:) 我们一般需要采取 TVS(Transient Voltage Suppression)二极管来进行保护,有些时候 TVS 二极管也会被叫做 ESD保护装置,Electro-Static Discharger。 现在而言,TVS 和 ESD 其实已经有了功能性的变化:TVS更倾向于保护电路不被浪涌伤害,ESD倾向于保护电路不被静电伤害,但是他们都应该是属于TVS 二极管。 TVS 的特点? TVS是一种齐纳二极管,所以他的特点和齐纳二极管是一致的。但齐纳二极管用于提供恒压,TVS则通过自身过压被击穿而保护电路。

2024/11/12
articleCard.readMore

使用 Rust 实现 SnowflakeId

在最近的业务中更改设计的时候最终决定使用 雪花 ID(下文称之为SID)作为数据库的主键,这样可以避免使用发号器等中间件。 但是广为使用的 snowflake的实现实际上是线程级别的唯一,而不是分布式意义上的唯一,因此在生产上如果和分布式搭配会产生极大的问题。 怎么办?只能自己写了。 原理 SID 实际上是 Rust 的 i64,他有 64位。但是有一位是符号位,所以实际上可以使用的只有 63 位但也绰绰有余。 所以我们的结构看起来像是这样: 1 2 | sign | data | # sign not used. | 1bit | 63bit | 接下来我们将会介绍标准的 SID实现。为什么是标准的?因为存在很多变种,比如 Mastodon 就是变种SID,我们不讨论他们。 标准的 SID 包含如下信息 时间戳: 41bit 标识符: 10bit 序列号: 12bit 所以我们的 SID 看起来应该如下 1 2 3 | sign | data | | 0 | Timestamp | Identifier | Sequence Number | | 1bit | 41bit | 10bit | 12bit | ✨ 深入黑暗 很好,现在我们理解了最基本的 SID 组成,让我们更深一步。 时间戳 标准的 SID 使用的是毫秒精度,恰好是 41 位。实际上根据 SID的设计不同,时间戳可以是任意精度,也可以是任意起始位置。 标识符 在设计分布式系统时,我们会有多台机器(或实例)同时运行。 因此,我们必须区分它们。基于标识符为 10 位,我们可以同时拥有 1024个实例,这简直太酷了! 序列号 你注意到 Sequence Number 了吗?它有 12位,这意味着它在一毫秒内最多可以处理 4096条信息(或其他东西,随便你)。 综上所述:整个系统在一毫秒内最多可以产生1024 * 4096 = 4194304 条信息,这完全足够了! 分配完毕(不对,你是怎么做到的?!) 但我们总有可能遇到这样的情况:这一毫秒内的所有 SID 都已分配完毕! 此时,实例必须等待下一毫秒。在下一毫秒,我们将有新的 4096 个 SID可以分配。 在这种情况下,可能需要拓展实例了 XD

2024/10/24
articleCard.readMore