Uploaded avatar of iHiD

细啃十一月来啦

@iHiD
接近3年前
视频

Introduction

大家好。欢迎来到十一月。希望你们一切都好。

刚过去的十月我们忙得不可开交。我们刚刚对社区解答做了一次大改进。 我们现在会对它们去重,所以相似的解答只会显示一次;我们还新增了多种排序方式、按代码搜索的能力,如果你去看 C#,会发现我们还加了按不同编程概念筛选的功能。 这样你就能搜索使用位移、递归或其他任何你感兴趣的东西的解答。我们会在社区周把它推广到其他 track 上。

不过现在,我们先把注意力放到 #12in23 上。 十月是个有趣的月份,我们探索了面向对象语言,而本月我们要来点硬核的。 我们聚焦汇编语言,具体来说是 MIPS 汇编、x86-64 汇编和 WebAssembly。 和往常一样,Erik 会带我们了解这些语言为何有趣、为何独特。

The badges

和往常一样,完成这些语言中任意 5 道练习,就能获得 Nibbly November 徽章。我们还有全年徽章,我知道很多人都在朝着它努力。为此我们准备了 5 道精选练习供你完成,分别是:

  • 位数统计:数出一个数字里 1 的个数
  • 麦粒:计算棋盘上每格翻倍的麦粒总数
  • 电阻色环:把电阻色环的颜色转换成对应的数值
  • 旋转密码:实现旋转密码(又称凯撒密码)
  • 核苷酸计数:计算每种核苷酸在 DNA 字符串中出现的次数

Background

Why name this Nibble November?

嗯,你大概知道,一个 byte 是 8 位。而一个 nibble 是 4 位。如果你把 byte 里的 y 换成 i,就得到 bite,这个名字就开始讲得通了。而 nibble 就是咬一小口。

What is an assembly language?

好,我们先从一些基础讲起。CPU 执行指令,比如“把两个数相加”或“把位左移”等等。这些指令被称为机器码指令,它们不过是特定的位序列。执行一个程序,“简单来说”(打个引号)就是 CPU 处理并执行这些位序列。

由于直接把指令写成位序列既繁琐又容易出错,早在 1947 年,Kathleen Booth 和 Andrew Donald Booth 就提出了一种对人类更友好的语言来表示机器码指令。这种用来表示机器码指令的语言就叫汇编语言。汇编语言再通过“汇编器”转换成机器码指令。

汇编语言一个有意思的地方是,它们与 CPU 架构直接绑定,不过通常独立于操作系统。

顺便说一句,如果你还记得穿孔卡片,就是最早期的计算机用来执行程序的那种大卡片,它们其实也是一种汇编语言!

How is assembly language different from the languages we now program in?

关键区别在于汇编语言非常底层。你会失去许多已经习惯的抽象。类或对象完全不见踪影。循环?你得通过跳转手写出来。函数?没门!写汇编代码让我感到很谦卑,因为你会意识到现代语言为你的生活带来了多少便利。不过,写汇编代码也非常有用,因为你会对事物的实际运作方式有更深入的理解。

有趣的是:RollerCoaster Tycoon 99% 的源代码都是手写的汇编代码!这是一项惊人的成就,等你亲自写一点汇编之后,会更加体会到这一点。

Do people still write in assembly code?

嗯,比过去少了。过去,手工编写的汇编常常比编译器生成的机器码性能更好(这也是 C++ 允许直接内嵌汇编代码的原因之一),但如今编译器生成机器码的水平已经非常高,这种情况已经很少见了。尽管如此,在性能关键或资源受限的环境中,你仍然能看到汇编语言的身影。

Overviews

MIPS

  • MIPS(Microprocessor without Interlocked Pipelined Stages)是一系列精简指令集计算机(RISC)指令集架构
  • 由 MIPS Computer Systems 开发,于 1985 年首次发布
  • 有多个版本:MIPS I、II、III、IV、V 和 MIPS32/64。前两个版本只支持 32 位,而 MIPS III 引入了 64 位支持。
  • 若干可选扩展,例如 SIMD 指令和压缩
  • 对后来的 RISC 架构影响深远
  • MIPS 在 2021 年宣布不再继续开发 MIPS 架构,并迁移到 RISC-V(一个开源、免版税架构)
  • 主要用于嵌入式系统(如路由器)和服务器(Silicon Graphics 的计算机用过它,这些计算机以电影特效闻名)、NEC Cenju-4 超级计算机、特斯拉 Model S 汽车、NASA 的新视野号探测器,也用于在大学里教授汇编,以及多款游戏主机(如初代 PlayStation、PlayStation Portable 和 Nintendo 64)

