🚙

💨 💨 💨

×

  • Categories

  • Archives

  • Tags

  • About

GCC的原子操作函数

Posted on 04-11-2015 | In Linux

linux支持的哪些操作是具有原子特性的?知道这些东西是理解和设计无锁化编程算法的基础。

下面的东西整理自网络。先感谢大家的分享!

原子操作的api函数

gcc从4.1.2以后提供了 __sync_* 系列的下面几类的内嵌函数,提供用于针对数字或布尔型变量的原子操作。

n++类

这组返回更新前的值

type __sync_fetch_and_add (type *ptr, type value, ...)
type __sync_fetch_and_sub (type *ptr, type value, ...)
type __sync_fetch_and_or (type *ptr, type value, ...)
type __sync_fetch_and_and (type *ptr, type value, ...)
type __sync_fetch_and_xor (type *ptr, type value, ...)
type __sync_fetch_and_nand (type *ptr, type value, ...)

++n类

这组返回更新后的值

type __sync_add_and_fetch (type *ptr, type value, ...)
type __sync_sub_and_fetch (type *ptr, type value, ...)
type __sync_or_and_fetch (type *ptr, type value, ...)
type __sync_and_and_fetch (type *ptr, type value, ...)
type __sync_xor_and_fetch (type *ptr, type value, ...)
type __sync_nand_and_fetch (type *ptr, type value, ...)

type可以是1,2,4或8字节长度的int类型,即:

int8_t / uint8_t
int16_t / uint16_t
int32_t / uint32_t
int64_t / uint64_t

后面的可扩展参数(…)用来指出哪些变量需要memory barrier,因为目前gcc实现的是full barrier(类似于linux kernel 中的mb(),表示这个操作之前的所有内存操作不会被重排序到这个操作之后),所以可以略掉这个参数。

CAS类

CAS 即 compare-and-swap ,

下面这两个函数提供原子的比较和交换,如果 *ptr == oldval,就将 newval 写入 *ptr

  • 此函数在相等并写入的情况下返回 true

    bool __sync_bool_compare_and_swap (type *ptr, type oldval, type newval, ...)
    /* 对应的伪代码 */
    { if (*ptr == oldval) { *ptr = newval; return true; } else { return false; } }
    
  • 此函数在返回 oldval

    type __sync_val_compare_and_swap (type *ptr, type oldval, type newval, ...)
    /* 对应的伪代码 */
    { if (*ptr == oldval) { *ptr = newval; } return oldval; }
    

其他原子操作

type __sync_lock_test_and_set (type *ptr, type value, ...)

将 *ptr 设为value并返回 *ptr 操作之前的值。

void __sync_lock_release (type *ptr, ...)

将 *ptr 置 0

内存栅障

内存栅障主要是处理不同cpu运作时(主要是执行不同线程代码时),一个cpu对内存的操作的原子性,也就保证其他cpu见到的内存单元数据的正确性。

内存栅障介绍

如果有对某一变量上写锁, 就不能在不获得相应的锁时对其进行读取操作。
内存栅的作用在于保证内存操作的相对顺序, 但并不保证内存操作的严格时序。

内存栅并不保证 CPU 将本地快取缓存或存储缓冲的内容刷写回内存, 而是在锁释放时确保其所保护的数据, 对于能看到刚释放的那个锁的 CPU 或设备可见。
持有内存栅的 CPU 可以在其快取缓存或存储缓冲中将数据保持其所希望的、 任意长的时间, 但如果其它 CPU 在同一数据元上执行原子操作,
则第一个 CPU 必须保证, 其所更新的数据值, 以及内存栅所要求的任何其它操作, 对第二个 CPU 可见。

__sync_synchronize (...)

发出一个full barrier.

内存栅障应用

对于执行一条指令,操作到4个寄存器时,如:

write1(dev.register_size,size);
write1(dev.register_addr,addr);
write1(dev.register_cmd,READ);
write1(dev.register_control,GO);

最后一个寄存器是控制寄存器,在所有的参数都设置好之后向其发出指令,设备开始读取参数.

