为了在PowerPC上寻找一个好的内存策略,我写了几个复制函数.使用具有高速缓存提示(dcb*)的Altivec或fp寄存器可以使大数据的简单字节复制循环的性能提高一倍.最初很满意的是,我定期记忆,看看它的比较...比我最好的速度快10倍!我无意重写memcpy,但我希望从中学习并加速几个简单的图像过滤器,这些过滤器花费大部分时间将像素移入和移出内存.
Shark分析显示它们的内部循环使用dcbt预取,有4个向量读取,然后是4个向量写入.在调整了我的最佳函数以便每次迭代运行64个字节之后,memcpy的性能优势仍然令人尴尬.我正在使用dcbz来释放带宽,Apple没有使用任何东西,但两个代码都倾向于对商店犹豫不决.
prefetch dcbt future dcbt distant future load stuff lvx image lvx image + 16 lvx image + 32 lvx image + 48 image += 64 prepare to store dcbz filtered dcbz filtered + 32 store stuff stvxl filtered stvxl filtered + 16 stvxl filtered + 32 stvxl filtered + 48 filtered += 64 repeat
有没有人对为什么非常相似的代码有如此戏剧性的性能差距有一些想法?我喜欢用真正的图像过滤器来腌制真正的图像过滤器!
附加信息:所有数据都是矢量对齐的.我正在制作图像的过滤副本,而不是替换原始图像.该代码在PowerPC G4,G5和Cell PPU上运行.Cell SPU版本已经非常快.
我正在尝试使用SIMD内在函数编写流压缩(获取数组并删除空元素).循环的每次迭代一次处理8个元素(SIMD宽度).
使用SSE内在函数,我可以使用_mm_shuffle_epi8()进行相当有效的操作,它执行16条表查找(收集并行计算术语).shuffle索引是预先计算的,并使用位掩码查找.
for (i = 0; i < n; i += 8)
{
v8n_Data = _mm_load_si128(&data[i]);
mask = _mm_movemask_epi8(&is_valid[i]) & 0xff; // is_valid is byte array
v8n_Compacted = _mm_shuffle_epi8(v16n_ShuffleIndices[mask]);
_mm_storeu_si128(&compacted[count], v8n_Compacted);
count += bitCount[mask];
}
Run Code Online (Sandbox Code Playgroud)
我的问题是现在我想为Altivec SIMD实现这个(不要问为什么 - 错误的商业决策).Altivec没有_mm_movemask_epi8()的等价物,这是一个关键因素.所以,我需要找到一种方法
模拟_mm_movemask_epi8() - 似乎很贵,有几个班次和OR
直接生成有效的shuffle指数 -
即,索引i将是未压缩数据中第i个有效元素的索引
element_valid: 0 0 1 0 1 0 0 1 0
gather_indices: x x x x x x 6 4 1
scatter_indices: 3 3 2 2 1 1 1 0 0
Run Code Online (Sandbox Code Playgroud)
串行执行此操作非常简单,但我需要它是并行(SIMD).使用前缀sum生成散列索引似乎很容易,但由于AltiVec和SSE都没有散点指令,我需要收集索引.收集索引是散射指数的反函数,但是如何并行获得?我知道在GPU编程的开创性时代,将散射转换为收集是一种常见的技术,但这两种方法中没有一种看似实用.
也许如果不坚持压缩保留元素顺序将允许更有效的实现?我可以放弃.
我在PowerPC上遭受GCC内联汇编.该程序编译良好-g2 -O3
,但无法编译-g3 -O0
.问题是,我需要在调试器下观察它,所以我需要没有优化的符号.
这是程序:
$ cat test.cxx
#include <altivec.h>
#undef vector
typedef __vector unsigned char uint8x16_p;
uint8x16_p VectorFastLoad8(const void* p)
{
long offset = 0;
uint8x16_p res;
__asm(" lxvd2x %x0, %1, %2 \n\t"
: "=wa" (res)
: "g" (p), "g" (offset/4), "Z" (*(const char (*)[16]) p));
return res;
}
Run Code Online (Sandbox Code Playgroud)
这是错误.(自使用内联汇编替换PowerPC vec_xl_be以来,该错误已经存在,但直到现在我才能忽略它).
$ g++ -g3 -O0 -mcpu=power8 test.cxx -c
/home/test/tmp/ccWvBTN4.s: Assembler messages:
/home/test/tmp/ccWvBTN4.s:31: Error: operand out of range (64 is not between 0 and 31) …
Run Code Online (Sandbox Code Playgroud) 我们在几个地方置换一个向量,我们需要使用vec_perm
内置的可识别0值.我们无法找到vec_zero()
或类似,所以我们想知道我们应该如何处理事情.
该代码目前使用两种策略.第一个策略是矢量加载:
__attribute__((aligned(16)))
static const uint8_t z[16] =
{ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 };
const uint8x16_p8 zero = vec_ld(0, z);
Run Code Online (Sandbox Code Playgroud)
第二个策略是使用我们打算使用的掩码的xor:
__attribute__((aligned(16)))
static const uint8_t m[16] =
{ 15,14,13,12, 11,10,9,8, 7,6,5,4, 3,2,1,0 };
const uint8x16_p8 mask = vec_ld(0, m);
const uint8x16_p8 zero = vec_xor(mask, mask);
Run Code Online (Sandbox Code Playgroud)
我们还没有开始基准测试,所以我们不知道一个是否优于另一个.第一种策略使用VMX负载,但价格昂贵.第二种策略避免了负载,但引入了数据依赖性.
我们如何获得零VSX值?
我正在使用带有内核加密的PowerPC机器.我无法使用内置函数将AES密钥扩展从big endian移植到little endian.大端工作,但小端不工作.
下面的算法是IBM博客文章中提供的代码片段.我想我的问题与以下第2行有关:
typedef __vector unsigned char uint8x16_p8;
uint8x64_p8 r0 = {0};
r3 = vec_perm(r1, r1, r5); /* line 1 */
r6 = vec_sld(r0, r1, 12); /* line 2 */
r3 = vcipherlast(r3, r4); /* line 3 */
r1 = vec_xor(r1, r6); /* line 4 */
r6 = vec_sld(r0, r6, 12); /* line 5 */
r1 = vec_xor(r1, r6); /* line 6 */
r6 = vec_sld(r0, r6, 12); /* line 7 */
r1 = vec_xor(r1, r6); /* …
Run Code Online (Sandbox Code Playgroud) 我从教程中知道未对齐加载和存储它可能看起来像:
//Load a vector from an unaligned location in memory
__vector unsigned char LoadUnaligned(const unsigned char * src )
{
__vector unsigned char permuteVector = vec_lvsl(0, src);
__vector unsigned char low = vec_ld( 0, src);
__vector unsigned char high = vec_ld( 16, src);
return vec_perm( low, high, permuteVector);
}
//Store a vector to an unaligned location in memory
void StoreUnaligned(__vector unsigned char v, __vector unsigned char * dst)
{
//Load the surrounding area
__vector unsigned char low = …
Run Code Online (Sandbox Code Playgroud) 我在 ASM 方面的经验极其有限,在 SIMD 方面的经验就更少了。
但碰巧我有以下 MMX/SSE 优化代码,我想将其移植到 AltiVec 指令以在 PPC/Cell 处理器上使用。
这可能是一个很大的问题..尽管只有几行代码,但我在尝试弄清楚这里发生的事情时遇到了无穷无尽的麻烦。
原函数:
static inline int convolve(const short *a, const short *b, int n)
{
int out = 0;
union {
__m64 m64;
int i32[2];
} tmp;
tmp.i32[0] = 0;
tmp.i32[1] = 0;
while (n >= 4) {
tmp.m64 = _mm_add_pi32(tmp.m64,
_mm_madd_pi16(*((__m64 *)a),
*((__m64 *)b)));
a += 4;
b += 4;
n -= 4;
}
out = tmp.i32[0] + tmp.i32[1];
_mm_empty();
while (n --)
out += (*(a++)) * (*(b++)); …
Run Code Online (Sandbox Code Playgroud) 在gcc 4.1.2中,vec_ld()
在CPU MPC74XX板上不能正常工作。
float temp[4];
__vector float Src;
Src = (__vector float)vec_ld(0, temp);
Run Code Online (Sandbox Code Playgroud)
但是,如果float变量对齐到16个字节,则它可以正常工作:
float temp[4] __attribute__((aligned(16)));
Run Code Online (Sandbox Code Playgroud)
这是设计使然吗?