原子变量的性能问题

#include <stdio.h>
#include <sys/time.h>

int main()
{
    volatile int m;

    struct timeval start;
    gettimeofday(&start, NULL);
    for (int i = 0; i < 1000000; i++) {
        m++;
    }
    struct timeval end;
    gettimeofday(&end, NULL);

    printf("add cost %lldus\n", (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec));

    int n;
    gettimeofday(&start, NULL);
    for (int i = 0; i < 1000000; i++) {
        __sync_fetch_and_add(&n, 1);
    }
    gettimeofday(&end, NULL);
    printf("atomic cost %lldus\n", (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec));

    return 0;
}

之所以用volatile修饰m是拒绝编译器对m++做优化。

使用O2编译并查看性能:

$gcc -O2 -std=c99 -o perf atomic_perf.c
$./perf
add cost 2638us
atomic cost 8510us

  可见如果你的变量压根不会被多线程访问,并且对性能极度苛刻的话,还是不要用原子变量了吧。因为在有些平台上“A full memory barrier is created when this function is invoked”。

可以通过下面的方法看到m++和原子操作的汇编之间的区别:

$gcc -O2 -std=c99 -g -c atomic_perf.c
$objdump -Sl atomic_perf.o

atomic_perf.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:
main():
/home/admin/jinxin/test/atomic_perf.c:5
#include <stdio.h>
#include <sys/time.h>

int main()
{
   0: 55                    push   %rbp
/home/admin/jinxin/test/atomic_perf.c:9
    volatile int m;

    struct timeval start;
    gettimeofday(&start, NULL);
   1: 31 f6                 xor    %esi,%esi
/home/admin/jinxin/test/atomic_perf.c:5
   3: 53                    push   %rbx
   4: 48 83 ec 38           sub    $0x38,%rsp
/home/admin/jinxin/test/atomic_perf.c:9
   8: 48 8d 6c 24 10        lea    0x10(%rsp),%rbp
   d: 48 89 ef              mov    %rbp,%rdi
  10: e8 00 00 00 00        callq  15 <main+0x15>
  15: 31 d2                 xor    %edx,%edx
/home/admin/jinxin/test/atomic_perf.c:11
    for (int i = 0; i < 1000000; i++) {
        m++;
  17: 8b 44 24 2c           mov    0x2c(%rsp),%eax
/home/admin/jinxin/test/atomic_perf.c:10
  1b: 83 c2 01              add    $0x1,%edx
/home/admin/jinxin/test/atomic_perf.c:11
  1e: 83 c0 01              add    $0x1,%eax
/home/admin/jinxin/test/atomic_perf.c:10
  21: 81 fa 40 42 0f 00     cmp    $0xf4240,%edx
/home/admin/jinxin/test/atomic_perf.c:11
  27: 89 44 24 2c           mov    %eax,0x2c(%rsp)
/home/admin/jinxin/test/atomic_perf.c:10
  2b: 75 ea                 jne    17 <main+0x17>
/home/admin/jinxin/test/atomic_perf.c:14
    }
    struct timeval end;
    gettimeofday(&end, NULL);
  2d: 31 f6                 xor    %esi,%esi
  2f: 48 89 e7              mov    %rsp,%rdi
  32: e8 00 00 00 00        callq  37 <main+0x37>
/home/admin/jinxin/test/atomic_perf.c:16

    printf("add cost %lldus\n", (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec));
  37: 48 8b 04 24           mov    (%rsp),%rax
  3b: 48 2b 44 24 10        sub    0x10(%rsp),%rax
  40: bf 00 00 00 00        mov    $0x0,%edi
  45: 48 8b 74 24 08        mov    0x8(%rsp),%rsi
  4a: 48 2b 74 24 18        sub    0x18(%rsp),%rsi
  4f: 48 69 c0 40 42 0f 00  imul   $0xf4240,%rax,%rax
  56: 48 01 c6              add    %rax,%rsi
  59: 31 c0                 xor    %eax,%eax
  5b: e8 00 00 00 00        callq  60 <main+0x60>
/home/admin/jinxin/test/atomic_perf.c:19

    int n;
    gettimeofday(&start, NULL);
  60: 31 f6                 xor    %esi,%esi
  62: 48 89 ef              mov    %rbp,%rdi
  65: e8 00 00 00 00        callq  6a <main+0x6a>
  6a: 48 8d 54 24 28        lea    0x28(%rsp),%rdx
  6f: 31 c0                 xor    %eax,%eax
/home/admin/jinxin/test/atomic_perf.c:21
    for (int i = 0; i < 1000000; i++) {
        __sync_fetch_and_add(&n, 1);
  71: f0 83 02 01           lock addl $0x1,(%rdx)
/home/admin/jinxin/test/atomic_perf.c:20
  75: 83 c0 01              add    $0x1,%eax
  78: 3d 40 42 0f 00        cmp    $0xf4240,%eax
  7d: 75 f2                 jne    71 <main+0x71>
/home/admin/jinxin/test/atomic_perf.c:23
    }
    gettimeofday(&end, NULL);
  7f: 48 89 e7              mov    %rsp,%rdi
  82: 31 f6                 xor    %esi,%esi
  84: e8 00 00 00 00        callq  89 <main+0x89>
/home/admin/jinxin/test/atomic_perf.c:24
    printf("atomic cost %lldus\n", (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec));
  89: 48 8b 04 24           mov    (%rsp),%rax
  8d: 48 2b 44 24 10        sub    0x10(%rsp),%rax
  92: bf 00 00 00 00        mov    $0x0,%edi
  97: 48 8b 74 24 08        mov    0x8(%rsp),%rsi
  9c: 48 2b 74 24 18        sub    0x18(%rsp),%rsi
  a1: 48 69 c0 40 42 0f 00  imul   $0xf4240,%rax,%rax
  a8: 48 01 c6              add    %rax,%rsi
  ab: 31 c0                 xor    %eax,%eax
  ad: e8 00 00 00 00        callq  b2 <main+0xb2>
/home/admin/jinxin/test/atomic_perf.c:27

    return 0;
}
  b2: 48 83 c4 38           add    $0x38,%rsp
  b6: 31 c0                 xor    %eax,%eax
  b8: 5b                    pop    %rbx
  b9: 5d                    pop    %rbp
  ba: c3                    retq???

  