如果最后一条write1被换到了前几条语句之前,那么肯定不是我们所期望的,这时候我们可以在最后一条语句之前加入一个memory barrier,强制cpu执行完前面的写入以后再执行最后一条:

write1(dev.register_size,size);
write1(dev.register_addr,addr);
write1(dev.register_cmd,READ);
__sync_synchronize();
write1(dev.register_control,GO);

memory barrier有几种类型:

  • acquire barrier : 不允许将barrier之后的内存读取指令移到barrier之前(linux kernel中的wmb())。
  • release barrier : 不允许将barrier之前的内存读取指令移到barrier之后 (linux kernel中的rmb())。
  • full barrier : 以上两种barrier的合集(linux kernel中的mb())。

原子操作应用范围

原子操作只允许一次更新或读一个内存单元。 需要原子地更新多个单元时, 就必须使用锁来代替它了。

例如, 如果需要更新两个相互关联的计数器时, 就必须使用锁, 而不是两次单独的原子操作了。

原子操作例子

例子代码:

#include <stdio.h>  
#include <pthread.h>
#include <stdlib.h>

static int count = 0;

void *test_func(void *arg)
{
int i=0;
for(i=0;i<20000;++i){
__sync_fetch_and_add(&count,1);
}
return NULL;
}

int main(int argc, const char *argv[])
{
pthread_t id[20];
int i = 0;

for(i=0;i<20;++i){
pthread_create(&id[i],NULL,test_func,NULL);
}

for(i=0;i<20;++i){
pthread_join(id[i],NULL);
}
printf("%d\n",count);
return 0;
}

原子操作封装使用

根据常用的原子操作,封装一些实用的接口 :

//原子操作  
//设置值
int lock_set(volatile int &a, int value)
{
__sync_val_compare_and_swap(&a, a, value);
return a;
}
//加1
int lock_inc(volatile int &n)
{
return __sync_fetch_and_add(&n, 1);
}
//减1
int lock_dec(volatile int &n)
{
return __sync_fetch_and_sub(&n, 1);
}

//加值value
int lock_add(volatile int &n, int value)
{
return __sync_fetch_and_add(&n, value);
}

//减值value
int lock_sub(volatile int &n, int value)
{
return __sync_fetch_and_sub(&n, value);
}

//位或value
int lock_or(volatile int &n, int value)
{
return __sync_fetch_and_or(&n, value);
}

//位与value
int lock_and(volatile int &n, int value)
{
return __sync_fetch_and_and(&n, value);
}

//异或value
int lock_xor(volatile int &n, int value)
{
return __sync_fetch_and_xor(&n, value);
}

//无符号类型(函数重载)
//设置值
unsigned int lock_set(volatile unsigned int &a, unsigned int value)
{
__sync_val_compare_and_swap(&a, a, value);
return a;
}

//加1
unsigned int lock_inc(volatile unsigned int &n)
{
return __sync_fetch_and_add(&n, 1);
}

//减1
unsigned int lock_dec(volatile unsigned int &n)
{
return __sync_fetch_and_sub(&n, 1);
}

//加值value
unsigned int lock_add(volatile unsigned int &n, unsigned int value)
{
return __sync_fetch_and_add((int*)&n, value);
}

//减值value
unsigned int lock_sub(volatile unsigned int &n, unsigned int value)
{
return __sync_fetch_and_sub((int*)&n, value);
}

//位或value
unsigned int lock_or(volatile unsigned int &n, unsigned int value)
{
return __sync_fetch_and_or((int*)&n, value);
}

//位与value
unsigned int lock_and(volatile unsigned int &n, unsigned int value)
{
return __sync_fetch_and_and((int*)&n, value);
}

//异或value
unsigned int lock_xor(volatile unsigned int &n, unsigned int value)
{
return __sync_fetch_and_xor((int*)&n, value);
}

GCC的分支预测优化__builtin_expect

Posted on 04-11-2015 | In Linux

1. 为什么需要分支预测优化

