use*_*831 3 c memory assembly memory-management locking
我是低级别的新手,所以我完全忘记了你可能面临的问题,我甚至不确定我是否理解"原子"一词.现在我试图通过扩展程序集围绕内存操作进行简单的原子锁.为什么?为了好奇.我知道我在这里重新发明轮子,可能会过度简化整个过程.
这个问题? 我在这里提供的代码是否实现了使内存操作既线程安全又可重入的目标?
我只想做...
代码:
volatile int atomic_gate_memory = 0;
static inline void atomic_open(volatile int *gate)
{
asm volatile (
"wait:\n"
"cmp %[lock], %[gate]\n"
"je wait\n"
"mov %[lock], %[gate]\n"
: [gate] "=m" (*gate)
: [lock] "r" (1)
);
}
static inline void atomic_close(volatile int *gate)
{
asm volatile (
"mov %[lock], %[gate]\n"
: [gate] "=m" (*gate)
: [lock] "r" (0)
);
}
Run Code Online (Sandbox Code Playgroud)
然后像:
void *_malloc(size_t size)
{
atomic_open(&atomic_gate_memory);
void *mem = malloc(size);
atomic_close(&atomic_gate_memory);
return mem;
}
#define malloc(size) _malloc(size)
Run Code Online (Sandbox Code Playgroud)
..同样适用于calloc,realloc,free和fork(适用于linux).
#ifdef _UNISTD_H
int _fork()
{
pid_t pid;
atomic_open(&atomic_gate_memory);
pid = fork();
atomic_close(&atomic_gate_memory);
return pid;
}
#define fork() _fork()
#endif
Run Code Online (Sandbox Code Playgroud)
为atomic_open加载堆栈帧后,objdump生成:
00000000004009a7 <wait>:
4009a7: 39 10 cmp %edx,(%rax)
4009a9: 74 fc je 4009a7 <wait>
4009ab: 89 10 mov %edx,(%rax)
Run Code Online (Sandbox Code Playgroud)
另外,鉴于上面的拆卸; 我可以假设我正在进行原子操作,因为它只是一条指令吗?
还不够好?我应该在C中使用register关键字吗?
register 在现代优化编译器中是一个毫无意义的暗示.
我认为一个简单的自旋锁在x86上没有任何真正重大/明显的性能问题就是这样的.当然,真正的实现会futex在旋转一段时间后使用系统调用(如Linux ),解锁时必须检查是否需要通过另一个系统调用通知任何服务员.这个很重要; 你不想永远浪费CPU时间(和能量/热量)无所事事.但从概念上讲,在你采用后备路径之前,这是自旋锁的旋转部分. 这是实现轻量级锁定的重要部分.(在调用内核之前只尝试锁定一次将是一个有效的选择,而不是完全旋转.)
在内联asm中实现尽可能多的内容,或者最好使用C11 stdatomic,就像这个信号量实现一样.
;;; UNTESTED ;;;;;;;;
;;; TODO: **IMPORTANT** fall back to OS-supported sleep/wakeup after spinning some
; first arg in rdi, in the AMD64 SysV ABI
;;;;;void spin_lock (volatile char *lock)
global spin_unlock
spin_unlock:
;; debug: check that the old value was non-zero. double-unlocking is a nasty bug
mov byte [rdi], 0
ret
;; The store has release semantics, but not sequential-consistency (which you'd get from an xchg or something),
;; because acquire/release is enough to protect a critical section (hence the name)
;;;;;void spin_unlock(volatile char *lock)
global spin_lock
spin_lock:
cmp byte [rdi], 0 ; avoid writing to the cache line if we don't own the lock: should speed up the other thread unlocking
jnz .spinloop
mov al, 1 ; only need to do this the first time, otherwise we know al is non-zero
.retry:
xchg al, [rdi]
test al,al ; check if we actually got the lock
jnz .spinloop
ret ; no taken branches on the fast-path
.spinloop:
pause ; very old CPUs decode it as REP NOP, which is fine
cmp byte [rdi], 0 ; To get a compiler to do this in C++11, use a memory_order_acquire load
jnz .spinloop
jmp .retry
Run Code Online (Sandbox Code Playgroud)
如果您使用原子标记的位域,则可以使用lock bts(测试和设置)相当于xchg-with-1.你可以旋转bt或test.要解锁,你不仅需要lock btr,btr因为它将是字节的非原子读 - 修改 - 写,甚至包含32位.
使用字节或字大小的锁定,您甚至不需要lock编辑操作来解锁; 发布语义就足够了.glibc pthread_spin_unlock和我的解锁功能一样:一个简单的商店.
如果我们看到它已被锁定,这可以避免写入锁定.这避免了无效运行中拥有它的线程核心的L1高速缓存行,因此它可以回去"修改时间"(MESIF或MOESI解锁期间)以较少的高速缓存一致性的延迟.
我们也不会lock在循环中使用ed操作充斥CPU .我不确定这会减慢多少速度,但是所有等待相同螺旋锁的10个线程都会使内存仲裁硬件保持相当繁忙.这可能会减慢持有锁的线程或系统上其他无关线程的速度,同时它们会使用其他锁或内存.
PAUSE也是必不可少的,以避免CPU对内存排序的错误推测.你退出只有当你正在阅读的记忆回路是由另一个核心修改.但是,我们不希望pause在无争议的情况下.在Skylake,PAUSE等待更长时间,比如~100cycles IIRC,所以你一定要将spinloop与初始检查分开进行解锁.
我确信英特尔和AMD的优化手册都在讨论这个问题,请参阅x86标签wiki以及大量其他链接.
| 归档时间: |
|
| 查看次数: |
1214 次 |
| 最近记录: |