时间: 2024-10-10 07:44:41

原子变量的性能问题的相关文章

Java并发编程实战 第15章 原子变量和非阻塞同步机制

非阻塞的同步机制 简单的说,那就是又要实现同步,又不使用锁. 与基于锁的方案相比,非阻塞算法的实现要麻烦的多,但是它的可伸缩性和活跃性上拥有巨大的优势. 实现非阻塞算法的常见方法就是使用volatile语义和原子变量. 硬件对并发的支持 原子变量的产生主要是处理器的支持,最重要的是大多数处理器架构都支持的CAS(比较并交换)指令. 模拟实现AtomicInteger的++操作 首先我们模拟处理器的CAS语法,之所以说模拟,是因为CAS在处理器中是原子操作直接支持的.不需要加锁. public s

多线程并发编程之原子变量与非阻塞同步机制

1.非阻塞算法 非阻塞算法属于并发算法,它们可以安全地派生它们的线程,不通过锁定派生,而是通过低级的原子性的硬件原生形式 —— 例如比较和交换.非阻塞算法的设计与实现极为困难,但是它们能够提供更好的吞吐率,对生存问题(例如死锁和优先级反转)也能提供更好的防御.使用底层的原子化机器指令取代锁,比如比较并交换(CAS,compare-and-swap). 2.悲观技术 独占锁是一种悲观的技术.它假设最坏的情况发生(如果不加锁,其它线程会破坏对象状态),即使没有发生最坏的情况,仍然用锁保护对象状态.

J.U.C中的原子变量

