AES的优化及其记录
- 在最初完全是按照书上的说明照葫芦画瓢实现了AES,能跑的通,但效率很低,对同一明文反复进行加密的话,每秒差不多5MB的加密速度,这里的速度和个人电脑关系密切,我在每次加密都执行一次密钥扩展,如果多次加密使用同一密钥的话,可以单独仅实现一次密钥扩展,速度会快很多,大概能有一倍的速率提升。
- 使用查表法能大幅提升加密速度,最简单的在列混淆时GF28上的乘法(只用乘2和乘3)可以列个表能有一定的提升,大概20%左右,达到了6MB/S。
- 查表法真正的提升是在列混淆的建表上,可以建成一个4*256大小的int数组,能极大提升加密速度,这里注意AES的轮函数中,S盒代替和行移位的顺序可以交换,我先将S盒和列混淆放入了同一张表,我的代码建表后达到了9-10MB/S,比起最初的版本效率几乎翻了一倍。
- AES的数据是按列输入的,而这样在计算机中操作并不方便,将AES转置后可以按行输入数据,这样可以使用指针的强制类型转换来减少循环的轮次,部分操作将8bit的数据按照32位或64位进行存取,注意在转置后的部分操作也要进行转置的变化,包括行移位和列混淆,在进行这一步的同时,我将S盒,行移位和列混淆的查表进行了统一,进一步提升了查表效率,执行速度再次翻倍,达到22M/S。AES的转置实现见-AES转置实现。注意在对指针进行强行转换后会出现int内部的字节倒序的现象,见字节倒序。
- 其实在第四部中就可以看出,在追求运行速度的时候,循环的多少,函数的套用还有临时变量的建立,都会影响到程序的效率,我开始尽可能删除一切的多余代码,与此同时,带来的是代码的可读性和可维护性大大降低,当然在进行查表融合的时候代码就已经没有什么可读性了。每删除一个函数封装,临时变量或函数循环,都可以带来肉眼可见速度提升,每次都能提升大概1MB/S左右,在删除的差不多的时候,我的代码有了将近50%的速度提升,到达了33MB/S。注意这里有些技巧,对小段代码使用内联函数可以在提升运行效率的同时保持代码的可读性,对于大段的代码取消函数封装有时并不能提升性能反而会有所损失,4次及以下的循环改为手写的顺序执行可能效率会更高.
- 复制操作将数组转为uin64_t会减少循环提升效率,但涉及到其中的移位等操作,32bit或64bit的反而没有直接按字节操作效率高。详细到密钥扩展中g函数的循环移位,将按字循环改为按字节交替位置后我的代码直接提高到了37MB/S。
- 代码中有很多的临时数组需要建立,在进行类似
sate(t + 1) = fun(state(t))
的操作时,需要备份t时间的数据以免原地操作的破坏,我们可以在静态存储区建立持久的temp[16]变量来省去分配变量空间的时间,实际上,在对需要备份的轮函数观察后,发现可以通过设计来避免零散的数组内容复制:假设我们现在拥有两个持久的长度16的数组分别为temp和text,在加密的初始我们通过一轮复制保持两个数组中数据的一致(都为明文),经过前面的优化,现在在每一轮函数中我们执行两个操作,一个是S盒代替,行移位和列混淆的联合变换,一个是轮密钥加。联合变换中我们将text数组作为备份,执行结果存储在temp中,temp = fun(text)
,轮密钥加中我们将结果存储在text中,text = temp ^ roundKey
,在经过所有轮次后text中就是我们所需要的密文,从而省去很多分配临时数组和复制数据的时间。代码速度提升到45MB/S。
我的代码优化到这里也就差不多了,在将密钥扩展移出只执行一次后(每次加密使用同一密钥),加密速到可以达到105MB/S,可做参考。
这是之前的优化代码记录,这几天又发现了一些问题,作为补充:
- 我之前的运行都是在debug下运行的,换成release会快很多,大概有150%-200%的提速。达到150MB到250MB的样子。
- 上述第7点还有优化的空间,我们看到本来在轮密钥加环节时,可以有
text ^= roundkey
,而现在变成了text = temp ^ roundKey
,^=
其实对于程序运行有很大的提升,因为上述的改动而失去了这个优化,我们可以这样来进行,使用两个指针temp和text分别指向两个大小位16的数组,代码中的操作始终以temp作为备份,text作为操作空间,但在每轮结束后交换temp和text指针指向的地址,从而达到对实际操作地址的改变,同时能享有^=
符号的编译加速。 - 其实有一个一直忘了说的优化点,做成一个一维数组后,直接
SBOX[uint8_t t]
就可以完成转换了。 - 再想要加速就使用Intel的SSE指令集了