揭开黑盒 / 第二章 / 可视化讲解

同一个答案,
为什么要绕这么远?

把计算放慢,看看数字在哪里失去了一点,数据为什么来回搬,计算者又在等谁。

01

先猜:一个数加 1,还会是它自己吗?

数越大,缝隙越宽。

固定只加 1。向右拖动,观察橙色目标与绿色落点什么时候分开。

真实 JavaScript Number 运算 · binary64

● 可表示数与实际落点◆ 精确目标
为什么落点会改变?

在 2 的 e 次方右侧,binary64 相邻数的间隔是 2 的 (e−52) 次方。53 时,加 1 正好落在两个数的中点,按“最近值,中点取偶数”舍入回起点。图只画起点右侧;左侧跨过幂次边界时间隔不同。精确整数目标用 BigInt 计算。

这只说明当前表示法容纳不下那个结果。大整数、小数与其他精度格式有各自的空间和计算成本。

换一个顺序,答案也会变吗?

这四个整数的精确和是 2;并排比较左到右、相邻配对、补偿求和。配对不保证每个输入都更准。

看起来绕路的公式

两个接近的平方根相减,会让此前的舍入误差暴露出来。把表达式改写为倒数,可以避开这次相消。展开讲义再核对它的适用范围。

02

先猜:少搬几次,会不会比少乘几次更重要?

一份数据,走了多少回头路?

每个结果格子都来自 A 的一行与 B 的一列。切换计算顺序,沿着每次读写走一遍;绿色表示缓存中已有,橙色表示需要装入。

教学模型 · 每行 2 个数 · 全相联 LRU · 写分配 · 动画速度不代表机器速度
当前一次访问

等待第一步

缓存 / 左边最久未使用

相同输入与缓存条件下,完整运行的模型账本
顺序乘法次数数组访问装入缓存行结果核对
模型省略了什么?

三个数组各自按缓存行对齐;只统计装入,不统计脏行写回、预取、多个缓存层、真实地址冲突和编译器寄存器优化。每个数字占 8 字节。缓存容量是可调的假设,不能用这张表推断本机缓存大小或实际毫秒数。逐格实现的部分和留在局部变量,其余两种实现按源码更新 C。

03

先猜:16 个人会把工作缩短为十六分之一吗?

多出来的手,也要等同一个开头。

可调成本模型 · 单人任务为 100 时间单位

■ 串行准备■ 可分工工作■ 协调汇合灰底为等待 / 空闲

这条时间线基于哪些假设?

准备完成后,剩余工作可均匀拆给所有人,最后统一汇合;协调成本假设为“每增加一人的成本 × (人数−1)”。它是一种可检验的估计,不是调度器记录。将串行占比或协调成本设为零,分别检查理想上限;真实 Worker 计时在下方。

04

先猜:换一种表示,什么时候才划算?

提前做的工作,会在以后省回来吗?

查一次,还是查很多次?

每次求 16 项之和。逐项读取与前缀和真实执行并核对结果;条形只比较数组读取次数,读取成本并未设为实际毫秒。前缀和准备另有写入、分配和算术开销。

留下有用的值,也要记住它在哪里。

真实生成两个 256 项向量,并实际计算稠密与稀疏点积。空间为 typed array 数据区字节数,不含对象头与临时数组。转换要先扫描两份输入;非零比例高时,索引本身可能使空间更大。

05

先猜:再多算几步,总能再准一点吗?

答案慢慢靠近,什么时候可以停?

真实二分迭代 · 蓝线为区间半宽

为什么不直接宣称“误差小于这个数”?

精确实数运算中,根始终在二分区间内,半宽可界定中点的误差。本实现的 mid×mid 使用浮点数,接近精度极限时比较也会舍入;严格误差认证需要带向外舍入的区间方法。这里同时报告实际停止原因,并与 Math.sqrt 的结果对照,Math.sqrt 也不是精确实数答案。

回到真实机器 / 保留与直觉不同的结果

看过过程,再测一次。

同一组小整数输入,先用 BigInt 独立核对每个输出。三种顺序轮换测量各 5 次,保留全部样本;另一组比较使用 1、2、4 个新 Worker,包含启动、输入复制、计算、返回与拼接。

等待测量。较小输入可能接近计时器分辨率。

计时范围与环境

顺序实验测量函数调用,包含输入核验、输出分配与乘法;预热各一次,不包含生成输入和 BigInt 参考答案。Worker 实验每轮新建 Worker、复制完整 A/B,包含脚本加载和合并,参考核对在计时之外。没有测 GPU,也未读取 CPU 性能计数器。浏览器优化、后台负载、电源状态和计时器精度都可能影响结果。