我正在尝试在 T4 机器上快速创建大量 sha256 哈希。 T4 有一条“sha256”指令,允许我在一个操作码中计算哈希值。我创建了一个内联汇编模板来调用 sha256 操作码:
在我的 C++ 代码中:
extern "C"
{
void ProcessChunk(const char* buf, uint32_t* state);
}
pchunk.il:
.inline ProcessChunk,8
.volatile
/* copy state */
ldd [%o1],%f0 /* load 8 bytes */
ldd [%o1 + 8],%f2 /* load 8 bytes */
ldd [%o1 +16],%f4 /* load 8 bytes */
ldd [%o1 +24],%f6 /* load 8 bytes */
/* copy data */
ldd [%o0],%f8 /* load 8 bytes */
ldd [%o0+8],%f10 /* load 8 bytes */
ldd [%o0+16],%f12 /* load 8 bytes */
ldd [%o0+24],%f14 /* load 8 bytes */
ldd [%o0+32],%f16 /* load 8 bytes */
ldd [%o0+40],%f18 /* load 8 bytes */
ldd [%o0+48],%f20 /* load 8 bytes */
ldd [%o0+56],%f22 /* load 8 bytes */
sha256
nop
std %f0, [%o1]
std %f2, [%o1+8]
std %f4, [%o1+16]
std %f6, [%o1+24]
.end
在单线程环境中运行良好,但速度不够快。我使用 openmp 来并行化应用程序,以便我可以同时调用 ProcessChunk。该应用程序的多线程版本适用于几个线程,但当我增加线程数(例如 16 个)时,我开始得到虚假结果。 ProcessChunk 函数的输入都是每个线程的本地堆栈变量。我已经确认无论线程数量如何,输入都能正确生成。如果我将 ProcessChunk 放入关键部分,我会得到正确的结果,但性能会显着降低(单线程性能更好)。我对问题可能出在哪里感到难过。 solaris 线程是否可以踩到另一个线程的浮点寄存器?
有什么办法可以调试吗?
问候
更新:
我更改了代码以使用四倍大小(16 字节)加载和保存:
.inline ProcessChunk,8
.volatile
/* copy state */
ldq [%o1], %f0
ldq [%o1 +16],%f4
/* copy data */
ldq [%o0], %f8
ldq [%o0+16],%f12
ldq [%o0+32],%f16
ldq [%o0+48],%f20
lzd %o0,%o0
nop
stq %f0, [%o1]
stq %f4, [%o1+16]
.end
乍一看,这个问题似乎已经消失了。在 32 个线程之后性能显着下降,所以这是我坚持使用的数字(至少目前)并且使用当前代码我似乎得到了正确的结果。我可能只是掩盖了这个问题,所以我将进行进一步的测试。
更新 2:
我找了一些时间回到这个问题上,并且我能够从 T4 中获得不错的结果(一分钟内达到数千万个哈希值)。
我所做的更改是:
- 使用汇编代替内联汇编
- 因为函数是叶函数,所以我没有触及注册窗口
我将所有内容打包到库中并提供代码 here
最佳答案
不是 Spark 架构专家(我可能是错的)但这是我的猜测:
您的内联汇编代码将堆栈变量加载到一组特定的浮点寄存器中,以便能够调用 sha 汇编操作。
这对两个线程如何工作?对 ProcessChunk 的两次调用都将尝试将不同的输入值复制到完全相同的 CPU 寄存器中。
我通常的看法是,asm 代码中的 CPU 寄存器就像高级编程语言的“全局”变量。
您的系统有多少个内核?在每个核心/一组硬件寄存器都有一个线程之前,您可能还不错。但这也意味着代码的行为可能取决于线程在系统不同内核上的调度方式。
您知道系统在 CPU 核心上调度来自同一进程的线程时的行为吗?我的意思是:系统是否存储未调度线程的寄存器,就像在上下文切换中一样?
我要运行的测试是生成数量等于 N 个 CPU 核心的线程,然后使用 N+1 运行相同的测试(我在这里的假设是每个 CPU 核心都有一个浮点寄存器集)。
关于c++ - 多线程内联汇编,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21491052/