更多精彩内容,请关注微信公众号:后端技术小屋git
在C语言中,restrict关键字用于修饰指针(C99标准)。经过加上restrict关键字,编程者可提示编译器:在该指针的生命周期内,其指向的对象不会被别的指针所引用。github
须要注意的是,在C++中,并没有明确统一的标准支持restrict关键字。可是不少编译器实现了功能相同的关键字,例如gcc和clang中的__restrict关键字。编程
那么restrict关键字能给程序的实际运行带来哪些好处呢?下面举例说明后端
int add1(int* a, int* b) { *a = 10; *b = 12; return *a + *b; }
你们猜猜add1
函数的返回值是多少?是10 + 12 = 22吗?性能优化
答案是不必定。在指针a和b的地址不一样时,返回22没有问题。可是当指针a与b指向的是同一个int对象时,该对象先被赋值为10,后被赋值为12,所以a和b都返回12,所以add1
函数最终返回24微信
使用-O3
优化, add1
对应的汇编代码以下。能够看到,在计算返回值时,为了获得*a
的值访问了1次内存,而无论在何种条件下(a == b
or a != b
),*b
的值都是12。所以聪明的编译器将*a
的值载入eax
寄存器后,直接加上当即数12,而无需再访问内存获取*b
的值。在没法肯定指针a和b是否相同的状况下,编译器只能帮你到这里了.函数
0000000000400a10 <_Z4add1PiS_>: 400a10: c7 07 0a 00 00 00 movl $0xa,(%rdi) ; *a = 10 400a16: c7 06 0c 00 00 00 movl $0xc,(%rsi) ; *b = 10 400a1c: 8b 07 mov (%rdi),%eax ; 结果 = *a 400a1e: 83 c0 0c add $0xc,%eax ; 结果 += 12 400a21: c3 retq
可是若是加上了restrict关键字,状况便大不相同。C/C++和通过-O3优化的汇编代码以下。经过restrict关键字,编译器依然确认指针a和b不可能指向同一个内存地址,所以在求*a + *b
时,无需访问内存,由于*a
必然等于当即数10,*b
必然等于当即数12。源码分析
int add2(int* __restrict a, int* __restrict b) { *a = 10; *b = 12; return *a + *b ; }
0000000000400a30 <_Z4add2PiS_>: 400a30: c7 07 0a 00 00 00 movl $0xa,(%rdi) ; *a = 10 400a36: b8 16 00 00 00 mov $0x16,%eax ; 结果 = 22 400a3b: c7 06 0c 00 00 00 movl $0xc,(%rsi) ; *b = 12 400a41: c3 retq
经过无restrict和有restrict两种状况下的汇编指令可看到,后者比前者少访问一次内存,且少执行一条指令。所以咱们预期有restrict的版本可以得到可观的性能提高:性能
int main() { int * a = new int; int * b = new int; { std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); for (size_t i=0; i<100000000; i++) add1(a, b); std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); std::cout << "Time difference = " << std::chrono::duration_cast<std::chrono::nanoseconds> (end - begin).count() << "[ns]" << std::endl; } { std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); for (size_t i=0; i<100000000; i++) add2(a, b); std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); std::cout << "Time difference = " << std::chrono::duration_cast<std::chrono::nanoseconds> (end - begin).count() << "[ns]" << std::endl; } return 0; }
以上代码分别执行add1
和add2
函数各一亿次,计算两者耗时,结果以下。优化
Time difference = 146[ns] Time difference = 56[ns]
在这个case里,使用restrict可以得到2+倍的性能提高!注意使用restrict的时候,编程者必须确保不会出现pointer aliasing
, 即同一块内存没法经过两个或以上的指针变量名访问。不知足这个条件而强行指定restrict, 将会出现undefined behavior
PS: 此篇文章有感于clickhouse
近期一个与restrict有关的性能优化(https://github.com/ClickHouse/ClickHouse/pull/19946),只是由于在聚合相关的函数中加上restrict关键字,便能使聚合性能提高1.6倍!因此对于我辈码农来讲,多了解一些底层原理永远不亏,说不定哪天你就用上了~
推荐阅读
更多精彩内容,请扫码关注微信公众号:后端技术小屋。若是以为文章对你有帮助的话,请多多分享、转发、在看。