导言
- 构建复杂模型:学习如何构建更复杂的神经网络模型,如卷积神经网络(CNN)、循环神经网络(RNN)等。
- 损失函数与优化器:理解不同的损失函数(如交叉熵、均方误差)和优化器(如SGD、Adam)。
- 训练与验证:编写训练循环,理解如何监控训练过程,防止过拟合。
在进程/线程并发执行的过程中,进程/线程之间存在协作的关系,例如有互斥、同步的关系。
为了实现进程/线程间正确的协作,操作系统必须提供实现进程协作的措施和方法,主要的方法:
这两个都可以方便地实现进程/线程互斥,而信号量比锁的功能更强一些,它还可以方便地实现进程/线程同步。
Test-and-Set 和 Compare-and-Swap (CAS) 都是并发编程中的重要原子操作指令,通常用于同步和处理多线程或多进程环境中的竞争条件。它们的作用是确保某些操作在执行时不会被中断,以保持数据一致性和避免并发冲突。我们来详细了解这两个命令。
它的操作过程如下:
操作是原子的,即不会被中断或干扰,保证在并发环境下,多个线程或进程不能同时修改同一位置。
用途:
它的操作步骤如下:
CAS 也具有原子性,这意味着它在操作期间不会被中断。CAS 是实现无锁编程和避免线程同步问题的常用工具,尤其在需要高性能的并发程序中。
用途:
Test-and-Set 指令,XCHG 被用作实现类似功能的原子操作。CMPXCHG 指令,ARM 等其他架构也有类似的原子操作指令(如 LDREX 和 STREX)。std::atomic 是对 Test-and-Set 和 Compare-and-Swap 等底层原子操作的高级封装,使得 C++ 开发者可以在多线程环境中更方便、更安全地使用这些原子操作。
原子操作(std::atomic)有如下特点:
std::atomic 只能用于一些简单的类型(如整数、指针和布尔类型),对于复杂的数据结构,仍然需要其他同步机制(如锁)。std::memory_order对于一个线程里的同一个代码块内的无依赖关系的指令,处理器在运行时会重排序乱序执行。这对原子指令也是一样的。但实际生产中,多个原子操作之间,可能会有依赖关系,比如一个线程读取另一个线程写入的数据。为了确保这些依赖关系的正确性,需要使用内存顺序来控制处理器对指令的重排序。
std::atomic 的操作支持指定内存顺序,这控制了编译器和硬件对操作的优化和重排序。常见的内存顺序选项有:
std::memory_order_relaxed:没有限制,运行时会乱序。std::memory_order_acquire 和 std::memory_order_release:分别用于“获取”和“释放”同步,确保操作顺序。std::memory_order_seq_cst:默认的内存顺序,保证在多线程程序中,所有原子操作按顺序执行,不允许重排序。但性能可能会略受影响。store在原子操作中,store 用来将一个值存储到原子变量中。与普通赋值不同,store 会确保在多线程环境下,赋值操作是原子性的(即不会被打断)。
1 | std::atomic<int> counter(0); |
store 会保证在指定的内存顺序下将值存储到原子变量中,因此它是线程安全的。loadload 用于从原子变量中读取值。它会确保在多线程环境中,读取操作是线程安全的,并且可以指定内存顺序。
1 | int value = counter.load(std::memory_order_acquire); // 从原子变量读取 |
load 会确保读取的是正确同步后的值,避免在多线程场景下出现读取错误。compare_exchange_strongcompare_exchange_strong(以及 compare_exchange_weak)广泛应用于实现锁和无锁算法。它会尝试将原子变量的值与一个预期值进行比较,如果相同,则将其更新为新值。如果比较失败,原子变量的值不会更改,并且返回 false。1 | std::atomic<int> value(0); |
exchangeexchange 是一种常见的原子操作,类似于 store,但它会返回原子变量的先前值。
1 | int oldValue = counter.exchange(5); // 返回更新前的值,并将 counter 设置为 5 |
exchange 在多线程环境下是安全的,并且可以返回修改前的值,适用于某些需要获取旧值的场景。多线程同步的一种忙等待锁,线程反复检查锁变量是否可用。
1 | std::atomic_flag lock = ATOMIC_FLAG_INIT; |
把自己阻塞起来(内核态和用户态之间的切换进入阻塞状态,可能上下文切换),等待重新调度请求。
适用于大段的修改和同步。
eventfd 事件通知eventfd 是 Linux 提供的一种用于线程安全的事件通知机制,类似于文件描述符,可以通过读写操作来实现跨线程或跨进程的同步。通过 eventfd,线程或进程可以通过某些事件(例如计数器增减、通知等)来触发其他线程或进程的动作。
eventfd 可以通过两种方式工作:
eventfd_write 增加计数器,其他线程或进程可以通过 eventfd_read 来读取这些计数器的值。eventfd 进行事件通知,线程或进程通过 eventfd_read 来等待并响应这些事件。eventfd_read 的功能 : eventfd_read 是一个用于从 eventfd 文件描述符读取事件的系统调用。它会从 eventfd 中读取一个 64 位无符号整数,并返回这个值。这个值通常表示某个事件的状态或计数器的值。
https://www.cswiki.top/pages/f398f1/#blocking-i-o
原文链接:https://blog.csdn.net/qq_15437629/article/details/79116590
LLVM Machine Code Analyzer 是一种性能分析工具,它使用llvm中可用的信息(如调度模型)静态测量特定CPU中机器代码的性能。
性能是根据吞吐量和处理器资源消耗来衡量的。该工具目前适用于在后端中使用LLVM调度模型的处理器。
该工具的主要目标不仅是预测代码在目标上运行时的性能,还帮助诊断潜在的性能问题。
给定汇编代码,llvm-mca可以估计每个周期的指令数(IPC)以及硬件资源压力。分析和报告风格的灵感来自英特尔的IACA工具。
https://github.com/llvm/llvm-project/tree/main/llvm/tools/llvm-mca
https://llvm.org/docs/CommandGuide/llvm-mca.html
1 | -mtriple=<target triple> |
1 | # 查看支持的arch |
1 | -output-asm-variant=<variant id> |
1 | -dispatch=<width> |
1 | -resource-pressure |

