非常感谢,我正在尝试优化用C编写的Kasumi算法.Fin函数中有S-box用于加密数据,S7-box有127个元素,S9-box有512个元素。 FI功能代码如:
static u16 FI(u16 in, u16 subkey)
{
static u16 s7[] = {...};
static u16 s9[] = {...};
nine = (u16)(in>>7);
seven = (u16)(in&0x7F);
/* Now run the various operations */
nine = (u16)(S9[nine] ^ seven);
seven = (u16)(S7[seven] ^ (nine & 0x7F));
seven ^= (subkey>>9);
nine ^= (subkey&0x1FF);
nine = (u16)(S9[nine] ^ seven);
seven = (u16)(S7[seven] ^ (nine & 0x7F));
in = (u16)((seven<<9) + nine);
return( in );
}
u16表示无符号短文。
通过一些转变。我将S7-box和S9-box合并到S16-box,我使用avx指令使16个数据并行。 FI函数的代码如:
static u16 FI(__m256i in, u16 subkey)
{
u16 arr[16];
_mm256_store_si256((__m256i*)arr, in);
u8 i;
for(i = 0; i < 16; i++)
{
arr[i] = (u16)(s16[arr[i]] ^ subkey);
arr[i] = (arr[i] << 7) | (arr[i] >> 9);
arr[i] = s16[arr[i]];
}
in = _mm256_load_si256((__m256i*)arr);
}
S16-box有65536个元素,因此可能会发生一些缓存未命中。我也使用收集指令,如:
inline static __m256i FI( __m256i in, u16 subkey )
{
__m256i _tmp = _mm256_set1_epi32(0xffff);
__m256i even_sequence = _mm256_and_si256(in, _tmp);
__m256i odd_sequence = _mm256_srli_epi32(in, 16);
even_sequence = _mm256_i32gather_epi32((int const*)s16, even_sequence, 2);
__m256i _subkey = _mm256_set1_epi16(subkey);
even_sequence = _mm256_xor_si256(even_sequence, _subkey);
even_sequence = _mm256_and_si256(even_sequence, _tmp);
odd_sequence = _mm256_i32gather_epi32((int const*)s16, odd_sequence, 2);
odd_sequence = _mm256_xor_si256(odd_sequence, _subkey);
odd_sequence = _mm256_and_si256(odd_sequence, _tmp);
// rotate
__m256i hi = _mm256_slli_epi16(even_sequence, 7);
__m256i lo = _mm256_srli_epi16(even_sequence, 9);
even_sequence = _mm256_or_si256(hi, lo);
//same for odd
hi = _mm256_slli_epi16(odd_sequence, 7);
lo = _mm256_srli_epi16(odd_sequence, 9);
odd_sequence = _mm256_or_si256(hi, lo);
even_sequence = _mm256_i32gather_epi32((int const*)s16, even_sequence, 2);
odd_sequence = _mm256_i32gather_epi32((int const*)s16, odd_sequence, 2);
even_sequence = _mm256_and_si256(even_sequence, _tmp);
odd_sequence = _mm256_slli_epi32(odd_sequence, 16);
in = _mm256_or_si256(even_sequence, odd_sequence);
return in;
}
但是性能不能满足要求,我也想到了位片。我读了一篇可以使128个数据并行但需要一些硬件支持的论文。我认为位转换操作耗时并且存在许多约束。
非常感谢!
答案 0 :(得分:1)
这段代码可以解释性能问题以及它下面的评论。
static u16 FI(__m256i in, u16 subkey) {
u16 arr[16];
_mm256_store_si256((__m256i*)arr, in);
u8 i;
for(i = 0; i < 16; i++)
{
arr[i] = (u16)(s16[arr[i]] ^ subkey);
arr[i] = (arr[i] << 7) | (arr[i] >> 9);
arr[i] = s16[arr[i]];
}
in = _mm256_load_si256((__m256i*)arr);
}
S16-box有65536个元素,因此可能会发生一些缓存未命中。
平均x64处理器只有32KB的L1(AMD有时候会有64K,但现在让我们忽略它)。
这意味着使用随机访问模式,您的64K阵列将获得32KB / 64KB * 100%= 50%的缓存命中率,如果没有其他数据结构使用任何L1并且您未运行的高速线程也可能使用某些L1另一个线程。
让我们简化一下,说你只有16KB的64KB,每次访问都有75%的错失机会。所以你的循环在每一行之间都有数据依赖关系,即。下一个语句不能在前一个语句完成之前开始。幸运的是,每次迭代都是独立于其他迭代的数据。
arr[i] = (u16)(s16[arr[i]] ^ subkey);
arr[i] = (arr[i] << 7) | (arr[i] >> 9);
arr[i] = s16[arr[i]];
此时 arr
几乎肯定会在L1缓存中,仅产生4个周期的启动成本,每次访问s16将平均成本为0.25 * 4 + 0.75 * 12 = 1 + 9 = 10个周期。这给出了每个语句的以下近似延迟成本(忽略了存储和重载arr [i]的成本,假设arr [i]存储在寄存器中)
arr[i] = (u16)(s16[arr[i]] ^ subkey); // arr: 4 + S16: 10 + ^:1
arr[i] = (arr[i] << 7) | (arr[i] >> 9); // << : 1 + |: 1
arr[i] = s16[arr[i]]; // s16 : 10 + store arr : 4
每次迭代的31个周期延迟,幸运的是每次迭代之间没有数据依赖关系。每次迭代需要大约3个周期才能发出,因此最后一个将在~3 * 16 + 31 = 79个周期内完成,假设完美的分支预测并忽略最后in
分配的数据危险。
我认为你的下一个代码是将这个循环重写为AVX2将具有许多相同的加载依赖性和完全相同的缓存未命中,循环开销将消失但是一些较长的延迟AVX指令可能会增加时间。平均时间仍然是~31个周期延迟+一些AVX延迟+16个负载/(每个周期最多2个负载),假设40个周期。
如果你没有合并S7和S9,它们只需要(128 + 512)* 2个字节,并且当你运行更长的编码时几乎肯定会在L1缓存中。然后,循环延迟将以负载数量的两倍为代价降低一半,并且您的完整AVX将达到每周期15 + 32次/ 2次,假设30个周期。
好消息是每个16字节的迭代似乎独立于前一个,因此它们可能会在时间上重叠。但是你最终受限于加载和存储量,一个初始加载,来自s7 + s9和一个存储的32个加载,最多2个存储或加载限制最大可能吞吐量到16个字节/((1 + 32 + 1)/ 2)周期。
这是一个很乐观的假设,只有2个不同代码(s16 vs s7 + s9)的真实测量才能决定什么是最好的。