【发布时间】:2015-07-31 11:51:46
【问题描述】:
我正在修改 RNNLM 一个神经网络来研究语言模型。但是考虑到我的语料库的大小,它的运行速度真的很慢。我尝试优化 matrix*vector 例程(对于小数据集,它占总时间的 63%(我希望它在更大的数据集上会更糟))。现在我被内在函数困住了。
for (b=0; b<(to-from)/8; b++)
{
val = _mm256_setzero_ps();
for (a=from2; a<to2; a++)
{
t1 = _mm256_set1_ps (srcvec.ac[a]);
t2 = _mm256_load_ps(&(srcmatrix[a+(b*8+from+0)*matrix_width].weight));
//val =_mm256_fmadd_ps (t1, t2, t3)
t3 = _mm256_mul_ps(t1,t2);
val = _mm256_add_ps (val, t3);
}
t4 = _mm256_load_ps(&(dest.ac[b*8+from+0]));
t4 = _mm256_add_ps(t4,val);
_mm256_store_ps (&(dest.ac[b*8+from+0]), t4);
}
这个例子崩溃了:
_mm256_store_ps (&(dest.ac[b*8+from+0]), t4);
但是如果我改成
_mm256_storeu_ps (&(dest.ac[b*8+from+0]), t4);
(我想用 u 表示未对齐)一切都按预期工作。我的问题是:为什么 load 会起作用(而如果数据未对齐,则不应该这样做)而 store 不会。 (而且两者都在同一个地址上运行)。
dest.ac 已使用
分配void *_aligned_calloc(size_t nelem, size_t elsize, size_t alignment=64)
{
size_t max_size = (size_t)-1;
// Watch out for overflow
if(elsize == 0 || nelem >= max_size/elsize)
return NULL;
size_t size = nelem * elsize;
void *memory = _mm_malloc(size+64, alignment);
if(memory != NULL)
memset(memory, 0, size);
return memory;
}
它至少有 50 个元素长。 (顺便说一句,VS2012 我有一个关于一些随机分配的非法指令,所以我使用 linux。)
提前谢谢你, 阿坎图斯。
【问题讨论】:
-
from的值是多少?_mm256_load_psintrinsic 是否有可能实际实现为 2 128 位加载? -
崩溃时from的值为891. &(dest.ac[b*8+from+0]) = 0x957e6c。所以表格中间有一个访问,这个没有对齐。
-
有了这个值,负载工作更令人惊讶。您是否检查过您实际上加载了正确的值(对于 from 的值)?
-
您应该检查生成的 ASM,看看它是否每次通过内部循环都重新计算数组索引。如果是这样,请将恒定的部分拉出循环。通常效果很好的方法是让外部循环将
b增加8 * matrix_width,而不是在索引表达式中将b * 8相乘。当您不以这种方式编写循环时,gcc 似乎不擅长将循环转换为仅维护循环计数器的缩放版本。 -
另外,
set1内部函数可能很慢。小心对待他们。希望编译为vbroadcastss ymm, [mem]。如果您可以安排您的数据结构在内循环中不需要它,那可能会更快。只是交换内部/外部循环,因此相同的srcvec用于所有b值,因为必须从srcmatrix收集非连续数据,所以会更慢。vbroadcastss是 2 微秒,来自内存的 5 个周期延迟(在 Haswell 上)。使用 128 位目标而不是 256 可减少 1 个周期。吞吐量为每个周期 1(只能在 SnB/IvB/HSW 上的端口 5 上运行)。