【译】V8是如何实现BigInt的?

简介: 【译】V8是如何实现BigInt的?

640.jpg

文由我的大学同学 AlecHe 翻译

原文地址:https://v8.dev/blog/bigint

原文标题:Adding BigInts to V8


在过去的几个月中,我们已经实现了在V8中支持BigInt,就像在这个提议中定义的一样,包含在了下一个版本的ECMAScript中。接下来这篇文章讲述了我们这段旅程中发生的故事。


总结来说


作为一名Javascript的程序员,现在在你的工具箱中有了支持任意精度的整数:


const a = 2172141653n;const b = 15346349309n;a * b;// → 33334444555566667777n     // Yay!Number(a) * Number(b);// → 33334444555566670000      // Boo!const such_many = 2n ** 222n;// → 6739986666787659948666753771754907668409286105635143120275902562304n


关于新功能以及如何使用的细节,请参考我们深度解析BigInt的文章。我们期待看见你们用它来创造出美好的东西。


在内存中表示BigInt


通常来说,计算机把整数存储在寄存器中(现在通常是32位或是64位),或者是寄存器大小的内存块中。这也决定了你所熟悉的最小值和最大值。比如,一个32位的有符号整数可以表示的数值是从 -2,147,483,648 到 2,147,483,647。但BigInt并不会有这样的限制。

那么什么东西能够存储一个有成百上千位,甚至百万位的BigInt呢?因为它不能存在寄存器里,所以我们需要在内存中分配一个对象,使它足够大,能够在一系列的内存块中存储所有BigInt的二进制位,我们把它称作“digits” ------ 因为它在概念上类似于我们在表示比9大的数时就需要使用更多的位数,比如“10”;十进制中我们用的digits是从0到9,而我们BigInt的digtis是从 0 到 4,294,967,295 (即2^32 - 1)。这是32位CPU寄存器的范围,不包括符号位,因为符号位是分开存储的。在下面的伪代码中,一个拥有 3*32 = 96 位 的BigInt对象内部是这样的:


{  type: 'BigInt',  sign: 0,  num_digits: 3,  digits: [0x12…, 0x34…, 0x56…],}


回顾课堂上的Knuth


存储在CPU寄存器中的整数运算很简单,比如两个数相乘,软件可以使用一套机器指令来告诉CPU如何把两个寄存器的内容相乘,CPU就会照着做。对于BigInt的运算,我们必须要找到自己的解决方案。万幸的是,这是一个基本上每个孩子都会在某个时间点学着解决的问题:记得你在学校计算 345 * 678 的结果(并且不能使用计算器)的时候吗?


345 * 678---------     30    //   5 * 6+   24     //  4  * 6+  18      // 3   * 6+     35   //   5 *  7+    28    //  4  *  7+   21     // 3   *  7+      40  //   5 *   8+     32   //  4  *   8+    24    // 3   *   8=========   233910

这完全就是V8如何相乘BigInt的方法:每次一位,把所有的中间结果相加。这个方法适用于0到9的digits,同样也适用于BigInt中更大的digits。

1969年,Donald Knuth曾在他的著作《计算机程序设计艺术》的第二卷中发表了专门给由较小块组成的大数的乘除法实现。V8的实现也遵循了这本书,从而说明这一成果并不受时间推移的影响。


”少一点去糖化“ === 更多糖?


也许令人惊讶的是,我们不得不花很多精力去实现一元运算,比如 -x。到目前为止,-xx * (-1)做的事情是一样的。为了简化,V8在处理 JavasScript 的时候会尽早地完成这个替换,这个替换过程完成于语法解析阶段。这个方法被称作“去糖化”,因为它把像 -x 一样的表达式当作 x * (-1)的“糖衣语法”。系统的其他部分(解释器,编译器,和整个运行时系统)并不知道什么是一元运算,因为它们只看见了乘法,这个它们当然必须支持的运算。