1 | Iterations: 300 |
1 | Instruction Info: |
显示了指令里队列每条指令的延迟和吞吐量的倒数。
RThroughput是指令吞吐量的倒数。在不考虑循环依赖的情况下,吞吐量是单周期能执行的同类型指令的最大数量。
1 | Resources: |
每次循环或者每条指令执行,消耗的资源周期数。从而找到高资源占用的部分。
可打印流水线情况
1 | Timeline view: |
影响因素包括:
1 | Cycles with backend pressure increase [ 91.52% ] |
llvm/lib/Target/X86/X86SchedSandyBridge.tdllvm/lib/Target/AArch64/AArch64SchedTSV110.td执行时使用的平均或最大buffer entries (i.e., scheduler queue entries)
AMD Jaguar
JALU01 - A scheduler for ALU instructions.
JFPU01 - A scheduler floating point operations.
JLSAGU - A scheduler for address generation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
5. Retire Control Unit
1. 在一个周期里有多少指令retired的占比(好吧,感觉有语病)
6. A re-order buffer (ROB) 的使用情况
7. Register File statistics
1. physical register file (PRF)
2. floating-point registers (JFpuPRF)
3. integer registers (JIntegerPRF)
## Instruction Flow
llvm-mca 假设指令在模拟开始之前已经全部解码并放入队列中。因此,指令提取和解码阶段没有被计算。未考虑前端的性能瓶颈。此外,llvm-mca 不模拟分支预测。
### Instruction Dispatch
处理器的默认 dispatch width值等于LLVM’s scheduling model里的IssueWidth值。
An instruction can be dispatched if:
* The size of the **dispatch group** is smaller than processor’s dispatch width.
* There are enough entries in the **reorder buffer**.
* There are enough **physical registers** to do register renaming.
* The schedulers are **not full**.
reorder buffer负责跟踪命令,使之按照程序顺序retired结束。其默认值为 MicroOpBufferSize 。
各种Buffered resources 被视作scheduler resources.
### Instruction Issue
每个处理器调度器实现一个指令缓冲区。指令必须在调度程序的缓冲区中等待,直到输入寄存器操作数可用。只有在那个时候,指令才符合执行的条件,并且可能会被发出(可能是乱序的)以供执行。 llvm-mca 在调度模型的帮助下计算指令延迟。
llvm-mca 的调度器旨在模拟多处理器调度器。调度器负责跟踪数据依赖关系,并动态选择指令消耗哪些处理器资源。它将处理器资源单元和资源组的管理委托给资源管理器。资源管理器负责选择指令消耗的资源单元。例如,如果一条指令消耗了一个资源组的1cy,则资源管理器从该组中选择一个可用单元;默认情况下,资源管理器使用循环选择器来保证资源使用在组的所有单元之间均匀分配。
llvm-mca’s scheduler internally groups instructions into three sets:
* WaitSet: a set of instructions whose operands are not ready.
* ReadySet: a set of instructions ready to execute.
* IssuedSet: a set of instructions executing.
### Write-Back and Retire Stage
retire control unit
1. When instructions are executed,the flags the instruction as “ready to retire.”
2. Instructions are retired in program order
3. free the physical registers
### Load/Store Unit and Memory Consistency Model
load/store unit (LSUnit)用来模拟乱序memory操作
The rules are:
1. A younger load is allowed to pass an older load only if there are no intervening stores or barriers between the two loads.
2. A younger load is allowed to pass an older store provided that the load does not alias with the store.
3. A younger store is not allowed to pass an older store.不能交换顺序的意思
4. A younger store is not allowed to pass an older load.
假设 loads do not alias (-noalias=true) store operations.Under this assumption, younger loads are always allowed to pass older stores. ???
LSUnit不打算跑alias analysis来预测何时load与store不相互alias???
in the case of write-combining memory, rule 3 could be relaxed to allow reordering of non-aliasing store operations.???
LSUnit不管的其余三点:
1. The LSUnit does not know when store-to-load forwarding may occur.
2. The LSUnit does not know anything about cache hierarchy and memory types.
3. The LSUnit does not know how to identify serializing operations and memory fences.
4. The LSUnit does not attempt to predict if a load or store hits or misses the L1 cache(不考虑cache命中,默认是命中L1,产生the load-to-use latency的最乐观开销)
llvm-mca 不知道序列化操作或内存屏障之类的指令。 LSUnit 保守地假设同时具有“MayLoad”和未建模副作用的指令的行为类似于“软”load-barrier。这意味着,它在不强制刷新load队列的情况下序列化加载。类似地,“MayStore”和具有未建模副作用的指令被视为store障碍。完整的memory-barrier是具有未建模副作用的“MayLoad”和“MayStore”指令。LLVM的实现是不准确的,但这是我们目前使用 LLVM 中可用的当前信息所能做的最好的事情。
load/store barrier会占用在load/store 队列里占用一项。
当load/store barrier是其队列里oldest项时,其会被执行