x86-64 Assembly

  • 由 AMD 设计,于 1999 年以 AMD64 架构发布
  • 它是 x86 指令集的 64 位版本,x86 可追溯到 1978 年 Intel 推出 8086 微处理器的时候。那是一颗 16 位处理器,后来 80386 加入了 32 位指令,而那套指令集后来就成了 x86 的代名词。
  • 64 位带来的核心能力是可以寻址更多内存(32 位寻址上限为 4GB),而内存曾经是个瓶颈。64 位理论上可以寻址 16 EB,但目前只用了 48 位,可寻址 256 TB(以后有需要时可以扩展)
  • AMD64 扩展了 x86 指令集,并通过兼容模式设计与现有的 16 位和 32 位应用程序完全兼容
    • Intel 在没有 AMD 参与的情况下设计了 IA-64。它是一套全新、迥然不同且不向下兼容的 64 位指令集。最终 AMD64 胜出,Intel 也实现了自己的版本(只有细微的语义差异)
  • 无处不在。从工作站到服务器(包括超级计算机),从嵌入式系统到游戏主机(如 PS5 和 Xbox Series X)。

WebAssembly

  • 由 W3C 设计,这是一个负责 Web 技术的标准组织
  • 设计目标是:
    • 快速、安全、可移植
    • 高效且可移植的表示形式
  • 当年,要在 Web 上实现快速执行,通常得靠专门的浏览器插件,比如 Flash 和 Silverlight,因为 JavaScript 本身并不太适合高性能计算。这些插件的主要缺点是往往有大量安全问题,而且没有标准化。
    • Mozilla 设计了 asm.js,它是 JavaScript 的一个子集,旨在让代码在浏览器中运行时有出色的性能表现,它通过类型一致性(类型不能动态改变)和没有垃圾回收来实现这一点。各种语言于是可以编译成 asm.js,在 Web 上运行时依然能获得不错的性能。不过它终究还是 JS,能做到的事情有限。于是就有了新语言的提案:WASM。
  • 类汇编语言,因为它提供了一组要执行的指令。关键在于,它不绑定特定的 CPU,因此与平台无关,每个平台都需要一个实现(虚拟机)。这意味着 WebAssembly 实际上是字节码,而不是机器码
  • 静态类型(与 JS 的关键区别)
  • 通常使用提前编译或即时编译(但也可以解释执行)
  • 开放标准,定义了两样东西:
    • 二进制格式
    • 文本格式(会被编译成二进制格式)
  • 所有主流浏览器都有实现
  • 用于许多需要高性能的网页,比如 Google Earth、Figma、Unity 和 Autocad。在服务端也越来越受欢迎,比如用来运行微服务、运行在 SaaS 平台上(如 CloudFlare workers),或用在 Docker 中

And from a programming perspective, how do they differ?

MIPS

  • 采用加载-存储架构(又称寄存器-寄存器架构),指令要么做内存访问,要么做算术运算,但都只对寄存器中的数据进行操作

x86-64 Assembly

  • 采用寄存器-内存架构,允许对内存(或从内存)以及寄存器执行操作

WebAssembly

  • 采用基于栈的编程(没有寄存器),并且可以选择从内存读取数据以及写入内存

What are the things that make these languages great?

MIPS

  • 小巧。MIPS 指令集的所有指令一页就能装下
  • 成熟的调用约定有助于弄清如何使用可用的寄存器,比如用哪些传递实参、用哪些返回结果。
  • 稳定。最后一个版本发布于 2014 年。
  • 文档丰富,尤其在学术教材中。
  • 实际应用广泛。数十亿台设备。