然而有了BigInt,这样的实现突然变得不合理了,因为把BigInt和一个数(如-1)相乘肯定会抛出一个TypedError。如果x是一个BigInt,语法解析器不得不将-x去糖为x *(-1n),但是它并不知道x会被解析成什么。所以我们必须停止依赖这个尽早去糖化的过程,取而代之的是,我们应该在系统各处都添加对Number和BigInt一元运算的适当支持。


有意思的位运算


如今大部分的计算机系统都采用了一种巧妙的方法“2的补码”来存储有符号整数,它的一个很棒的特质是用第一位来表示符号,并且加上1之后总能保证所表示的数值也增加1,它可以自动地处理符号位的变化,例如对于一个8-bit的整数:


  • 10000000 是 -128,能表示的最小的数,
  • 10000001 是 -127,
  • 11111111 是 -1,
  • 00000000 是 0,
  • 00000001 是 1,
  • 01111111 是 127,能表示的最大的数。


这套编码系统被广泛使用,许多程序员都期待并依赖使用它。传统BigInt的定义也预示着,如果这里新定义的BigInt遵照的这个定义,那么它必须要使用2的补码。但根据之前的内容,V8的BigInt并没有这样做!


为了根据定义来执行二进制运算,我们的BigInt必须假装在后台使用着2的补码。对于正数来说,这没有什么区别,但对于负数,我们必须做额外的工作去达到这样的效果。这有点像 a&b 一样神奇的效果。如果 a 和 b 都是负的 BigInt,实际上是需要执行以下四个步骤(与之相反两个数都是正数的情况下只需要一个步骤):


  1. 把 a 转化成模拟的2的补码
  2. 把 b 转化成模拟的2的补码
  3. 执行运算
  4. 把计算结果再做模拟2的补码转化为得到实际的结果


你或许会问为什么要反复做转换?因为这样会使所有非二进制运算简单一些。


两种新的类型化数组


BigInt提议中包括了两种类型化数组:BigInt64ArrayBigUint64Array。因为BigInt提供了读和写的方式,我们现在可以定义64位的类型化数组,然而如果有人试图用Number来表示其中的元素,那么就会导致有些位的丢失。这就是为什么这两种新的类型化数组与已经存在的 8/16/32 位整数的类型化数组有所不同:前者使用BigInt来访问元素,而使用Number访问元素会抛出异常。


> const big_array = new BigInt64Array(1);> big_array[0] = 123n;  // OK> big_array[0]123n> big_array[0] = 456;TypeError: Cannot convert 456 to a BigInt> big_array[0] = BigInt(456);  // OK


就像JavaScirpt代码,在表示和处理这两种新的类型化数组与传统的类型化数组代码时有所不同,我们必须泛化类型化数组的实现,让它能够对这两个新的类型化数组有不用的处理方式。


优化的考虑


现在,我们正在提交BigInt的基本功能。它已经具备完整的功能并有稳定的性能(比目前的userland库稍微快一些),但它还没有经过特别的优化过。这么做的理由是,我们的目标是优先完成实际应用的功能支持,其次才是那些人为创建的性能测试。我们首先想看看大家是怎么使用BigInt的,这样我们可以根据这些用例去准确地优化大家真正关心的性能!


比如,如果我们看见相对比较小的BigInt(最多64位)才是重要的实际用例,我们可以采用一个特殊的表示方式来提高它的空间效率:


{  type: 'BigInt-Int64',  value: 0x12…,}


其中的一个细节是,我们仍需要考虑是否应该使用"int64"的取值范围,还是"uint64",亦或是两者都支持 ------ 但请注意,如果支持更少这样的"捷径"我就可以更早地发布,而且讽刺的是,每增加一条"捷径"就会使其他的执行路径慢一点,因为涉及的操作总是需要去检查当前的情况是否符合某条”捷径“的要求。


另一个故事是关于对于BigInt在编译器中的优化支持。对于在64位硬件上运行操作着64位数据的计算密集型应用来说,把这些数值存储在寄存器中会比分配给它们一个对象存储在堆空间上(我们现在的方式)快得多。我们已经有了该怎么去支持这一优化的方案,但我们首先需要弄清这个是否是大家最关心的点,我们是否应该把时间花在解决其他问题上。


