zoukankan      html  css  js  c++  java
  • C代码调用汇编&使用指令集优化

      最近研究x264汇编代码,感觉使用到的优化思想和手法非常不错,在此写一个demon来记录我学习过程

    • 从搭建环境开始

      x264使用汇编优化的思想是将汇编代码编译到一个静态库里,供C代码调用,所以首先需要构建一个汇编函数得静态库。因为手动配置使用yasm来编译汇编文件,并生成一个lib相当麻烦,我选择的是使用cmake来构建。

      在demon里有一个sum.asm的汇编文件,文件里是所有的汇编函数,通过yasm编译后生成sum.obj,然后通过sum.obj来创建一个sum.lib库供C代码使用。还有一个main。c的C文件,用来生成可执行文件main,CMakeLists.txt文件如下:

    cmake_minimum_required(VERSION 3.0.00)
    
    project (asm)
    
    find_program(YASM_EXECUTABLE 
        NAMES yasm yasm-1.2.0-win32 yasm-1.2.0-win64
        HINTS $ENV{YASM_ROOT} ${YASM_ROOT}
        PATH_SUFFIXES bin
    )
    set(FLAGS -f win64 -DARCH_X86_64=1)
    add_custom_command(
            OUTPUT sum.obj
            COMMAND ${YASM_EXECUTABLE}  ARGS ${FLAGS} ../source/sum.asm -o sum.obj
            DEPENDS sum.asm)

    #添加静态库sum add_library( sum STATIC sum.obj sum.asm ) set_target_properties(sum PROPERTIES LINKER_LANGUAGE C)

    #添加使用静态库的可执行程序main
    add_executable( main main.c )
    target_link_libraries(main sum )

      其中find_program是在系统环境变量中寻找看是否有yasm汇编器,在此假设是有的。

      需要注意的是在COMMAND ${YASM_EXECUTABLE} ARGS ${FLAGS} ../source/sum.asm -o sum.obj中指定汇编文件的路径得是相对与工程文件所在的相对路径,所以这里是../source/sum.asm

      至此环境搭建完毕,使用cmake就能生成需要的汇编lib工程和调用汇编函数得可执行文件工程。在vs上如下图所示

      

    •  关于汇编

      先写一个最简单的例子(在此针对的是64bit汇编),假设main函数里需要对两个数字求和,代码如下:

    1 int sum(int a, int b);//此函数通过汇编实现
    2 
    3 int main(int argc, char *argv[])
    4 {
    5     int num = sum(2, 3);
    6     return 0;
    7 }

      那麽对应的汇编实现sum函数的代码如下:

    1 global sum
    2 
    3 sum:
    4 
    5     add ecx, edx ;直接使用ecx和edx寄存器中的参数
    6     mov eax, ecx
    7 
    8     ret

       这是一个最简单的C调用汇编函数得demon,在写汇编函数的时候碰到了以下问题:

    1. 之前学习的都是32位汇编的时候,函数参数的传递都是通过栈来完成的,在64位汇编中,前四个参数是通过寄存器rcx、rdx、r8、r9来传递的,只有参数个数大于4个后才通过栈来传递,所在在以上汇编代码中直接使用了寄存器ecx和edx中的值

      当函数参数个数大于4时,假设C代码如下:

    1 int sum(int a, int b, int c, int d, int e);
    2 
    3 int main(int argc, char *argv[])
    4 {
    5     int num = sum(2, 3, 4, 5, 6);
    6     return 0;
    7 }

      对应的汇编代码如下:

     1 global sum
     2 
     3 sum:
     4 
     5     add rcx, rdx
     6     add rcx, r8
     7     add rcx, r9
     8 
     9     mov rdx, [rsp + 40] ;从栈中取出第5个参数放入rdx寄存器
    10     add rcx, rdx
    11 
    12     mov rax, rcx
    13 
    14     ret

      此处需要注意的是:前四个参数是通过寄存器传递,从栈中取出第五个参数时,并不是从rsp+8的地方取,而是从rsp+40(40 = 4*8 + 8)的地方取,说明虽然前四个参数是通过寄存器传递,但是在栈中还是占用了相应的空间,我对此的理解是为了__stdcall和__cdecll的兼容吧。

    • 使用指令集优化(SSE AVX等)

      首先来看一下SIMD寄存器

     

      SSE使用到的SIMD寄存器是128bit,一共有16个,从XMM0到XMM15

      AVX拓展出来的SIMD寄存器是256bit,一共也是16个,从YMM0到YMM16,当然AVX也能使用SSE的XMM寄存器

      AVX2.0的时候将寄存器拓展到了512bit,一共有32个,从ZMM0到ZMM31

      假设我们的main函数是对两个数组进行求和,代码如下:

     1 #define N 8
     2 
     3 int sum(float a[], float b[]);
     4 
     5 int sum_c(float a[], float b[])
     6 {
     7     for (int i = 0; i < N; i++)
     8     {
     9         a[i] += b[i];
    10     }
    11     return 0;
    12 }
    13 
    14 int main(int argc, char *argv[])
    15 {
    16     float a[N] = { 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 };
    17     float b[N] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 };
    18     //将数组b[N]中的数据加到数组a[n]中
    19     sum_c(a, b);//不使用汇编优化
    20     sum(a, b);//使用汇编优化
    21     return 0;
    22 }

      可以看到,不使用汇编优化的话,在sum_c函数中,我们需要依次计算出a[i] + b[i]的和并保存在a[i]中。

      如果使用SSE指令集优化的话,代码如下:

     1 global sum
     2 
     3 sum:
     4 
     5     movups xmm0, [rcx]
     6     movups xmm1, [rdx]
     7     movups xmm2, [rcx + 16]
     8     movups xmm3, [rdx + 16]
     9 
    10     addps xmm0, xmm1
    11     addps xmm2, xmm3
    12 
    13     movups [rcx], xmm0
    14     movups [rcx + 16], xmm2
    15     
    16     ret

      可以看到,只需要进行两次加法运算就能计算出a[8]和b[8]中8个数字相加的和,这里需要进行两次计算是因为xmm寄存器是128bit,所以每次只能计算4个float数据,8个数据得分两次计算。

      使用AVX指令集优化代码如下:

     1 global sum
     2 
     3 sum:
     4 
     5     vmovups ymm1, [rcx]
     6     vmovups ymm2, [rdx]
     7 
     8     vaddps ymm0, ymm1, ymm2
     9     vmovups [rcx], ymm0
    10     
    11     ret

      因为AVX使用到了256bit的ymm寄存器,所以一次可以处理8个32bit的float数据,一次计算就能完成两组8个float数据分别的求和操作。

  • 相关阅读:
    Tesseract-OCR
    chrome浏览器插件推荐
    安装配置sublime Text 3 快捷键方式
    远程桌面与远程控制
    一个C#的XML数据库访问类
    WPF小程序:贪吃蛇
    恐惧源于一知半解
    8条佛曰 66句禅语
    自动开机 双网卡网络唤醒
    C#,Java,C++中的finally关键字
  • 原文地址:https://www.cnblogs.com/JeroZeng/p/4782438.html
Copyright © 2011-2022 走看看