这是我简单的提示功能:
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int check ( int a , int n[3] )
{
for ( int i = 0 ; i < 3 ; i++ )
{
if ( a == n[i] )
{
return 1 ;
}
}
return 0 ;
}
int main ()
{
//PRE-PROGRAM DECLARATIONS
int n[3] , a[3] , input , repition ;
repition = 0 ;
srand(time(NULL)) ;
n[0] = rand() % 10 ;
n[1] = rand() % 10 ;
n[2] = rand() % 10 ;
//TESTING STUFF GOES HERE
//cout << n[0] << n[1] << n[2] << endl ;
//OUTPUT, INPUT, and PROCESSING
cout << "Enter your Guess Here : " ;
cin >> input ;
a[0] = input / 100 ;
a[1] = ( input % 100 ) / 10 ;
a[2] = ( input % 10 ) ;
//CONDITIONS
if ( a[0] == n[0] && a[1] == n[1] && a[2] == n[2] )
{
repition = 4 ;
}
else
{
for ( int i = 0 ; i < 3 ; i++ )
{
repition = repition + check( n[i] , a ) ;
}
}
//OUTPUT
switch (repition)
{
case 4:
cout << "All digits correct - Exact Order!\n" ;
break ;
case 3:
cout << "All digits correct - Different Order!\n" ;
break ;
case 2:
cout << "Two digits correct!\n" ;
break ;
case 1:
cout << "Only One digit Correct!\n" ;
break ;
case 0:
cout << "Sorry Try Again!\n" ;
break ;
default :
cout << "Something is terrible!\n" ;
}
return 0;
}
我已经在static void blit8(unsigned char* dest, unsigned char* src)
{
byte i;
for (i = 0; i < 8; ++i) {
if (*src != 0) {
*dest = *src;
}
++dest;
++src;
}
}
,并且-O3
已被内联。 blit8
(gcc)在这里无效。也没有以任何不同的方式递增指针,也没有使用其他数字作为透明度,也没有使用restrict
的其他类型……我什至尝试传递1字节的位掩码并检查它,而不是取消引用i
。将src
的限制提高到16,似乎可以提供非常较小的加速(〜4-6%),但是我使用的是8字节而不是16字节块。
我的瓶颈?实际上不知道,我不认为这是高速缓存行,因为我的未命中率很低(?),i
(高速缓存行大小)在更改周围事物时没有特殊意义。但是我也不认为这是内存速度(因为64
更快,更多。)
memcpy
谈到cg_annotate
(无内联):
blit8
常规Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw file:function
3,747,585,536 62 1 1,252,173,824 2,097,653 0 674,067,968 0 0 ppu.c:blit8.constprop.0
输出(带有内联):
cachegrind
I refs: 6,446,979,546
I1 misses: 184,752
LLi misses: 22,549
I1 miss rate: 0.00%
LLi miss rate: 0.00%
D refs: 2,150,502,425 (1,497,875,135 rd + 652,627,290 wr)
D1 misses: 17,121,968 ( 2,761,307 rd + 14,360,661 wr)
LLd misses: 253,685 ( 70,802 rd + 182,883 wr)
D1 miss rate: 0.8% ( 0.2% + 2.2% )
LLd miss rate: 0.0% ( 0.0% + 0.0% )
LL refs: 17,306,720 ( 2,946,059 rd + 14,360,661 wr)
LL misses: 276,234 ( 93,351 rd + 182,883 wr)
LL miss rate: 0.0% ( 0.0% + 0.0% )
D1未命中率?对我来说听起来很低。
对我来说,最有趣的是,删除0.8%
-check(在功能上与0
相同)可以使<1%加速,即使:
memcpy
快25%。我希望尽可能接近原始memcpy
的速度,同时保持颜色memcpy
透明。
据我所知,问题是没有向量指令支持条件,但我需要保留0
,其中dest
是src
。有什么[快速]可以像0
一样,但是在字节级别上起作用?
我在有扩展名或告诉CPU不要缓存某些数据之前阅读过文件,但我找不到它。我的想法是不直接从OR
中读取,而仅从其中写入src
,并确保不对其进行缓存。然后只需从位掩码中读取以检查透明度。 我只是不知道该怎么做。那还有可能更不用说快速吗?我也不知道,所以为什么要问这个问题。
我希望有一个技巧,介绍如何仅使用C或某些gcc扩展就可以更快地完成开发,但是如果x86汇编是唯一的方法,那就这样吧。帮助我理解我的实际瓶颈(因为我对结果感到困惑)也会有所帮助。
答案 0 :(得分:2)
您没有提到是否使用GCC,但我们假设是。 如果涉及到循环内的条件,那么GCC会很挑剔-这就是您的示例无法向量化的原因。
所以这段代码:
void blit8(unsigned char* dest, unsigned char* src)
{
char i;
for (i = 0; i < 8; ++i) {
if (*src != 0) {
*dest = *src;
}
++dest;
++src;
}
}
最终显示为:
blit8:
movzx eax, BYTE PTR [rsi]
test al, al
je .L5
mov BYTE PTR [rdi], al
.L5:
movzx eax, BYTE PTR [rsi+1]
test al, al
je .L6
mov BYTE PTR [rdi+1], al
.L6:
movzx eax, BYTE PTR [rsi+2]
test al, al
je .L7
mov BYTE PTR [rdi+2], al
.L7:
movzx eax, BYTE PTR [rsi+3]
test al, al
je .L8
mov BYTE PTR [rdi+3], al
.L8:
movzx eax, BYTE PTR [rsi+4]
test al, al
je .L9
mov BYTE PTR [rdi+4], al
.L9:
movzx eax, BYTE PTR [rsi+5]
test al, al
je .L10
mov BYTE PTR [rdi+5], al
.L10:
movzx eax, BYTE PTR [rsi+6]
test al, al
je .L11
mov BYTE PTR [rdi+6], al
.L11:
movzx eax, BYTE PTR [rsi+7]
test al, al
je .L37
mov BYTE PTR [rdi+7], al
.L37:
ret
它已由编译器展开,但仍可用于单个字节。
但是在这种情况下,有一种技巧很常见-代替if(cond)使用三元运算符。这将解决一个问题。 但是还有一个-GCC拒绝向量化短的小块-在此示例中为8个字节。因此,让我们使用另一个技巧-在更大的块上进行计算,但忽略其中的一部分。
这是我的例子:
void blit8(unsigned char* dest, unsigned char* src)
{
int i;
unsigned char temp_dest[16];
unsigned char temp_src[16];
for (i = 0; i < 8; ++i) temp_dest[i] = dest[i];
for (i = 0; i < 8; ++i) temp_src[i] = src[i];
for (i = 0; i < 16; ++i)
{
temp_dest[i] = (temp_src[i] != 0) ? temp_src[i] : temp_dest[i];
}
for (i = 0; i < 8; ++i) dest[i] = temp_dest[i];
}
和相应的程序集:
blit8:
mov rax, QWORD PTR [rdi]
vpxor xmm0, xmm0, xmm0
mov QWORD PTR [rsp-40], rax
mov rax, QWORD PTR [rsi]
mov QWORD PTR [rsp-24], rax
vmovdqa xmm1, XMMWORD PTR [rsp-24]
vpcmpeqb xmm0, xmm0, XMMWORD PTR [rsp-24]
vpblendvb xmm0, xmm1, XMMWORD PTR [rsp-40], xmm0
vmovq QWORD PTR [rdi], xmm0
ret
注意: 我没有进行基准测试-只是证明可以通过使用适当的编码规则和技巧来生成SIMD代码;)
答案 1 :(得分:1)
如果您的编译器/体系结构支持vector extensions(例如clang和gcc),则可以使用以下方法:
//This may compile to awful code on x86_64 b/c mmx is slow (its fine on arm64)
void blit8(void* dest, void* src){
typedef __UINT8_TYPE__ u8x8 __attribute__ ((__vector_size__ (8), __may_alias__));
u8x8 *dp = dest, d = *dp, *sp = src, s = *sp, cmp;
cmp = s == (u8x8){0};
d &= cmp;
*dp = s|d;
}
//This may compile to better code on x86_64 - worse on arm64
void blit8v(void* dest, void* src){
typedef __UINT8_TYPE__ u8x16 __attribute__ ((__vector_size__ (16), __may_alias__));
typedef __UINT64_TYPE__ u64, u64x2 __attribute__ ((__vector_size__ (16), __may_alias__));
u8x16 *dp = dest, d = *dp, *sp = src, s = *sp, cmp;
cmp = s == (u8x16){0};
d &= cmp;
d |= s;
*(u64*)dest = ((u64x2)d)[0];
}
//This one is fine on both arm and x86, but 16 bytes vs. 8
void blit16(void* dest, void* src){
typedef __UINT8_TYPE__ u8x16 __attribute__ ((__vector_size__ (16), __may_alias__));
u8x16 *dp = dest, *sp = src, d = *dp, s = *sp, cmp;
cmp = s == (u8x16){0};
*dp = s|(d & cmp);
}
直接编译为:
blit8:
ldr d1, [x1]
ldr d2, [x0]
cmeq v0.8b, v1.8b, #0
and v0.8b, v0.8b, v2.8b
orr v0.8b, v0.8b, v1.8b
str d0, [x0]
ret
blit16:
ldr q1, [x1]
ldr q2, [x0]
cmeq v0.16b, v1.16b, #0
and v0.16b, v0.16b, v2.16b
orr v0.16b, v0.16b, v1.16b
str q0, [x0]
ret
在x86_64上:
blit8v: # @blit8v
movdqa xmm0, xmmword ptr [rsi]
pxor xmm1, xmm1
pcmpeqb xmm1, xmm0
pand xmm1, xmmword ptr [rdi]
por xmm1, xmm0
movq qword ptr [rdi], xmm1
ret
blit16: # @blit16
movdqa xmm0, xmmword ptr [rsi]
pxor xmm1, xmm1
pcmpeqb xmm1, xmm0
pand xmm1, xmmword ptr [rdi]
por xmm1, xmm0
movdqa xmmword ptr [rdi], xmm1
ret