相关文章
|
12月前
|
Rust JavaScript 前端开发
《剖开WebAssembly 2.0:C++/Rust内存管理困局与破局》
WebAssembly 2.0 提供更底层控制,带来内存管理挑战。其线性内存模型要求开发者精细规划内存分配、使用与释放,尤其在 C++/Rust 编译为 .wasm 时,需兼顾性能、安全与 JS 交互。合理设计内存布局、遵循对齐规则、避免泄漏与多线程冲突,是构建高效 Web 应用的关键。
308 41
|
存储 Web App开发 缓存
一个简单的弱网差点搞死了组内前端
最近上线了一个 React Native 外访项目,用户为公司外访员,外访员根据公司业务去实地考察,收集记录一些资料,考察记录资料的过程全部用公司配的专用手机,里面安装了当前外访项目APP。目前项目试运行阶段,还没有正式交付。APP项目上线后,在用户真实使用中遇到一些各种各样的问题,有些问题处理时也比较棘手(如弱网情况),这次主要复盘APP在实际场景中的弱网(或网络不稳定)相关的问题。
1404 0
一个简单的弱网差点搞死了组内前端
|
机器学习/深度学习 人工智能 自然语言处理
智能语音识别技术的现状与未来发展趋势####
本文旨在探讨智能语音识别技术的发展历程、当前主要技术特点、面临的挑战以及未来的发展趋势。通过综述该领域的最新研究进展和应用实例,本文为读者提供了一个关于智能语音识别技术的全面概览,并展望了其在未来可能的发展方向。 ####
|
监控 测试技术 开发工具
小游戏对接广告联盟流量变现项目系统/开发模式规则
小游戏对接广告联盟流量变现项目系统开发是一个多步骤的过程,包括需求分析、选择广告联盟、SDK集成与接入、广告位设置与管理、流量变现系统开发、数据埋点与监控及测试与优化。通过这一系列步骤,确保广告能够有效展示并实现变现。
|
存储 安全 Ubuntu
CentOS 与 Debian:主要相似点和不同点
【8月更文挑战第27天】
1570 2
CentOS 与 Debian:主要相似点和不同点
|
传感器 搜索推荐 人机交互
虚拟现实中的人机交互设计:探索未来交互的无限可能
【8月更文挑战第26天】虚拟现实中的人机交互设计是一项充满挑战与机遇的技术领域。随着技术的不断进步和应用场景的不断拓展,我们有理由相信未来VR人机交互将更加自然、直观和个性化。设计师需要不断探索和创新以应对各种技术挑战和用户需求变化,为用户带来更加丰富和愉悦的交互体验。
|
Ubuntu Java Linux
update-alternatives命令如何使用?【20240805】
【8月更文挑战第4天】update-alternatives命令如何使用?【20240805】
952 4
|
JavaScript 搜索推荐 Java
vscode打造舒适的python开发环境
_shigen_ 是一位专注于Java、Python、Vue和Shell等技术的博主,分享成长与认知。本文旨在记录配置Mac Python开发环境的过程,以优化使用体验和效率。内容包括:检查与验证Python版本,设置pip的阿里云镜像源以加速下载,以及VSCode的个性化配置,如选用美观的等宽字体和安装Python、isort(导入排序)及autopep8(代码格式化)插件。通过这些步骤,读者可复刻作者的高效开发环境。关注_shigen_ ,每天学习新知识!
579 0
vscode打造舒适的python开发环境
|
存储 缓存 负载均衡
CC攻击解析与防御策略
CC攻击是DDoS的一种,利用代理服务器向目标发送大量合法请求,消耗服务器资源。识别特征包括命令行大量"SYN_RECEIVED"连接、IP批量异常连接和日志中异常访问模式。防御策略包括提升服务器性能、数据缓存优化、页面静态化、请求速率限制、IP访问限制及使用CDN。专业高防产品提供智能识别和响应,帮助企业构建全面防御体系。
1259 2
|
存储 程序员
Typora设置 “图片自动保存到文档对应目录下” 的方法(亲测有效)
Typora设置 “图片自动保存到文档对应目录下” 的方法(亲测有效)