继续我的第一个问题,我试图优化通过VTune分析64位C程序发现的内存热点。
特别是,我想找到最快的方法来测试一个128字节的内存块是否包含全零。您可以为内存块假定任何所需的内存对齐方式;我使用64字节对齐。
我使用的是一台配备英特尔Ivy Bridge Core i7 3770处理器、32 GB内存和免费版微软vs2010 C编译器的电脑。
我的第一次尝试是:
const char* bytevecM; // 4 GB block of memory, 64-byte aligned
size_t* psz; // size_t is 64-bits
// ...
// "m7 & 0xffffff80" selects the 128 byte block to test for all zeros
psz = (size_t*)&bytevecM[(unsigned int)m7 & 0xffffff80];
if (psz[0] == 0 && psz[1] == 0
&& psz[2] == 0 && psz[3] == 0
&& psz[4] == 0 && psz[5] == 0
&& psz[6] == 0 && psz[7] == 0
&& psz[8] == 0 && psz[9] == 0
&& psz[10] == 0 && psz[11] == 0
&& psz[12] == 0 && psz[13] == 0
&& psz[14] == 0 && psz[15] == 0) continue;
// ...
相应程序集的VTune评测如下:
cmp qword ptr [rax], 0x0 0.171s
jnz 0x14000222 42.426s
cmp qword ptr [rax+0x8], 0x0 0.498s
jnz 0x14000222 0.358s
cmp qword ptr [rax+0x10], 0x0 0.124s
jnz 0x14000222 0.031s
cmp qword ptr [rax+0x18], 0x0 0.171s
jnz 0x14000222 0.031s
cmp qword ptr [rax+0x20], 0x0 0.233s
jnz 0x14000222 0.560s
cmp qword ptr [rax+0x28], 0x0 0.498s
jnz 0x14000222 0.358s
cmp qword ptr [rax+0x30], 0x0 0.140s
jnz 0x14000222
cmp qword ptr [rax+0x38], 0x0 0.124s
jnz 0x14000222
cmp qword ptr [rax+0x40], 0x0 0.156s
jnz 0x14000222 2.550s
cmp qword ptr [rax+0x48], 0x0 0.109s
jnz 0x14000222 0.124s
cmp qword ptr [rax+0x50], 0x0 0.078s
jnz 0x14000222 0.016s
cmp qword ptr [rax+0x58], 0x0 0.078s
jnz 0x14000222 0.062s
cmp qword ptr [rax+0x60], 0x0 0.093s
jnz 0x14000222 0.467s
cmp qword ptr [rax+0x68], 0x0 0.047s
jnz 0x14000222 0.016s
cmp qword ptr [rax+0x70], 0x0 0.109s
jnz 0x14000222 0.047s
cmp qword ptr [rax+0x78], 0x0 0.093s
jnz 0x14000222 0.016s
我能够通过Intel instrinsic改进这一点:
const char* bytevecM; // 4 GB block of memory
__m128i* psz; // __m128i is 128-bits
__m128i one = _mm_set1_epi32(0xffffffff); // all bits one
// ...
psz = (__m128i*)&bytevecM[(unsigned int)m7 & 0xffffff80];
if (_mm_testz_si128(psz[0], one) && _mm_testz_si128(psz[1], one)
&& _mm_testz_si128(psz[2], one) && _mm_testz_si128(psz[3], one)
&& _mm_testz_si128(psz[4], one) && _mm_testz_si128(psz[5], one)
&& _mm_testz_si128(psz[6], one) && _mm_testz_si128(psz[7], one)) continue;
// ...
相应程序集的VTune评测如下:
movdqa xmm0, xmmword ptr [rax] 0.218s
ptest xmm0, xmm2 35.425s
jnz 0x14000ddd 0.700s
movdqa xmm0, xmmword ptr [rax+0x10] 0.124s
ptest xmm0, xmm2 0.078s
jnz 0x14000ddd 0.218s
movdqa xmm0, xmmword ptr [rax+0x20] 0.155s
ptest xmm0, xmm2 0.498s
jnz 0x14000ddd 0.296s
movdqa xmm0, xmmword ptr [rax+0x30] 0.187s
ptest xmm0, xmm2 0.031s
jnz 0x14000ddd
movdqa xmm0, xmmword ptr [rax+0x40] 0.093s
ptest xmm0, xmm2 2.162s
jnz 0x14000ddd 0.280s
movdqa xmm0, xmmword ptr [rax+0x50] 0.109s
ptest xmm0, xmm2 0.031s
jnz 0x14000ddd 0.124s
movdqa xmm0, xmmword ptr [rax+0x60] 0.109s
ptest xmm0, xmm2 0.404s
jnz 0x14000ddd 0.124s
movdqa xmm0, xmmword ptr [rax+0x70] 0.093s
ptest xmm0, xmm2 0.078s
jnz 0x14000ddd 0.016s
正如您所看到的,汇编指令更少,而且这个版本在计时测试中被进一步证明更快。
由于我在英特尔SSE/AVX指令领域相当薄弱,我欢迎关于如何更好地利用它们来加速代码的建议。
虽然我搜索了数百个可用的直觉,但我可能错过了理想的。特别是,我无法有效地雇用_mm_cmpeq_epi64();我寻找这个内在的“不相等”版本(似乎更适合这个问题),但结果很干。虽然下面的代码“有效”:
if (_mm_testz_si128(_mm_andnot_si128(_mm_cmpeq_epi64(psz[7], _mm_andnot_si128(_mm_cmpeq_epi64(psz[6], _mm_andnot_si128(_mm_cmpeq_epi64(psz[5], _mm_andnot_si128(_mm_cmpeq_epi64(psz[4], _mm_andnot_si128(_mm_cmpeq_epi64(psz[3], _mm_andnot_si128(_mm_cmpeq_epi64(psz[2], _mm_andnot_si128(_mm_cmpeq_epi64(psz[1], _mm_andnot_si128(_mm_cmpeq_epi64(psz[0], zero), one)), one)), one)), one)), one)), one)), one)), one), one)) continue;
它几乎不可读,而且(毫不奇怪)被证明比上面给出的两个版本慢得多。我确信一定有一种更优雅的方式来使用_mm_cmpeq_epi64(),并欢迎关于如何实现的建议。
除了使用C语言的内部函数外,针对这个问题的原始英特尔汇编语言解决方案也很受欢迎。
当128字节块的98%都是零时,平均每4K不到一个非零字节。对于稀疏数组,您尝试过将其存储为稀疏数组吗?您将节省大量内存和随之而来的缓存未命中延迟;如果简单的std::映射结果更快,我不会感到惊讶。
不好意思回答帖子,我没有足够的口碑来评论。< br >如果使用以下内容作为测试,会发生什么情况?
if( (psz[0] | psz[1] | psz[2] | psz[3] |
psz[4] | psz[5] | psz[6] | psz[7] |
psz[8] | psz[9] | psz[10] | psz[11] |
psz[12] | psz[13] | psz[14] | psz[15] ) == 0) continue;
不幸的是,我没有一个64位的系统来编译它,我不熟悉编译器对c代码的具体操作,但在我看来,二进制或比单个==比较更快。我也不知道什么是英特尔内部函数,但可能以与您已经做过的类似的方式优化上述代码
我希望我的回答有帮助
马斯
正如其他人所指出的,主要问题是您正在检查的128字节数据缺少数据高速缓存和/或TLB,并转到DRAM,这很慢。VTune告诉你这一点
cmp qword ptr [rax], 0x0 0.171s
jnz 0x14000222 42.426s
你还有另一个更小的热点
cmp qword ptr [rax+0x40], 0x0 0.156s
jnz 0x14000222 2.550s
占JNZ指令的42.4±2.5秒实际上是由之前的内存加载导致的停顿...在您分析程序的过程中,处理器总共有45秒钟无所事事...在等DRAM。
你可能会问第二个热点到底是怎么回事。嗯,您访问的是128字节,高速缓存行是64字节,CPU在读取前64字节时就开始为您预取...但是您没有对前64个字节做足够的工作来完全覆盖进入内存的延迟。
Ivy Bridge的内存带宽非常高(这取决于您的系统,但我猜超过10 GB/秒)。您的内存块是4GB,如果您按顺序访问它并让CPU提前为您预取数据,您应该能够在不到1秒的时间内通过它。
我猜您是通过以不连续的方式访问128字节的块来阻碍CPU数据预取器。
将您的访问模式更改为顺序,您会惊讶于它的运行速度有多快。然后,您可以担心下一个优化级别,这将确保分支预测运行良好。
要考虑的另一件事是TLB未命中
。这些成本很高,尤其是在 64 位系统中。与其使用 4KB 页面,不如考虑使用 2MB 的大页面
。有关以下各项的 Windows 支持,请参阅此链接:大页面支持 (Windows)
如果您必须以某种随机方式访问4GB数据,但您提前知道m7
值的序列(您的索引),那么您可以在使用之前显式地管道
获取内存(它需要比您使用它的时间提前100个CPU周期才能有效)。见
以下是一些关于内存优化的链接
每个程序员都应该知道的关于内存的知识 作者:Ulrich Drepper
http://www.akkadia.org/drepper/cpumemory.pdf
机器架构:您的编程语言从未告诉过您的事情,Herb Sutter
http://www.gotw.ca/publications/concurrency-ddj.htm
http://nwcpp.org/static/talks/2007/Machine_Architecture_-_NWCPP.pdf
http://video.google.com/videoplay?docid=-4714369049736584770#
问题内容: 我有一个,并且想知道最快的方法是检查所有值是否均为零? 有没有比做更快的方法: 问题答案: 我先将所有字节加总后就重写了这个答案,但是这是不正确的,因为Java已经对字节进行了签名,因此我需要or。 另外,我已将JVM预热更改为正确。 最好的选择实际上是简单地遍历所有值。 我想您有三种主要选择: 或所有元素并检查总和。 进行无分支比较。 与分支进行比较。 我不知道使用Java添加字节的
因此,每个测试都有3+1=4个周期的延迟。 其中一些可以通过在、等之间交替并行运行。 但它仍然相当慢。 有没有更快的方法来实现这一点? 我需要在一行中测试8个XMM/YMM寄存器。一字节位图中每个寄存器1位。
但应返回false 还有:我的代码高效吗?我觉得这是一个更容易的方法。如有任何帮助,不胜感激
问题内容: 我需要确定一个字符串是否包含我定义的自定义集中的任何字符。 您可以使用rangeOfString确定一个字符串是否包含另一个字符串。如果您一次测试一个字符,那么这当然也适用于字符。 我想知道什么是最好的方法。 问题答案: 您可以创建一个包含自定义字符集的,然后针对此字符集测试成员资格: 斯威夫特3: 对于不区分大小写的比较,请使用 (假设字符集仅包含小写字母)。 斯威夫特2: 斯威夫特
问题内容: 基本上,我大约有1,000,000个字符串,对于每个请求,我都必须检查一个String是否属于列表。 我担心性能,最好的方法是什么??哈希? 问题答案: 最好的选择是使用并通过方法检查集合中是否存在字符串。建立HashSet可以通过使用Object方法和进行快速访问。状态的Javadoc : 此类为基本操作(添加,删除,包含和调整大小)提供了恒定的时间性能, HashSet 将对象存储
我在内存中有大量64位值。不幸的是,它们可能无法与64位地址对齐。我的目标是更改所有这些值的endianess,即交换/反转它们的字节。 我知道bswap指令交换32位或64位寄存器的字节。但由于它需要一个register参数,我无法将内存地址传递给它。当然,我可以先将内存加载到寄存器中,然后交换,然后再写回: 但考虑到地址可能未对齐,这是否正确? 另一种可能性是手动进行交换: 这显然是更多的指示
在空手道中,我希望有一个模式变量,它是响应数据的超集,这样我就可以用相同的模式测试多个请求。 这对于GraphQL应该特别有用,因为请求本身定义了返回的字段。 预期模式: 回答数据: 在本例中,响应返回的所有键。数据应该在架构中,但架构中的任何键都不在响应中。数据应该被忽略。 在空手道中有没有办法做到这一点,或者有没有计划在将来增加这一功能? 编辑:更新了示例,因为唯一遗漏的属性是一个可为空的属性
我想找到一种最快的方法来检查一个文件是否存在于标准C++11、C++或C中。我有数千个文件,在对它们进行操作之前,我需要检查它们是否全部存在。在下面的函数中,我可以写什么来代替?