0, gcc 内联汇编的格式
0.1 GCC 内联汇编的格式:
asm (assemble template
: output operands (optional)
: input operands (optional)
: list of clobbered registers (optional) );
例如:
读取
__asm__ __volatile__("rdtsc" : "=a"(low),"=d"(high))
x86 的RDTSC指令,是指 Real Time Stamp Counter 读取时间计数器的指令。
这个指令读取cpu时间计数器的值,返回一个unsigned 64bit 整数;
通过edx eax 寄存器返回 cpu 被引导后,时钟所走的圈数。这个指令可以用于测量两次调用之间的时间间隔;这里的代码将时间计数器的64bit值存储进了 edx:eax的组合中。
0.2 AT&T asm 的特点
1, 寄存器前边 加% %eax
2, 源操作数与目的操作数的顺序: movl src dest
3,
movb 1byte
movw 2bytes 一个字
movl 4bytes 两个字
4, 立即数前边加$
movl $0xff77, %eax
5,内存间接引用加括号()
movb (%esi), %al
0.3 gcc 内联汇编的语法变形
寄存器前边加两个 %
%%eax
未完再补。。。
1,先上代码
__volatile__ 告诉编译器,不要对内部的asm代码做优化,原封不动地搬进程序中即可。
#include <iostream>
#include <omp.h>
int atomicAdd(int* ptr, int value) {
int result;
__asm__ __volatile__(
"lock xaddl %0, %1\n"
: "=r" (result), "=m" (*ptr)
: "0" (value), "m" (*ptr)
: "memory"
);
return result;
}
int main() {
int sharedValue = 0;
// 原子加法操作
// int increment = 10;
#pragma omp parallel
{
for(int i=0; i<900000; i++)
atomicAdd(&sharedValue, 10);
//atomicAdd(&sharedValue, increment);
}
std::cout << "New value: " << sharedValue << std::endl;
return 0;
}
Makefile:
EXE := hello_atomic
all: $(EXE)
%: %.cpp
g++ -fopenmp $< -o $@
.PHONY: clean
clean:
-rm -rf $(EXE)
2,实验运行效果
18核36线程,每个线程做1W次加法,没有加丢:
3,原理
在 x86 架构的 CPU 中,原子操作通常是通过特殊的 CPU 指令来实现的,这些指令能够确保在多线程并发执行时,线程或进程使用这类指令对共享内存的操作是原子的,即不会被中断或打断。以下是一些常见的 x86 CPU 中用于实现原子操作的指令:
1. XCHG 指令:XCHG 指令用于交换内存中的值和寄存器中的值。在多线程编程中,可以使用 XCHG 指令来实现原子的读-修改-写操作。
2. CMPXCHG 指令:CMPXCHG 指令用于比较首操作数与 XAX的值,如果相等则将第二操作数的值复制给首操作数。这个指令通常用于实现原子的比较并交换操作(Compare and Swap)。
3. LOCK 前缀:LOCK 前缀可以与其他指令一起使用,用于将指令执行过程中的内存访问变为原子操作。例如,LOCK XCHG 或 LOCK CMPXCHG。
4. 内存屏障指令:MFENCE、SFENCE 和 LFENCE 指令用于确保内存操作的顺序性和可见性,以避免乱序执行带来的问题。
这些指令和前缀可以确保在多线程并发执行时,对共享内存的操作是原子的,从而避免竞态条件和数据不一致性问题。在编写并发程序时,可以利用这些原子操作来实现线程安全的共享内存访问。