🚙

💨 💨 💨

×

  • Categories

  • Archives

  • Tags

  • About

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() 就是用来解决这个问题的.

智能指针笔记

Posted on 04-11-2015 | In Misc

有些错误是编译器查不到的, 这种错误是最可怕的, 当项目大了之后,
即使用 Valgrind 也很难定位,
因为裸指针在团队合作中使用很容易导致其他成员忘记释放或多次释放, 所以在团队合作中一般使用智能指针.

而智能指针用的不好, 结果可能适得其反.

所以我们聊一下智能指针的几点注意事项.

总结自 C++ Primer.

一个简单的包含删除器的例子演示

#include<iostream>
#include<functional>
#include<memory>

using std::cout;
using std::endl;
using std::bind;
using namespace std::placeholders;

int testBind( int* a, int b, int c )
{
cout << *a + b + c << endl;
return *a;
}

struct Foo
{
Foo() = default;
Foo( const Foo & a )
{
data = a.data;
std::cout << "复制构造" << std::endl;
}
void print_sum( int n1, int n2 )
{
std::cout << n1 + n2 << '\n';
}
int data = 10;
};

int main()
{
//绑定类成员函数用对象的指针
Foo foo;
auto f3 = std::bind( &Foo::print_sum, &foo, 95, _1 );
f3( 5 );

auto check_testBind = std::bind( testBind, std::placeholders::_1, 3, 9 );
int * p = new int( 7 );
cout << check_testBind( p ) << endl;

shared_ptr<int> pi( new int(),
check_testBind );
*pi = 88;

shared_ptr<int> pii( new int( 12 ),
std::bind( testBind, std::placeholders::_1, 32, 19 ) );

std::function< int( int* ) > test_function_bind =
std::bind( testBind, std::placeholders::_1, 331, 9 );

cout << "test_function_bind( p, 331, 9 ) = " << test_function_bind( p ) << endl;;

shared_ptr<int> piii( new int( 112 ),
test_function_bind );

return 0;
}

打印结果 :

100
119
7
347
test_function_bind( p, 331, 9 ) = 7
452
63
100
请按任意键继续. . .

智能指针陷阱

智能指针可以提供对动态分配的内存安全而又方便的管理,但这建立在正确使用的
前提下 。 为了正确使用智能指针,我们必须坚持一些基本规范 :

  • 不使用相同的内置指针值初始化(或 reset) 多个智能指针 。
  • 不 delete get ( ) 返 回的指针 。
  • 不使用 get () 初始化或 reset 另 一 个智能指针 。
  • 如果你使用 get () 返 回的指针,记住当最后一个对应的智能指针销 毁 后, 你 的
    指 针就 变为无 效 了 。
  • 如果你使用智能指针管理的资源不是 new 分配的内存 , 记住传递给它一个删除
    器( 参见 12. 1.4 节 , 第 415 页和 12. 1.5 节 , 第 419 页)。

尽量用make_shared而非new

shared_ptr 可以协调对象的析构 , 但这仅限于其自身的拷贝 ( 也 是 shared_ptr)
之间。

这也是为什么我们推荐使用 make_shared 而不是 new 的原因 。

这样 , 我们就能在分配对象的同时就将 shared_ptr 与之绑定,
从而避免了无意中将同一块内存绑定到多个独立创建的 shared_ptr 上 。(这是最容易犯的错)

总结 : 所以我们要尽量一开始就用make_shared来分配动态内存, 而不是先new一个出来, 再找机会将它转为智能指针.

考虑下面对 shared_ptr 进行操作的函数 :

// 在函数被调用时 ptr 被创建并初始化
void process(shared_ptr<int> ptr)
{
// 使用 ptr
} // ptr 离 开作用域,被销毁

process 的参数是传值方式传递的,因此实参会被拷贝到 ptr 巾 。 拷贝 一 个 shared_ptr
会递增其引用讨数,因此, 在 process 运行过程中,引用七| 数值至少为 2 。 当 process
结束时, ptr 的引用计数会边喊,但不会变为 0 。 因此,当用音11变 11 ptr 被销毁时, ptr
指向的内存不会被释放 。

使用此函数的正确方法是传递给它一个 shared_ptr :

shared_ptr<int> p(new int(42)) ; // 引用计数为 1
process(p); // 拷贝 p 会递增它的引用计数 ;在 process 中引用计数位为 2
int i = *p; // 正确:引用计数位为 1

虽然不能传递给 process 一 个内置指针,但可以传递 给它 一 个(临时的)
shared_ptr , 这个 shared_ptr 是用 一个内 置指针显式构造的 。 但是,这样做很可能
会导致错误 :

int *x(new int(1024)); // 危险 x 是一个普通指针,不是一个智能指针
process(x) ; // 错误 : 不能将 int* 转换为 一个 shared_ptr<int>
process(shared_ptr<int>(x)); // 合法的,但内存会被释放!
int j = *x ; //未定义的 x 是一个空悬指针!

在上面的调用中 , 我们将一个临时 shared_ptr 传递给 process 。 当这个调用所在的表
达式结束时,这个临时对象就被销毁了 。 销毁这个临时变量会递减引用计数,此时引用计
数就变为 0 了 。 因此,当临时对象被销毁时 , 它所指向的内存会被释放 。
但 x 继续指 向 (已经释放的)内存,从而变成一个空悬指针。如果试图使用 x 的值,
其行为是未定义的 。

当将一个 shared_ptr 绑定到一个普通指针时 , 我们就将内存的管理责任交给了这
个 shared_ptr 一旦这样做 了 , 我们就不应该再使用内置指针来访问 shared_ptr 所
指向的内存了

不要使用 get 初始化另一个智能指针或为智能指针赋值

智能指针类型定义了 一个名为 get 的函数(参见表 1 2. J ),它返回一个内置指针,
指向智能指针管理的对象 。 此函数是为了这样一种情况而设计的 : 我们需要向不能使用智
能指针的代码传递一个内置指针。使用 get 返回的指告| 的代码不能 delete 此指针 。
虽然编译器不会给出错误信息 , 但将另一个智能指针也绑定到 get 返回的指针上是
错误的 :

shared_ptr<int> p(new int(42)) ; // 引 用计数为 1
int *q = p . get() ; // 正确 · 但使用 q 时妥注意,不要让它管理的指针被释放
{ // 新程序块
// 未定义:两个独立的 shared_ptr 指 向 相同的内存
shared ptr<int> (q) ;
} // 程序块结束, q 被销毁 , 它指向的内存被待放
int foo = *p ; // 未定义 p 指向的内存 已 经被释放了

在本例中, p 和 q 指 向相同的内存。由于它们是相互独立创建的,因此各自的引用计数都
是 1。 当 q 所在的程序块结束时 , q 被销毁 , 这会导致 q 指向的内存被释放 。 从而 p 变成
一个空悬指针,意味着当我们试图使用 p 时,将发生未定义的行为 。 而且 , 当 p 被销毁时 ,
这块内存会被第二次 delete 。

get 用来将指针的访问权限传递给代码,你只有在确定代码不会 get.
特别是,永远不要用 get 初始化另一个智能指针 del ete 指针或者为另一个智能指针赋值.

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…22232425262728293031323334353637
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
290 posts
111 tags
about
GitHub Spotify
© 2013 - 2025 Mike