将流水线引入cpu,可以提高cpu的效率。更简单的说,让cpu可以预先取出下一条指令,可以提供cpu的效率。如下图所示:

取指令 执行指令 输出结果
取指令 执行

可见,cpu流水钱可以减少cpu等待取指令的耗时,从而提高cpu的效率。
如果存在跳转指令,那么预先取出的指令就无用了。cpu在执行当前指令时,从内存中取出了当前指令的下一条指令。执行完当前指令后,cpu发现不是要执行下一条指令,而是执行offset偏移处的指令。cpu只能重新从内存中取出offset偏移处的指令。因此,跳转指令会降低流水线的效率,也就是降低cpu的效率。

综上,在写程序时应该尽量避免跳转语句。那么如何避免跳转语句呢?答案就是使用__builtin_expect。

这个指令是gcc引入的,作用是”允许程序员将最有可能执行的分支告诉编译器”。

这个指令的写法为:builtin_expect(EXP, N)。意思是:EXP==N的概率很大。一般的使用方法是将builtin_expect指令封装为LIKELY和UNLIKELY宏。这两个宏的写法如下。

#define LIKELY(x) __builtin_expect(!!(x), 1) //x很可能为真
#define UNLIKELY(x) __builtin_expect(!!(x), 0) //x很可能为假

在很多源码如Linux内核、Glib等,我们都能看到likely()和unlikely()这两个宏,通常这两个宏定义是下面这样的形式。
可以看出这2个宏都是使用函数 builtin_expect()实现的, builtin_expect()函数是GCC的一个内建函数(build-in function).

2. 函数声明

函数__builtin_expect()是GCC v2.96版本引入的, 其声明如下:

long __builtin_expect(long exp, long c);

2.1. 功能描述

由于大部分程序员在分支预测方面做得很糟糕,所以GCC 提供了这个内建函数来帮助程序员处理分支预测.

你期望 exp 表达式的值等于常量 c, 看 c 的值, 如果 c 的值为0(即期望的函数返回值), 那么 执行 if 分支的的可能性小, 否则执行 else 分支的可能性小(函数的返回值等于第一个参数 exp).

GCC在编译过程中,会将可能性更大的代码紧跟着前面的代码,从而减少指令跳转带来的性能上的下降, 达到优化程序的目的.

通常,你也许会更喜欢使用 gcc 的一个参数 ‘-fprofile-arcs’ 来收集程序运行的关于执行流程和分支走向的实际反馈信息,但是对于很多程序来说,数据是很难收集的。

2.2. 参数详解

  • exp
    exp 为一个整型表达式, 例如: (ptr != NULL)

  • c
    c 必须是一个编译期常量, 不能使用变量

2.3. 返回值

  返回值等于 第一个参数 exp

2.4. 使用方法

与关键字if一起使用.首先要明确一点就是 if (value) 等价于 if (__builtin_expert(value, x)), 与x的值无关.

例子如下:

例子1 : 期望 x == 0, 所以执行func()的可能性小

if (__builtin_expect(x, 0))
{
func();
}
else
{
  //do someting
}

例子2 : 期望 ptr !=NULL这个条件成立(1), 所以执行func()的可能性小

if (__builtin_expect(ptr != NULL, 1))
{  
  //do something
}
else
{
  func();
}

例子3 : 引言中的likely()和unlikely()宏

首先,看第一个参数!!(x), 他的作用是把(x)转变成”布尔值”, 无论(x)的值是多少 !(x)得到的是true或false, !!(x)就得到了原值的”布尔值”

使用 likely() ,执行 if 后面的语句 的机会更大,使用 unlikely(),执行 else 后面的语句的机会更大。

#define likely(x)    __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

int main(char *argv[], int argc)
{
int a;

/* Get the value from somewhere GCC can't optimize */
a = atoi (argv[1]);

if (unlikely (a == 2))
  {
a++;
}
else
  {
   a--;
  }
printf ("%d\n", a);

return 0;
}

3. RATIONALE(原理)

if else 句型编译后, 一个分支的汇编代码紧随前面的代码,而另一个分支的汇编代码需要使用JMP指令才能访问到.