x86-64 Assembly

  • 虽然它是 x64 的扩展,但加入了许多新特性,包括:
    • 64 位整数支持
    • 更多寄存器
    • SSE 指令(向量指令)
    • 相对数据访问(使用共享库时更高效)
    • 不可执行位(一项安全特性,防止在某些内存页中执行代码)
  • 熟悉。由于它扩展了 x86 指令集,熟悉 x86 指令集的人学起来会相对容易。 文档详尽而全面。
  • 稳定。虽然会定期加入新版本,但核心部分极其稳定,且向下兼容。

WebAssembly

  • 与物理处理器的汇编语言(包括 RISC)相比,基于栈的 WebAssembly 虚拟机精简而简单。这使得为它编译相对容易。
  • WebAssembly 文本格式使用 S 表达式的“语法糖”来实现熟悉的命令式风格,再被转换成基于栈的代码。带 S 表达式的形式称为“加糖形式”,它会被“脱糖”成另一种形式,与二进制中的内容等价。任何用过 LISP 的人都会熟悉 S 表达式。
  • 与 JavaScript 的互操作性强。在 JavaScript 之间来回传递数据很简单。重要提醒:WASM 目前还不能与 DOM 交互
  • 持续改进中。不仅 WASM 虚拟机在不断改进,标准本身也在积极开发中。大量新特性正在设计和开发,比如与 SIMD 相关的指令、垃圾回收、线程、尾调用优化等等
  • 安全。代码会在沙箱环境中被验证并运行,相比 JavaScript 或原生汇编语言,提供了更高程度的静态验证。语义定义明确,便于检查与推理

Standout features

MIPS

  • 高效。MIPS 处理器非常高效,非常适合嵌入式系统。
  • 性能。性能出色,因此 MIPS 曾被用于超级计算机
  • 易于学习。指令少,每条指令只做一件简单的事,因此容易上手,非常适合教学。

x86-64 Assembly

  • 强大。x86-64 经过几十年的打磨,拥有大量有助于提升性能的指令。一个例子是 SIMD(单指令多数据),这类指令,嗯,能让一条指令在多个数据上并行执行。
  • 无处不在。运行 x86-64 的设备随处可见。Intel 和 AMD 的 CPU 都实现了它,它已经做了相当长时间的事实标准
  • 定期更新。比如新的向量指令,包括 SSE3-5、AVX、AVX-512 等等

WebAssembly

  • 高效。二进制格式紧凑,可以在一次快速遍历中完成解码、验证和编译。它还支持流式处理,可以在尚未收到全部数据时尽早开始解码、验证和编译。它还可以并行处理,因此非常适合高性能的 Web 应用。
  • 优秀的编译目标。让许多语言的代码都能在 Web 上运行。大多数主流语言都支持编译成 WebAssembly 二进制文件,这意味着你的代码无需编写 JavaScript 就能在 Web 上运行。有些语言不是把你的代码编译成 WebAssembly,而是把运行时编译成 WebAssembly,然后就能运行未经改动的字节码。
  • 易于部署和运行。你只需要一个能解释字节码的虚拟机,而所有主流浏览器都自带一个。
  • 不局限于 Web,也能在服务端运行。WebAssembly 系统接口(WASI)是一个旨在可移植到任何平台的接口(ABI 和 API)。它类似于 POSIX(Unix 系统的标准接口),并提供 I/O 之类的功能。安全是其设计的核心部分,包括沙箱化以及面向能力(你需要显式请求对文件或套接字等资源的权限)。WASI 还有潜力让不同语言之间轻松互操作。Docker 联合创始人 Solomon Hykes 在 2019 年写道:“如果 2008 年就有 WASM+WASI,我们根本不需要创造 Docker。”

Which to choose

  • 如果你从未接触过汇编语言,WebAssembly 大概是最容易上手的。话虽如此,如果你想学一门会编译成机器码的汇编语言,那就试试 MIPS 汇编
  • 如果你用的是 x86-64 机器(很可能),那就试试 x86-64 汇编
  • 如果你熟悉 LISP,你会欣赏 WebAssembly 使用 S 表达式这一点
  • 如果你在做 Web 应用,WebAssembly 是最合理的选择
  • 如果你在意性能,x86-64 和 MIPS 都是很好的选择。或者,如果你在意 Web 性能,就试试 WebAssembly
2023年11月01日 · 觉得有用吗?