设为首页 加入收藏

TOP

C语言进阶指南(3)丨显式内联、矢量扩展、C的逸闻轶事(三)
2019-04-07 16:07:48 】 浏览:303
Tags:语言 进阶 指南 内联 矢量 扩展 逸闻轶事
n.h

     AVX:immintrin.h

使用内建函数后,前面的例子可以改为:

#include <stdint.h>

#include <string.h>

#include <emmintrin.h>

static void __always_inline addtwo(int16_t *a, int16_t *b, int16_t size){

    int16_t i;

    __m128i c = _mm_set1_epi16(2);

    for (i = 0; i < size; i+=8) {

        __m128i bb = _mm_loadu_si128(b+i);  // movqdu b+i -> xmm0

        __m128i r = _mm_add_epi16(bb, c);   // paddw c + xmm0 -> xmm0

        _mm_storeu_si128(a+i, r);           // movqdu xmm0 -> a+i

    }

}

int main(){

    const int16_t size = 1024;

    int16_t a[size], b[size];

    /* ... */

    addtwo(a, b, size);

    return a[0];

}

当编译器产生次优的代码,或因代码中的 if 条件矢量类型不可能表达需要的操作时时,可能需要这种编写代码的方法。

6.2 内存对齐

注意到上个例子用了与 movqdu 而非 movqda (上面的例子里仅用 SIMD 产生的汇编指令使用的是 movqda。译者注)同义的 _mm_loadu_si128。这因为不确定 a 或 b 是否已按 16 字节对齐。使用的指令是期望内存对象对齐的,但使用的内存对象是未对齐的,这样肯定会导致运行错误或数据毁坏。为了让内存对象对齐,可在定义时用 aligned 属性指导编译器对齐内存对象。某些情况下,可考虑把关键数据按 64 字节对齐,因为 x86 L1 缓存也是这个大小,这样能提高缓存使用率。

#include <stdint.h>

#include <string.h>

#include <emmintrin.h>

static void __always_inline addtwo(int16_t *a, int16_t *b, int16_t size){

    int16_t i;

    __m128i c = _mm_set1_epi16(2) __attribute__((aligned(16)));

    for (i = 0; i < size; i+=8) {

        __m128i bb = _mm_load_si128(b+i);  // movqda b+i -> xmm0

        __m128i r = _mm_add_epi16(bb, c);   // paddw c + xmm0 -> xmm0

        _mm_store_si128(a+i, r);           // movqda xmm0 -> a+i

    }

}

int main(){

    const int16_t size = 1024;

    int16_t a[size], b[size] __attribute__((aligned(16)));

    /* ... */

    addtwo(a, b, size);

    return a[0];

}

考虑到程序运行速度,使用自动变量好过静态或全局变量,情况允许的话还应避免动态内存分配。当动态内存分配无法避免时,Posix 标准 和 Windows 分别提供了 posix_memalign和 _aligned_malloc 函数返回对齐的内存。

高效使用矢量扩展喊代码优化需要深入理解目标架构工作原理和能加速代码运行的汇编指令。这两个主题相关的信息源有Agner`s CPU blog和它的装订版Optimization manuals

 

七、逸闻轶事

本文最后一节讨论 C 编程语言里一些有趣的地方:

array[i] == i[array];

因为下标操作符等价于*(array + i),因此 array 和 i 是可交换的,二者等价。

$ gcc -dM -E - < /dev/null | grep -e linux -e unix

#define unix 1

#define linux 1

默认情况下,GCC 把 linux 和 unix 都定义为 1,所以一旦把其中一个用作函数名,代码就会编不过。

int x = 'FOO!';

short y = 'BO';

没错,字符表达式可扩展到任意整型大小。

x = i+++k;

x = i++ +k;

后缀自增符在加号之前被词法分析扫描到。

(即示例中两句等价,不同于 x = i +  (++k) 。译者注)

x = i+++++k; //error

x = i++ ++ +k; //error

y = i++ + ++k; //ok

词法分析查找可被处理的最长的非空格字符序列(C标准6.4节)。第一行将被解析成第二行的样子,它们俩都会产生关于缺少左值的错误,缺失的左值本应该被第二个自增符处理。

博主是一个有着7年工作经验的架构师,对于c++,自己有做资料的整合,一个完整学习C语言c++的路线,学习资料和工具。可以进我的Q群7418,18652领取,免费送给大家。希望你也能凭自己的努力,成为下一个优秀的程序员!另外博主的微信公众号是:C语言编程学习基地,欢迎关注!

首页 上一页 1 2 3 下一页 尾页 3/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇纪念第一次写博客 下一篇C语言进阶指南(2)丨数组和指针..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目