很明显通过JMP访问需要更多的时间, 在复杂的程序中,有很多的if else句型,又或者是一个有if else句型的库函数,每秒钟被调用几万次,

通常程序员在分支预测方面做得很糟糕, 编译器又不能精准的预测每一个分支,这时JMP产生的时间浪费就会很大,

函数 __builtin_expert() 就是用来解决这个问题的.

linux一些不要想当然的事(一)之目录权限

Posted on 03-18-2015 | In Linux

目录的可读/可写/可执行权限

不要把目录的这几个权限和档案的这几个权限混淆了, 不要想当然的以为是差不多的, 差很多!
记忆技巧 : 档案的rwx是针对于档案的内容来设计的, 而目录的rwx是针对于目录的文件名列表来设计的

. . .

Linux常用运维命令(df和free和top)笔记整理(三)

Posted on 03-11-2015 | In Linux

df

df命令用于显示磁盘分区上的可使用的磁盘空间。默认显示单位为KB。可以利用该命令来获取硬盘被占用了多少空间,目前还剩下多少空间等信息。

  • -a或–all:包含全部的文件系统;
  • –block-size=<区块大小>:以指定的区块大小来显示区块数目;
  • -h或–human-readable:以可读性较高的方式来显示信息;
  • -H或–si:与-h参数相同,但在计算时是以1000 Bytes为换算单位而非1024 Bytes;
  • -i或–inodes:显示inode的信息;
  • -k或–kilobytes:指定区块大小为1024字节;
  • -l或–local:仅显示本地端的文件系统;
  • -m或–megabytes:指定区块大小为1048576字节;
  • –no-sync:在取得磁盘使用信息前,不要执行sync指令,此为预设值;
  • -P或–portability:使用POSIX的输出格式;
  • –sync:在取得磁盘使用信息前,先执行sync指令;
  • -t<文件系统类型>或–type=<文件系统类型>:仅显示指定文件系统类型的磁盘信息;
  • -T或–print-type:显示文件系统的类型;
  • -x<文件系统类型>或–exclude-type=<文件系统类型>:不要显示指定文件系统类型的磁盘信息;
  • –help:显示帮助;
  • –version:显示版本信息

. . .

Linux常用运维命令(netstat和lsof)笔记整理(二)

Posted on 03-09-2015 | In Linux

netstat

netstat命令用来打印Linux中网络系统的状态信息,可让你得知整个Linux系统的网络情况。

  • -a或–all:显示所有连线中的Socket;
  • -A<网络类型>或–<网络类型>:列出该网络类型连线中的相关地址;
  • -c或–continuous:持续列出网络状态;
  • -C或–cache:显示路由器配置的快取信息;
  • -e或–extend:显示网络其他相关信息;
  • -F或–fib:显示FIB;
  • -g或–groups:显示多重广播功能群组组员名单;
  • -h或–help:在线帮助;
  • -i或–interfaces:显示网络界面信息表单;
  • -l或–listening:显示监控中的服务器的Socket;
  • -M或–masquerade:显示伪装的网络连线;
  • -n或–numeric:直接使用ip地址,而不通过域名服务器;
  • -N或–netlink或–symbolic:显示网络硬件外围设备的符号连接名称;
  • -o或–timers:显示计时器;
  • -p或–programs:显示正在使用Socket的程序识别码和程序名称;
  • -r或–route:显示Routing Table;
  • -s或–statistice:显示网络工作信息统计表;
  • -t或–tcp:显示TCP传输协议的连线状况;
  • -u或–udp:显示UDP传输协议的连线状况;
  • -v或–verbose:显示指令执行过程;
  • -V或–version:显示版本信息;
  • -w或–raw:显示RAW传输协议的连线状况;
  • -x或–unix:此参数的效果和指定”-A unix”参数相同;
  • –ip或–inet:此参数的效果和指定”-A inet”参数相同。

. . .

Linux常用运维命令(iostat)笔记整理(一)

Posted on 03-07-2015 | In Linux