### In-order Issue and Execute
有序处理器被建模为单个 InOrderIssueStage 阶段。它绕过 Dispatch、Scheduler 和 Load/Store 单元。一旦它们的操作数寄存器可用并且满足资源要求,就会发出指令。根据LLVM的调度模型中IssueWidth参数的值,可以在一个周期内发出多条指令。一旦发出,指令就会被移到 IssuedInst 集,直到它准备好retire。 llvm-mca 确保按顺序提交写入。但是,如果 RetireOOO 属性for at least one of its writes为真,则允许指令提交写入并无序retire???
## Custom Behaviour 自定义行为
某些指令在该模型中并不能被准确的模拟。为了几条指令而修改模型不是个好的选择,一般通过**CustomBehaviour**类对某些指令进行特殊建模:自定义数据依赖,以及规避、单独处理特殊情况。
为此,llvm-mca设置了一个通用的以及多个特殊的**CustomBehaviour**类。下面两种情况下会使用通用类:
1. 开启了`-disable-cb`选项
2. 不存在针对某目标的特殊类(通用类也做不了什么,我什么也做不到😥)
但是注意目前只有in-order流水线实现了**CustomBehaviour**类,out-order流水线将来也会支持。
该类主要通过`checkCustomHazard()`函数来实现,通过当前指令和真正流水线中执行的指令,来判断当前指令需要等待几个周期才能发射。
如果想对没有实现的目标添加**CustomBehaviour**类,可以参考已有的实现,比如在`/llvm/lib/Target/AMDGPU/MCA/`目录下。
## Custom Views 自定义视图
关于自定义的视图的添加路径,如果**没有输出**从未在MC layer classes (MCSubtargetInfo, MCInstrInfo, etc.)里出现过的**新后端值**,请把实现加入`/tools/llvm-mca/View/`。相反,请加入`/lib/Target/<TargetName>/MCA/`目录。
关于Custom Views所需内容,需要写特殊的**CustomBehaviour**类来覆写`CustomBehaviour::getViews()`函数,根据位置的不同还有三种实现`getStartViews(), getPostInstrInfoViews(),getEndViews()`。
## 影响准确性的因素
调度模型不仅用于计算指令延迟和吞吐量,还用于了解可用的处理器资源以及如何模拟它们。
llvm mca进行分析的质量不可避免地受到**llvm中调度模型质量**的影响。
## 功能(能估计的值
1. IPC
2. 硬件资源压力resource-pressure
3. 一些额外Info?
1.
register-file-stats
-dispatch-stats
-scheduler-stats
-retire-stats
-instruction-info
instruction-tables
1
2
3
4
5
6
7
4. 吞吐量瓶颈?
### 支持对特定代码块的分析
1. 汇编代码,支持命名和嵌套
add %eax, %eax
1 |
|
int foo(int a, int b) {
__asm volatile(“# LLVM-MCA-BEGIN foo”);
a += 42;
__asm volatile(“# LLVM-MCA-END”);
a *= b;
return a;
}
但是,这会干扰循环矢量化等优化,并可能对生成的代码产生影响。具体影响请对比汇编代码。
Google学术搜llvm-mca,一堆论文。但是不急着看,因为没有预备知识,没有问题的去看论文。效率和收获很低的,而且会看不懂。
mc-ruler是整合了llvm-mca的cmake,可以打印指定部分的代码分析信息。如果之后要测试可能用得上。
如何和大神交流呢+提问的艺术

-O3的优化,而且其顺序对结果也有影响。详细解释各阶段:
后端的实现分散在LLVM源代码树的不同目录中。代码生成背后的主要程序库位于lib目录和它的子文件夹CodeGen、MC、TableGen、和Target中, 具体参考文档
Tablegen位置在类似 llvm/lib/Target/X86/X86.td的地方
程序优化选项 -O3 是通过启用 LLVM Pass Manager 并按照顺序执行包含多个具体优化 Pass 的过程实现的。包括:
这些 Pass 的执行范围涵盖 LLVM IR 与 LLVM 后端。
TableGen的输入文件使用扩展名“.td”(TableGen的缩写),它们可以描述如下内容:
实现一个简单的LLVM IR后端,将LLVM IR转换为x86汇编代码,能line by line的输出。
参考LLVM官方文档中的“Writing an LLVM Backend”以及“TableGen Backends”
暂无
暂无
https://getting-started-with-llvm-core-libraries-zh-cn.readthedocs.io/zh_CN/latest/ch06.html#id2
Pass类是实现优化的主要资源。然而,我们从不直接使用它,而是通过清楚的子类使用它。当实现一个Pass时,你应该选择适合你的Pass的最佳粒度,适合此粒度的最佳子类,例如基于函数、模块、循环、强联通区域,等等。常见的这些子类如下:
ModulePass:这是最通用的Pass;它一次分析整个模块,函数的次序不确定。它不限定使用者的行为,允许删除函数和其它修改。为了使用它,你需要写一个类继承ModulePass,并重载runOnModule()方法。FunctionPass:这个子类允许一次处理一个函数,处理函数的次序不确定。这是应用最多的Pass类型。它禁止修改外部函数、删除函数、删除全局变量。为了使用它,需要写一个它的子类,重载runOnFunction()方法。BasicBlockPass:这个类的粒度是基本块。FunctionPass类禁止的修改在这里也是禁止的。它还禁止修改或者删除外部基本块。使用者需要写一个类继承BasicBlockPass,并重载它的runOnBasicBlock()方法。被重载的入口函数runOnModule()、runOnFunction()、runOnBasicBlock()返回布尔值false,如果被分析的单元(模块、函数和基本块)保持不变,否则返回布尔值true。
1 | char PIMProf::AnnotationInjection::ID = 0; |
最简单框架hello.cpp如下,注意Important一定需要:
1 |
|
参考官方文档。
An example of a project layout is provided below.
1 | <project dir>/ |
Contents of <project dir>/CMakeLists.txt:
1 | find_package(LLVM REQUIRED CONFIG) |
Contents of <project dir>/<pass name>/CMakeLists.txt:
1 | add_library(LLVMPassname MODULE Pass.cpp) |
运行cmake编译。产生LLVMPassname.so文件
1 | mkdir build && cd build |
请阅读知乎的文章
1 | clang -c -emit-llvm main.c -o main.bc # 随意写一个C代码并编译到bc格式 |
把源代码编译成IR代码,然后用opt运行Pass实在麻烦且无趣。
1 | clang -Xclang -load -Xclang path/to/LLVMHello.so main.c -o main |
1 | void InjectSimMagic2(Module &M, Instruction *insertPt, uint64_t arg0, uint64_t arg1, uint64_t arg2) |
这段代码使用内联汇编嵌入到 LLVM IR 中,指令如下:
1 | mov $0, %rax |
其中:
由于直接打印的是llvm IR的表示,想要打印特定架构比如x86的汇编代码,其实需要进行llvm后端的转换。(取巧,可执行文件反汇编,然后根据插入的汇编桩划分)
1 |
暂无
暂无
复现PIMProf论文时,用到了使用 llvm pass来插入特殊汇编