并行/组合多个 64 位值的按位排列
Posted
技术标签:
【中文标题】并行/组合多个 64 位值的按位排列【英文标题】:Bitwise permutation of multiple 64bit values in parallel / combined 【发布时间】:2011-08-20 02:24:57 【问题描述】:这个问题不是关于“我如何按位排列”我们现在如何做到这一点,我们正在寻找一种更快的方法,使用更少的 cpu 指令,灵感来自 DES 中 sbox 的位片实现强>
为了加快某些密码代码的速度,我们希望减少排列调用的数量。主要的密码函数基于查找数组进行多次按位排列。由于置换操作只是位移,
我们的基本思想是获取多个需要相同排列的输入值,然后并行移动它们。例如,如果输入位 1 必须移动到输出位 6。
有没有办法做到这一点?我们目前没有示例代码,因为完全不知道如何以高效的方式完成此操作。
我们平台上的最大值大小是128bit,最长输入值是64bit。因此代码必须更快,然后做整个排列128次。
编辑
这是一个简单的 8 位排列示例
+---+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <= Bits
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <= Input
+---+---+---+---+---+---+---+---+
| 3 | 8 | 6 | 2 | 5 | 1 | 4 | 7 | <= Output
+---+---+---+---+---+---+---+---+
密码使用多个输入键。这是一个分组密码,因此必须将相同的模式应用于输入的所有 64 位块。
由于每个输入块的排列相同,我们希望在一个步骤中处理多个输入块/以组合多个输入序列的操作。不是每次调用移动 128 次 1 位,而是一次移动 1 次 128 位。
EDIT2
我们不能使用线程,因为我们必须在没有线程支持的嵌入式系统上运行代码。因此我们也无法访问外部库,我们必须保持纯 C。
解决方案
在测试并使用给定的答案后,我们按照以下方式完成了:
我们将 128 个 64 位值的单个位放入 uint128_t[64]* 数组中。 对于置换,我们只需复制指针 完成所有操作后,我们恢复第一个操作并返回 128 个置换值是的,就是这么简单。我们在项目早期就以这种方式进行了测试,但速度太慢了。看来我们在测试代码中有错误。
谢谢大家的提示和耐心。
【问题讨论】:
你能举出更多例子吗? “要获取多个输入值,需要相同的排列,然后并行移动它们”还不是很清楚。 您说您使用的是嵌入式平台 - 哪个?您的 CPU 的能力显然将是一个重要因素。 只针对permutation阶段,nemo的bit-slicing解决方案似乎并没有更快(如果我理解正确的话,ideone.com/OYORo的Version B应该会给你bit-slicing的方法) .但我认为他说得对:“在位片表示上实现所有阶段”。在我看来,这应该是一个优势。 @Nick 有一个保密协议,阻止我说太多。但我可以告诉你,我们的系统原生支持or
、and
、xor
、nand
、nor
,寻址模式从 8 位到 128 位,以及 256 个 128 位寄存器。
@Christian 感谢比较运行时的工作。我们将再次尝试使用位切片。我们已经尝试过了,它比你的例子慢得多。所以,看看代码是否有任何问题。
【参考方案1】:
您可以使用八个将字节映射到 64 位字的查找表来加快 Stan 的逐位代码。要处理来自输入的 64 位字,请将其分成 8 个字节并从不同的查找表中查找每个字节,然后对结果进行 OR。在我的计算机上,后者比 32 位排列的逐位方法快 10 倍。显然,如果您的嵌入式系统几乎没有缓存,那么 32 kB 16 kB 的查找表可能是个问题。如果一次处理 4 位,则只需要 16 个 16*8=128 字节的查找表,即 2 kB 的查找表。
编辑:内部循环可能看起来像这样:
void permute(uint64_t* input, uint64_t* output, size_t n, uint64_t map[8][256])
for (size_t i = 0; i < n; ++i)
uint8_t* p = (uint8_t*)(input+i);
output[i] = map[0][p[0]] | map[1][p[1]] | map[2][p[2]] | map[3][p[3]]
| map[4][p[4]] | map[5][p[5]] | map[6][p[6]] | map[7][p[7]];
【讨论】:
因为我必须将位从任何位置移动到任何位置,我无法将输入和输出拆分为 8 位。而且我们必须一点一点地使用,因为它是一个 BITWISE 密码 ... 请注意,这个想法是将一个字节映射到一个 64 位字中,只是为了允许在 64 位字中的任何位置移动位。只要您不需要将位移动到另一个 64 位字中,它就可以正常工作。 那么,你应该举个例子,因为我现在无法想象 我添加了一个内部循环的示例实现。设置查找表作为练习留给读者... 我想这取决于处理器。我将代码添加到 Christian Ammer 的基准测试(作为版本 C),结果如下:版本 A 时间 = 0.675184;版本 B 时间 = 1.3193;版本 C 时间 = 0.016659【参考方案2】:我想您可能正在寻找bit-slicing implementation。这就是最快的 DES 破解工具的工作原理。 (或者无论如何,它是在 SSE 指令存在之前。)
我们的想法是以“逐位”方式编写函数,将每个输出位表示为输入位上的布尔表达式。由于每个输出位仅取决于输入位,因此任何函数都可以用这种方式表示,即使是加法、乘法或 S-box 查找等。
诀窍是使用单个寄存器的实际位来表示来自多个输入字的单个位。
我将用一个简单的四位函数来说明。
假设,例如,您想采用以下形式的四位输入:
x3 x2 x1 x0
...对于每个输入,计算一个四位输出:
x2 x3 x2^x3 x1^x2
您想为八个输入执行此操作。 (对于四位来说,查找表是最快的。但这只是为了说明原理。)
假设您的八个输入是:
A = a3 a2 a1 a0
B = b3 b2 b1 b0
...
H = h3 h2 h1 h0
这里,a3 a2 a1 a0
代表A
输入的四位等。
首先,将所有八个输入编码为四个字节,其中每个字节保存八个输入中的每个输入的一个位:
X3 = a3 b3 c3 d3 e3 f3 g3 h3
X2 = a2 b2 c2 d2 e2 f2 g2 h2
X1 = a1 b1 c1 d1 e1 f1 g1 h1
X0 = a0 b0 c0 d0 e0 f0 g0 h0
这里,a3 b3 c3 ... h3
是X3
的八位。它由所有八个输入的高位组成。 X2
是所有八个输入的下一位。以此类推。
现在要并行计算八次函数,您只需:
Y3 = X2;
Y2 = X3;
Y1 = X2 ^ X3;
Y0 = X1 ^ X2;
现在 Y3 保存所有八个输出的高位,Y2 保存所有八个输出的下一个位,依此类推。我们只是使用四个机器指令在八个不同的输入上计算了这个函数!
更好的是,如果我们的 CPU 是 32 位(或 64 位),我们可以在 32 个(或 64 个)输入上计算此函数,仍然只使用 4 条指令。
当然,对“位片”表示的输入进行编码和对输出进行解码需要一些时间。但是对于正确的函数类型,这种方法提供了巨大的位级并行性,从而大大提高了速度。
基本假设是您有许多输入(如 32 或 64),您希望在这些输入上计算相同的函数,并且该函数既不太难也不太容易表示为一堆布尔运算。 (太难使原始计算变慢;太容易使时间由位片编码/解码本身支配。)特别是对于密码学,其中(a)数据必须经过许多“轮”处理,(b ) 该算法通常已经根据位进行了修改,并且 (c) 例如,您正在对同一数据尝试许多密钥......它通常工作得很好。
【讨论】:
感谢您对位切片的精彩描述。但我不明白我们如何用这个实现来做基于矩阵的排列。当然,我们已经在代码的某些部分使用了 bitslice,但我不知道这如何帮助我们进行排列,就像我解释的那样...... @Thomas:但是排列很简单。在我的示例中,每个输入的 x3 和 x2 位被交换(置换)以产生相应的输出......再说一次,无论您的函数是什么(甚至是查找表),都存在 some用对输入位的布尔运算来表示它。 (布尔逻辑是通用的。) 请解释一下:我如何移动 128 个 64 位输入这种样式:输入位 1(对于 128 个中的每一个)必须在 128 个不同的输出中输出位 58。例如,第 58 位到第 9 位。你提到了DES。以任何排列(不是 sbox)为例 ;) 您的寄存器是 64 位的吗?然后让我们改用 64 个 64 位输入。首先从 all 64 个输入中取出位 0,并将它们放入一个 64 位字 X0 中。然后从 all 64 个输入中取出第 1 位,并将它们放入一个 64 位字 X1 中。依此类推,直到 X63。 (是的,这部分很慢,但你只做一次。)现在如果输出位 9 需要等于输入位 58,只需设置“Y9 = X58”。等等。最后,Y0 保存 all 64 个输出的第 0 位。 Y1 保存 all 64 个输出的第 1 位。等等。只需 64 条指令,您就可以对 64 个输入进行任意 64 位排列...使用位片编码 假设您通过多个阶段获取输入(对于加密,您肯定是?),您可以在开始时进行 once 转换,实现 all 位片表示上的阶段,然后在最后变换回 一次。这就是 DES 破解器的工作原理…… DES 有 16 个“回合”。他们实现了所有轮次以直接在位片编码形式上工作。位片实现的并行性足以弥补转换输入和输出的成本。【参考方案3】:似乎很难在一次调用中完成排列。您的问题的一种特殊情况,即反转整数中的位,需要多个“调用”(调用是什么意思?)。有关此示例的信息,请参阅Bit Twiddling Hacks by Sean。
如果你的映射模式不复杂,也许你可以找到一种快速计算答案的方法:)但是,我不知道你是否喜欢这种直接的方式:
#include <stdio.h>
unsigned char mask[8];
//map bit to position
//0 -> 2
//1 -> 7
//2 -> 5
//...
//7 -> 6
unsigned char map[8] =
2,7,5,1,4,0,3,6
;
int main()
int i;
//input:
//--------------------
//bit 7 6 5 4 3 2 1 0
//--------------------
//val 0 0 1 0 0 1 1 0
//--------------------
unsigned char input = 0x26;
//so the output should be 0xA1:
// 1 0 1 0 0 0 0 1
unsigned char output;
for(i=0; i<8; i++) //initialize mask once
mask[i] = 1<<i;
//do permutation
output = 0;
for(i=0; i<8; i++)
output |= (input&mask[i])?mask[map[i]]:0;
printf("output=%x\n", output);
return 0;
【讨论】:
我们已经这样做了。如果你必须使用 100 MB 的输入,那就太慢了 你的例子是一个函数调用。我们想要合并这些操作,因为位移的成本很高。【参考方案4】:您最好的选择是研究某种类型的线程方案......您可以使用消息传递系统,将每个块发送到一组固定的工作线程,或者您可以设置一个非-锁定以“同步”方式执行多个班次的单个生产者/消费者队列。我说“同步”是因为通用 CPU 上的管道不会像在固定功能设备上那样是真正的同步管道操作,但基本上对于给定的“时间片”,每个线程都在工作同时处理多阶段问题的一个阶段,您可以将源数据“流式传输”进和流出管道。
【讨论】:
好吧,那么你不会得到任何真正的并行性......没有线程意味着你只会在单核上执行你的代码,而单核不能同时做两件事. 对不起,我有点不清楚,我想合并操作。 bitslicing 答案显示了 64 位 CPU 如何并行处理 64 位。所有并行的诀窍在于你在哪里找到它(线程,位并行)以及你如何利用它。以上是关于并行/组合多个 64 位值的按位排列的主要内容,如果未能解决你的问题,请参考以下文章