在linux服务器开发过程中, 经常需要各种命令配合来查看各种状态,所以整理了一些老的笔记来备忘。

iostat

iostat主要用于监控系统设备的IO负载情况,iostat首次运行时显示自系统启动开始的各项统计信息,之后运行iostat将显示自上次运行该命令以后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息

  • -c 仅显示CPU统计信息.与-d选项互斥.
  • -d 仅显示磁盘统计信息.与-c选项互斥.
  • -k 以K为单位显示每秒的磁盘请求数,默认单位块.
  • -t 在输出数据时,打印搜集数据的时间.
  • -V 打印版本号和帮助信息.
  • -x 输出扩展信息.

. . .

python的struct模块

Posted on 03-02-2015 | In Misc

struct, 这玩意c/c++也有, 顾名思义, 能联想到这玩意是啥了

模块的主要作用就是对python基本类型值与

用python字符串格式表示的C struct类型间

的转化(This module performs conversions between Python values and C structs represented as Python strings.)

基本用法

import struct
import binascii
values = (1, 'abc', 2.7)
s = struct.Struct('I3sf')
packed_data = s.pack(*values)
unpacked_data = s.unpack(packed_data)

print 'Original values:', values
print 'Format string :', s.format
print 'Uses :', s.size, 'bytes'
print 'Packed Value :', binascii.hexlify(packed_data)
print 'Unpacked Type :', type(unpacked_data), ' Value:', unpacked_data

输出为:

Original values: (1, 'abc', 2.7) 
Format string : I3sf
Uses : 12 bytes
Packed Value : 0100000061626300cdcc2c40
Unpacked Type : <type 'tuple'> Value: (1, 'abc', 2.700000047683716)

代码中,

首先定义了一个元组数据,

包含int、string、float三种数据类型,

然后定义了struct对象,并制定了format‘I3sf’,

  • I 表示int,

  • 3s表示三个字符长度的字符串,

  • f 表示 float。最后通过struct的pack和unpack进行打包和解包。通过输出结果可以发现,

value被pack之后,

转化为了一段二进制字节串,

而unpack可以把该字节串再转换回一个元组,

但是值得注意的是对于float的精度发生了改变,

这是由一些比如操作系统等客观因素所决定的。打包之后的数据所占用的字节数与C语言中的struct十分相似。定义format可以参照官方api提供的对照表:

字节序设置

另一方面,打包的后的字节顺序默认上是由操作系统的决定的,

当然struct模块也提供了自定义字节顺序的功能,

可以指定大端存储、小端存储等特定的字节顺序,

对于底层通信的字节顺序是十分重要的,

不同的字节顺序和存储方式也会导致字节大小的不同。在format字符串前面加上特定的符号即可以表示不同的字节顺序存储方式,

例如采用小端存储 s = struct.Struct(‘<I3sf’)就可以了。官方api library 也提供了相应的对照列表:

MySQL入门二之一些小注意点

Posted on 03-02-2015 | In DB

distinct关键字

distinct是应用于所有列的, 而不是某一个列

mysql> select * from test_table;
+------+------+
| one | two |
+------+------+
| 56 | 12 |
| 52 | 10 |
| 56 | 12 |
| 56 | 13 |
+------+------+
4 rows in set (0.00 sec)

mysql> select distinct one, two from test_table;
+------+------+
| one | two |
+------+------+
| 56 | 12 |
| 52 | 10 |
| 56 | 13 |
+------+------+
3 rows in set (0.00 sec)

mysql> select distinct one from test_table;
+------+
| one |
+------+
| 56 |
| 52 |
+------+
2 rows in set (0.00 sec)

. . .

1…23242526272829303132333435363738
Mike

Mike

🚙 🚗 💨 💨 If you want to create a blog like this, just follow my open-source project, "hexo-theme-neo", click the GitHub button below and check it out ^_^ . It is recommended to use Chrome, Safari, or Edge to read this blog since this blog was developed on Edge (Chromium kernel version) and tested on Safari.

11 categories
298 posts
111 tags
about
GitHub Spotify
© 2013 - 2025 Mike