C/C++关键字之restrict

简介: C/C++关键字之restrict

C/C++关键字之restrict


在C语言中,restrict关键字用于修饰指针(C99标准)。通过加上restrict关键字,编程者可提示编译器:在该指针的生命周期内,其指向的对象不会被别的指针所引用。


需要注意的是,在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;
}



以上代码分别执行add1add2函数各一亿次,计算二者耗时,结果如下。


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倍!所以对于我辈码农来说,多了解一些底层原理永远不亏,说不定哪天你就用上了~

相关文章
|
27天前
|
存储 安全 算法
【C/C++ 关键字 函数说明符 】C++ noexcept 关键字(指定某个函数不抛出异常)
【C/C++ 关键字 函数说明符 】C++ noexcept 关键字(指定某个函数不抛出异常)
25 0
|
27天前
|
设计模式 算法 安全
【C/C++ 关键字 函数说明符 】C++ final关键字(修饰成员函数无法被子类重写覆盖)
【C/C++ 关键字 函数说明符 】C++ final关键字(修饰成员函数无法被子类重写覆盖)
37 1
|
27天前
|
算法 安全 编译器
【C++ 关键字 override】C++ 重写关键字override(强制编译器检查该函数是否覆盖已存在的虚函数)
【C++ 关键字 override】C++ 重写关键字override(强制编译器检查该函数是否覆盖已存在的虚函数)
25 0
|
27天前
|
算法 Java 编译器
【C++ 关键字 virtual 】C++ virtual 关键字(将成员函数声明为虚函数实现多态
【C++ 关键字 virtual 】C++ virtual 关键字(将成员函数声明为虚函数实现多态
25 0
|
30天前
|
存储 缓存 安全
【C/C++ 关键字 存储类说明符 】 线程局部变量的魔法:C++ 中 thread_local的用法
【C/C++ 关键字 存储类说明符 】 线程局部变量的魔法:C++ 中 thread_local的用法
33 0
|
30天前
|
存储 安全 编译器
【C++ 关键字 类型限定符 】揭秘C++编程中的神秘元素:深入了解volatile关键字的强大作用
【C++ 关键字 类型限定符 】揭秘C++编程中的神秘元素:深入了解volatile关键字的强大作用
19 0
|
11天前
|
编译器 C语言 C++
【C++的奇迹之旅(二)】C++关键字&&命名空间使用的三种方式&&C++输入&输出&&命名空间std的使用惯例
【C++的奇迹之旅(二)】C++关键字&&命名空间使用的三种方式&&C++输入&输出&&命名空间std的使用惯例
|
24天前
|
存储 算法 编译器
【C++ 关键字 static_assert 相关问题】C++ 关于静态断言的编译问题 ,深入了解静态断言
【C++ 关键字 static_assert 相关问题】C++ 关于静态断言的编译问题 ,深入了解静态断言
28 0
|
24天前
|
算法 编译器 C++
【C++ 关键字的混合使用 】C++深度探索:auto、static、constexpr的交互影响与应用
【C++ 关键字的混合使用 】C++深度探索:auto、static、constexpr的交互影响与应用
31 0
|
27天前
|
算法 编译器 C语言
【C/C++ 关键字 函数说明符 】C/C++ _Noreturn关键字(表明调用完成后函数不返回主调函数)
【C/C++ 关键字 函数说明符 】C/C++ _Noreturn关键字(表明调用完成后函数不返回主调函数)
33 1