Softmax 算子的实现及优化
思路和代码来自 B 站 UP 主 https://space.bilibili.com/1822828582 的课程。 Softmax 简介Softmax 的作用是将输入向量映射到一个所有元素值域在 [0, 1] 之间、且总和为 1 的概率分布上。 计算公式给定一个输入向量 $\mathbf{z} = [z_1, z_2, …, z_n] \in \mathbb{R}^n$,Softmax 的原始公式定义如下: \text{Softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{n} e^{z_j}}, \quad \forall i = 1, 2, ..., n 注意:为了避免指数爆炸(Numerical Overflow),在实现时通常会将所有输入减去输入向量中的最大值。 CPU 实现CPU 版本是最基础的串行实现,每个大循环处理一行数据,主要分三个步骤: 求最大值:遍历当前行,找到最大值 maxval。 计算指数和求和:对每一项减去最大值后求指数 expf(),同时累加求和 sum,结果保存在输出数组中。 归一化:对输出数组中的每一...
L1 Cache 组相联度探测
这篇博客主要介绍一种在已知 L1 Data Cache 大小的情况下,探测其 组相联度 (Associativity) 的方法。 背景其实组相联度通常不需要”探测”,因为现代 CPU 为了支持 VIPT (Virtually Indexed, Physically Tagged),通常遵循以下公式: \text{Cache总大小} / \text{组相联度} \leq \text{页大小} (4KB)为了最大化 Cache 容量和 Set 数量,设计时通常取等号。例如,Golden Cove 的 P-Core L1 Data Cache 大小为 48KB。根据公式 $48KB / \text{Ways} = 4KB$,很明显它的组相联度就是 12 路。 关于 VIPT 的详细解释,可以参考我的另一篇博客:Virtually Indexed, Physically Tagged 那么,有没有一种不通过计算,而是通过编写测试代码的方式来探测验证这一结论呢? 答案是有的。这种方法虽然不能给出精确数值,但能通过性能特征推断出大致范围。 探测原理测试方法的核心思想是:制造 Cache...
2026.1.19 - 2026.1.25 周报
这周主要是把 CPU 的前后端主要组件过了一下,做了一些简单的微基准测试,学习了对应工具的使用方法,ebpf 的内存泄漏工具大概过了一遍。 测试了一个 Windows 特定版本存在的类似预映射机制,也就是当正向访问一块匿名内存的时候,累计访问的 4K 页超过特定阈值(这里是 16),后续的访问并不是在缺页异常中只映射当前这个页,而是会映射加上本页和后面的一共 16 页,跨 PT,也就是累计 512 个 4K 页后,阈值会清零。由于是黑盒,所以只能大概知道有这个机制,wrk 中有一个类似机制叫页面聚类,不过是针对文件映射的,不是匿名内存。 了解了一下奔腾 4 的失败,为了 10GHZ 的频率将流水线加到了 31 级,导致了巨大的分支预测惩罚和无法控制的发热。 对当前 CPU 的后端 AGU 端口数量进行了探测,也就是 load 微指令的单核并行能力。具体来说就是固定 load 数量,更改每个循环中的 mov 读取指令数量,看他的线性提升什么时候结束。探测出来的 load 端口是 3 个,符合当前的硬件数据,也就是 Golden Cove,具有 3 个 AGU 端口。为了确定他...
2026.1.12 - 2026.1.18 周报
这周做了一部分 https://github.com/CppTrainingHub/training-cpp 的题目,把计网的八股大概过了一遍。 看了几篇 https://www.computerenhance.com/ 里面关于性能优化的文章,学习了一下循环展开、打破并行依赖链、向量化等常规优化手段。 了解了 VIPT,硬件层面的优化手段,根据 cache 大小,修改组相联的路数,满足 Cache 总大小 / 路数 <= 页大小,来实现虚拟地址的低 pagesize 位中包含了 cacheline 的 set,使得取出 set 中的 cacheline 的 tag 可以和 mmu 虚拟地址转化并行进行。 了解了下 Python 3.11 引入的优化手段:Specializing Adaptive Interpreter,总结来说就是一个特化,当一个操作接受的参数一直是某个类型,次数达到了阈值,这个操作就会变成这个类型的特化版本,减少了运行时检查开销。 以及 Python 3.13 引入的 JIT,和 JVM 运行时的中间语言编译为机器码不同,Python 是在构建 CPy...
2026.1.5 - 2026.1.11 周报
这周把 ipc、socket 网络编程、文件操作大概看了下,写了一部分练习代码。ipc 看的时间比较多,主要是结合 wrk 看了一下他的内核实现吧。也简单学习了一下 linux 上的 namespace、cgroup 机制。 编译器的项目写完了,下一个应该会写一个基于 ebpf 的内存泄露检测工具,我看训练营也有基于 LD_PRELOAD 的内存泄露和死锁的检测工具,我看能不能整合一下。 要做内存泄露、死锁检测,他涉及到的一个核心就是执行流的重定向。在重定向到我们写的代码后,做记录、判断等操作。 哪具体如何重定向呢,有下面几个流派:没有标注的都是 ring3 可以实现的。 inline hook:在目标函数中写 jmp,跳转到自己写的函数。这种破坏性比较强,不好管理,需要写一个跳板来保存跳转入口。需要保存破坏的指令,这就需要集成反汇编引擎来判断破坏的指令的字节码长度。比较臃肿,且稳定性差。 异常 hook:调试器常用的 hook,包括 ebpf 也在使用。分为以下几类: 0xcc 替换目标的机器码,进入 idt[3] 处理例程,内核派发异常,内核/用户调试器接受。 硬件断点,监...
Alignment
这篇博客来聊一个比较重要的概念:对齐 (Alignment)。 在 CPU 执行代码的过程中,一个比较严重的性能瓶颈就是 Back-end Memory Bound(后端访存延迟)。 Intel Haswell 访存延迟参考为了有个大概的概念,我们先看下 Intel Haswell 架构的具体访存延迟数据: 数据来源: 7-cpu.com - Haswell配置: Intel i7-4770 (Haswell), 3.4 GHz, RAM 32 GB (PC3-12800 cl11 cr2) L1 Data cache: 32 KB, 64 B/line, 8-WAY L1 Instruction cache: 32 KB, 64 B/line, 8-WAY L2 cache: 256 KB, 64 B/line, 8-WAY L3 cache: 8 MB, 64 B/line 具体延迟如下: L1 Data Cache Latency: 4 cycles (pointer simple access) 5 cycles (complex address calcula...
L1/2/3缓存探测
又是一篇cpu硬件探测的博客,这篇是探测L1(数据)/2/3缓存。 测试方法采取的方式如下: 每次读取:32 字节(一条 AVX2 指令) 每轮循环:256 字节 测试范围:在指定大小内循环(从1KB到32MB) 总读取量:1GB 核心代码listing_0152_cache_test.asm 1234567891011121314151617181920212223242526272829Read_32x8: xor r9, r9 mov rax, rdx align 64.loop: ; Read 256 bytes vmovdqu ymm0, [rax] vmovdqu ymm0, [rax + 0x20] vmovdqu ymm0, [rax + 0x40] vmovdqu ymm0, [rax + 0x60] vmovdqu ymm0, [rax + 0x80] vmovdqu ymm0, [rax + 0xa0] vmovdqu ymm0, [rax + 0xc0] vmovdqu ymm0,...
CPU 后端端口探测
CPU 后端流水线在经过取指、解码为 μops、寄存器重命名、分配 ROB 条目后,μops 到达调度器。当数据就绪且执行端口空闲时,它就会被发射到后端的执行单元端口中: INT Domain (整数域):ALU 负责加减乘除、位运算、跳转计算 FLOAT/VEC Domain (浮点/向量域):FPU/SIMD 单元负责浮点运算、AVX/SSE 指令 MEM Domain (访存域 / AGU):地址生成单元,负责计算 MOV RAX, [RBP+8] 中的内存地址 同时还有并行的 LSU(Load/Store Unit),负责 Load/Store 操作,让 MMU 将 AGU 计算出来的地址翻译为物理地址,接着与 Cache/DRAM 交互,进行地址的读取写入。 超标量与端口瓶颈我们知道现代 CPU 都是 超标量 的,也就是有多条流水线、多执行单元,来实现单核并行。 后端端口的数量是单核并行能力的一个瓶颈,尤其是在计算密集型任务中。 本机配置 i9-12900,分为性能核和能效核,它们分别是: 核心类型 微架构 Load 端口 Store 端口 性能核...
Pipeline Depth & CPU Frequency
这篇博客简单介绍两个概念:流水线深度 和 频率。 基本概念 流水线深度:一条指令流经 CPU 的过程被切分出来的步骤数量 频率:每秒钟流水线向前推进多少个步骤 现代流水线设计现代教学用的 CPU 模拟器一般是 5 级流水线,比如 gem5 中的一个 CPU 模型:O3CPU - 5 级乱序流水线 1Fetch → Decode → Rename → IEW → Commit 以本机的性能核 Golden Cove(2021 年推出)为例,根据 μops cache 是否命中、指令复杂度等因素,流水线深度在 14~19 级。 历史教训:Pentium 4 的 NetBurst 架构在历史上,CPU 流水线深度并不是稳步增长的。 20 多年前,英特尔曾两次尝试用完全不同的东西来取代其坚固但陈旧的 P6 微架构: 安腾 (Itanium):避免与乱序执行和可变长度解码相关的复杂性,提供非常宽的顺序执行 奔腾 4 (Pentium 4):基于 90nm(”Prescott”)工艺,于 2004 年推出,流水线深度达到了惊人的 31 级 10 GHz 的梦想破灭Pentium 4...
Page Fault Clustering
注意:本文中 Windows 平台的 PageFaultCount 使用 GetProcessMemoryInfo 获取,该字段并不是 Page Fault 进入内核的次数,而是映射的页面数。详见 WRK 源码。 在 WRK 中,匿名内存并不会触发聚类,只有文件映射会触发。但在 Win11 10.0.26100 Build 26100 上观测到了匿名内存的类似机制,猜测新版本做了实现。 引言我们都知道,不管是在 Linux 还是在 Windows,使用 mmap 或 VirtualAlloc 返回的内存只是在 VMA/VAD 上做了记录,并没有被真正分配出来——没有从管理物理页面的结构体中把物理页面拿出来,没有把这个物理页面的 PFN 挂在虚拟地址对应的 PTE 上。这些操作被推迟到 访问这块内存的时候 进行。 但我们思考一个问题:现在的操作系统/编程语言有着各种操作来避免频繁进入内核。例如: 操作系统、语言 (Go) 的 堆管理器:后端大量从操作系统批发,切分给前端使用 USER_SHARED_DATA:同一个物理内存映射在内核空间和用户空间,使得可以不进入内核就获取系统...