在开发过程中,很多时候都需要用到原子的递增递减操作:而我们知道,常用的i ++ 和 i -- 等操作都不是原子的,它包含了三步操作(读-改-写):首先,读取变量i的值,其次将i执行 +1 或者 -1 操作,最后,将计算后的结果赋值给i:通常情况下,只有加锁才能保证 i ++ 和 i -- 等操作的原子性.值得关注的是,在JDK1.5及之后的所有版本,java.concurrent.util.atomic包下给我们提供了原子变量,原子变量支持原子的递增和递减操作.这里,针对原子变量的底层实现原理及

《Java并发编程实战》第十五章 原子变量与非阻塞同步机制 读书笔记

一.锁的劣势 锁定后如果未释放,再次请求锁时会造成阻塞,多线程调度通常遇到阻塞会进行上下文切换,造成更多的开销. 在挂起与恢复线程等过程中存在着很大的开销,并且通常存在着较长时间的中断. 锁可能导致优先级反转,即使较高优先级的线程可以抢先执行,但仍然需要等待锁被释放,从而导致它的优先级会降至低优先级线程的级别. 二.硬件对并发的支持 处理器填写了一些特殊指令,例如:比较并交换.关联加载/条件存储. 1 比较并交换 CAS的含义是:"我认为V的值应该为A,如果是,那么将V的值更新为B,否则不需要修

Java并发——原子变量和原子操作与阻塞算法

十五年前,多处理器系统是高度专用系统,要花费数十万美元(大多数具有两个到四个处理器).现在,多处理器系统很便宜,而且数量很多,几乎每个主要微处理器都内置了多处理支持,其中许多系统支持数十个或数百个处理器. 要使用多处理器系统的功能,通常需要使用多线程构造应用程序.但是正如任何编写并发应用程序的人可以告诉你的那样,要获得好的硬件利用率,只是简单地在多个线程中分割工作是不够的,还必须确保线程确实大部分时间都在工作,而不是在等待更多的工作,或等待锁定共享数据结构. 问题:线程之间的协调 如果线程之间 

java并发编程(8)原子变量和非阻塞的同步机制

原子变量和非阻塞的同步机制 一.锁的劣势 1.在多线程下:锁的挂起和恢复等过程存在着很大的开销(及时现代的jvm会判断何时使用挂起,何时自旋等待) 2.volatile:轻量级别的同步机制,但是不能用于构建原子复合操作 因此:需要有一种方式,在管理线程之间的竞争时有一种粒度更细的方式,类似与volatile的机制,同时还要支持原子更新操作 二.CAS 独占锁是一种悲观的技术--它假设最坏的情况,所以每个线程是独占的 而CAS比较并交换:compareAndSwap/Set(A,B):我们认为内存

再谈Java原子变量以及同步的效率 -- 颠覆你的人生观

思维定视让我们觉得原子变量总是快过同步操作的,笔者也是一直这么认为,直到一次实现一个ID生成器的过程中的一次测试偶然发现了并非都这么回事. 测试代码: import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class ConcurrentAdder { private static final AtomicInteger ATOMIC_

Java并发之原子变量与volatile

我们知道在并发编程中,多个线程共享某个变量或者对象时,必须要进行同步.同步的包含两层作用:1)互斥访问:2)可见性:也就是多个线程对共享的变量互斥地访问,同时线程对共享变量的修改必须对其他线程可见,也就是所有线程访问到的都是最新的值. 1. volatile变量和引用 volatile的作用是:保证可见性,但是没有互斥访问语义.volatile能够保证它修饰的引用以及引用的对象的可见性,volatile不仅保证变量或者引用对所有访问它的线程的可见性,同时能够保证它所引用的对象对所有访问它的线程的

盲猜原子变量、内存屏障、内存模型、锁之间的关系

1.atomic_flag 和atomic<>的区别,atomic_flag 无论无锁是多大代价(一些cpu可能无锁代价大),都保证atomic_flag 是无锁的.atomic<>会视情况,可能是有锁的也可能是无锁的,哪个开销小选哪个. 2.C++内存模型可以被看作是C++程序和计算机系统(包括编译器,多核CPU等可能对程序进行乱序优化的软硬件)之间的契约,它规定了多个线程访问同一个内存地址时的语义,以及某个线程对内存地址的更新何时能被其它线程看见.C++11 中的 atomic