Playdate 掌机的 CPU 运行非常快,但访问主内存——尤其是数据尚未缓存时——相对较慢。这种不对称意味着,若程序将工作数据保持在寄存器内,并将最常执行的代码放在有限的指令缓存中,即使包含许多循环或存在分支预测错误,也能接近峰值速度运行。因此,针对模拟器或仿真器等高性能工作负载的开发者,应优先通过最大化寄存器使用并使核心循环紧凑地适配指令缓存,以最小化对慢速主内存的访问。
为了减小指令缓存占用,作者建议使用 -Os 而非 -O3 进行编译,将大的 switch 表精简为较少的分支,并将很少使用的操作码或边缘情况隔离在热核心之外。通过使用 __attribute__((section (``.text.<name>'')) ) 标记函数并使用从 SDK 的 C_API/buildsupport 文件夹复制的自定义链接脚本,链接器可以将所有核心函数放置在一个连续的块中,其大小可通过 nm Source/pdex.elf | sort > syms.txt 检查。对于数据,紧耦合内存(TCM)区域——硬件栈所在位置——提供比普通 RAM 更快的访问速度;栈分配的对象因而运行更快,开发者可以在计算期间将堆或静态结构 memcpy 到预留的低栈区域(dtcm_mempool),随后将结果复制回来。同样,函数可通过添加 __attribute__((section (``.itcm. '')) ) __attribute__((short_call)) 的宏标记为指令 TCM(ITCM)区域,这些函数在运行时从 .text 复制到 TCM,需注意地址对 2 的 congruence、short_call/long_call 属性,并在复制后刷新指令缓存。
即使应用了上述技巧,构建仍可能出现 ``performance lottery'',即看似无关的更改会导致速度出现大幅、不可预测的波动。这源于两种硬件效应:缓存行不对齐(32 字节行大小意味着,若函数起始地址不是 32 的倍数,则可能跨越两行,因而引入不需要的邻居或使有用代码未被缓存)以及分支预测表(相差 1024 倍数的指令地址可能共享同一预测条目,导致预测错误)。缓解方法包括在链接器映射中 deliberate 地将函数和循环对齐到 32 字节边界(如 . = ALIGN(32); . += offset)或使用如 -falign-loops=32 的编译器标志,并根据已知良好的构建撒布 . = ALIGN(1024); . += n 选择以装饰化预测索引。虽然在 Playdate 上 prefetching 的益处有限,但这些对齐技术在实践中基本消除了 performance lottery 现象。
