uint64_t mul384(uint64_t c[12], uint64_t a[6], uint64_t b[6]);//c = a * b
#include <stdint.h> #include "mul384.h" uint64_t mul384(uint64_t c[12], uint64_t a[6], uint64_t b[6]) { return 0; }
gcc -Wall -O2 mul384.c -S
这是mul384.s的全部内容
.file "mul384.c" .text .p2align 4,,15 .globl mul384 .type mul384, @function mul384: .LFB0: .cfi_startproc xorl %eax, %eax ret .cfi_endproc .LFE0: .size mul384, .-mul384 .ident "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-11)" .section .note.GNU-stack,"",@progbits
这个mul384.s文件之所以被称为汇编模板,是因为后面编写的汇编代码需要填入下面两句之间
.cfi_startproc .cfi_endproc
替换原先的这两条指令
xorl %eax, %eax ret
在x64平台上,无符号整数位宽为64位,一个384位无符号整数需要6个64位无符号整数构成,两个384位无符号整数相乘,其结果需要不超过768位的存储空间,占用12个64位无符号整数空间,故此设计C函数声明为:
uint64_t mul384(uint64_t c[12], uint64_t a[6], uint64_t b[6]);//c = a * b
uint64_t的定义在头文件stdint.h中,因此应用中编译C程序时要包含此头文件。
为了提高代码性能,采用“一次输入,一次输出”的内存访问策略,除了最初的输入和最后的输出外,整个运算过程中不访问内存,过程数据全部缓存于SSE寄存器中,为此CPU必须支持SSE42指令集,本文代码只适用于用户态程序,不能用于内核态(不清楚内核态和用户态编程区别的读者请无视这句话)。另外就是调用者传给函数用于输入输出的数组指针a[]、b[]和c[]必须满足16字节对齐要求,否则必将触发CPU错误导致进程崩溃。
384位乘法在大数运算中属于短位长运算,FFT乘法神马的就别想了,还不够折腾的,Karatsuba算法也不用考虑,对于常用x64处理器来讲,若将基本64位加法指令 addq 耗时设定为1单位,那么带进位64位加法指令 adcq 耗时为2单位,64位无符号乘法指令 mulq 耗时为4单位,综合考虑得失后,采用最基本的分治乘法算法作为本文使用的算法,详细如下:
|====|====|====|====|====|====|====|====|====|====|=====|=====| |c[0]|c[1]|c[2]|c[3]|c[4]|c[5]|c[6]|c[7]|c[8]|c[9]|c[10]|c[11]| |====|====|====|====|====|====|====|====|====|====|=====|=====| |a[0]*b[0]| | | | | | | | | | | |----|----|----|----|----|----|----|----|----|----|-----|-----| | |a[0]*b[1]| | | | | | | | | | | |a[1]*b[0]| | | | | | | | | | |----|----|----|----|----|----|----|----|----|----|-----|-----| | | |a[0]*b[2]| | | | | | | | | | | |a[1]*b[1]| | | | | | | | | | | |a[2]*b[0]| | | | | | | | | |----|----|----|----|----|----|----|----|----|----|-----|-----| | | | |a[0]*b[3]| | | | | | | | | | | |a[1]*b[2]| | | | | | | | | | | |a[2]*b[1]| | | | | | | | | | | |a[3]*b[0]| | | | | | | | |----|----|----|----|----|----|----|----|----|----|-----|-----| | | | | |a[0]*b[4]| | | | | | | | | | | |a[1]*b[3]| | | | | | | | | | | |a[2]*b[2]| | | | | | | | | | | |a[3]*b[1]| | | | | | | | | | | |a[4]*b[0]| | | | | | | |----|----|----|----|----|----|----|----|----|----|-----|-----| | | | | | |a[0]*b[5]| | | | | | | | | | | |a[1]*b[4]| | | | | | | | | | | |a[2]*b[3]| | | | | | | | | | | |a[3]*b[2]| | | | | | | | | | | |a[4]*b[1]| | | | | | | | | | | |a[5]*b[0]| | | | | | |----|----|----|----|----|----|----|----|----|----|-----|-----| | | | | | | |a[1]*b[5]| | | | | | | | | | | |a[2]*b[4]| | | | | | | | | | | |a[3]*b[3]| | | | | | | | | | | |a[4]*b[2]| | | | | | | | | | | |a[5]*b[1]| | | | | |----|----|----|----|----|----|----|----|----|----|-----|-----| | | | | | | | |a[2]*b[5]| | | | | | | | | | | |a[3]*b[4]| | | | | | | | | | | |a[4]*b[3]| | | | | | | | | | | |a[5]*b[2]| | | | |----|----|----|----|----|----|----|----|----|----|-----|-----| | | | | | | | | |a[3]*b[5]| | | | | | | | | | | |a[4]*b[4]| | | | | | | | | | | |a[5]*b[3]| | | |----|----|----|----|----|----|----|----|----|----|-----|-----| | | | | | | | | | |a[4]*b[5] | | | | | | | | | | | |a[5]*b[4] | | |----|----|----|----|----|----|----|----|----|----|-----|-----| | | | | | | | | | | | a[5]*b[5] | |====|====|====|====|====|====|====|====|====|====|=====|=====|
以高性能运算为目的的汇编语言编程设计中,寄存器规划是重中之重,还好384位乘法对x64处理器来讲只是入门级小菜,所以我用了两周时间完成了寄存器规划,详细使用规划如下:
(1).rdi是输出数据c[]的首地址,rsi是输入数据a[]的首地址,rdx是输入数据b[]的首地址 (2).xmm0 ~ xmm5这个6个SSE寄存器用于缓存运算过程与结果数据 |---------|---------|---------|---------|---------|-----------| | %xmm0 | %xmm1 | %xmm2 | %xmm3 | %xmm4 | %xmm5 | |----|----|----|----|----|----|----|----|----|----|-----|-----| |c[0]|c[1]|c[2]|c[3]|c[4]|c[5]|c[6]|c[7]|c[8]|c[9]|c[10]|c[11]| |----|----|----|----|----|----|----|----|----|----|-----|-----| (3).xmm6 ~ xmm8这个6个SSE寄存器用于输入数据a和b |---------|---------|---------|---------|---------|---------| | %xmm6 | %xmm7 | %xmm8 | %xmm6 | %xmm7 | %xmm8 | |----|----|----|----|----|----|----|----|----|----|----|----| |a[0]|a[1]|a[2]|a[3]|a[4]|a[5]|b[0]|b[1]|b[2]|b[3]|b[4]|b[5]| |----|----|----|----|----|----|----|----|----|----|----|----| (4).xmm14和xmm15用于r12 ~ r15的备份与恢复 |---------|---------| | %xmm14 | %xmm15 | |----|----|----|----| |%r12|%r13|%r14|%r15| |----|----|----|----| (5).r10 ~ r15用于乘法指令mulq的操作数,其数值固定 |----|----|----|----|----|----| |%r10|%r11|%r12|%r13|%r14|%r15| |----|----|----|----|----|----| |a[1]|a[3]|a[5]|b[1]|b[3]|b[5]| |----|----|----|----|----|----| (6).r8, r9, rsi三个通用寄存器用于累加过程,循环使用 |----|----|----|----|----|----|----|----|----|----|-----|-----| |c[0]|c[1]|c[2]|c[3]|c[4]|c[5]|c[6]|c[7]|c[8]|c[9]|c[10]|c[11]| |----|----|----|----|----|----|----|----|----|----|-----|-----| | |%r8 |%r9 |%rsi|%r8 |%r9 |%rsi|%r8 |%r9 |%rsi|%r8 |%r9 | |----|----|----|----|----|----|----|----|----|----|-----|-----|