POPPUR爱换

 找回密码
 注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

搜索
楼主: 紫色
打印 上一主题 下一主题

那位编程举个例子, 把sse的"威力"释放一下?

[复制链接]
21#
发表于 2007-9-29 22:49 | 只看该作者
原帖由 acqwer 于 2007-9-29 22:46 发表


http://en.wikipedia.org/wiki/AMD64

The original AMD64 architecture adopted Intel's SSE and SSE2 as core instructions.SSE3 instructions were added in April 2005. SSE2 replaces the x87instru ...

1) "the latter"是多久
2) 虽然SSE可能和x87指令一样快,但是我说的"不是所有问题都可以用SIMD模式解决"也没啥不对吧,至少很多问题在CPU上跑,将远达不到峰值吞吐量
回复 支持 反对

使用道具 举报

22#
发表于 2007-9-29 22:50 | 只看该作者
举例子是可以的,但就怕你看不懂。
举例详解
下面的程序是一个矩阵相乘的函数。在三维图形空间变换中,要用到4乘4的浮点矩阵,
而矩阵相乘的运算是很常用的。下面的函数的参数都是4乘4的浮点矩阵。写成这种形式
是为了保持比较强的伸缩性。
void MatMul_cpp(float *dest, float *m1, float *m2)
{
    for(int i = 0; i < 4; i ++)
    {
        for(int j = 0; j < 4; j ++)
        {
            dest[i*4+j] =
                m1[i*4+0]*m2[0*4+j] +
                m1[i*4+1]*m2[1*4+j] +
                m1[i*4+2]*m2[2*4+j] +
                m1[i*4+3]*m2[3*4+j] ;
        }
    }
}
VC的优化能力是很强的,象上面这样的比较常规的算法,你很难做出比它快得多的代码
。不过使用SSE以后就不一样了。下面是一个汇编函数,使用SSE 指令进行计算。注意,这个函数只能运行于32位的环境中。“.xmm”指示编译器使用SSE指令集进行编译。
函数的C语言原型是这样的:
extern "C"
{
void __stdcall MatMul_xmm(float *dest, float *m1, float *m2);
}
对于一些不太常用汇编语言编程的朋友来说,下面的程序可能比较难于理解。我将对一些常识性的东西做一下简单介绍。
在C语言中,代码段都是以“_TEXT”作为段名的。“use32”告诉编译器将代码编译为32位。
有些人看到“_MatMul_xmm@12”这个函数名以后可能会产生疑问。其实这只是遵循了VC所采用的命名规范。在VC中,所有标志为“__stdcall” 调用的,采用“C”链接的函数都要加下划线作为前缀,并且加上“@N”作为后缀,其中,“N”为参数的字节数。注意,上面的函数是采用“C”链接的,如果是“C++”链接,命名规范就太复杂了。如果你使用的是C++ Builder,命名规范就十分简单了,照搬函数名就行了。不同的调用规范将采用不同的命名方法,即使对相同的调用规范,不同的编译器也不一定兼容。有一种调用格式是每一个C++编译器都支持并且兼容的,那就是“__cdecl”。
各种调用格式所采用的堆栈操作也不太一样。使用“__stdcall”时,参数从右向左依次入栈,参数的弹出需要函数自己来处理。这种做法和“__cdecl” 调用方式不太一样,“__cdecl”的参数弹出需要调用者来处理。现在很流行的一种调用格式是“__fastcall”,也就是寄存器调用。这种调用方式通过寄存器“EAX”,“ECX”,“EDX”传递参数,不过很可惜,这种调用也不是在各个编译器中兼容的。Inprise在C++ Builder中提供了一个关键字“__msfastcall” 用来和微软兼容,如果你采用这种调用规范就可以在多个编译器中正常调用了。不过还有一件事让人很受打击,VC没有对“__fastcall”提供很好的优化,使用这种调用反而会降低效率。并不是所有的寄存器都能够随意使用的,多数32位寄存器都要先保存的。你可以不必保存的32位寄存器只有三个----“EAX”,“ECX”,“EDX”,其它的就只好“PUSH”,“POP”了。另外,浮点堆栈寄存器是不必保存的;MMX 寄存器和浮点堆栈共享,也是不必保存的;XMM寄存器不必保存。
很多SSE指令都会加上“ps”或“ss”后缀。“ps”表示“Packed Single-FP”,即打包
的浮点数,带这种后缀的指令通常是一次性对四个数进行操作的。“ss” 表示“Scalar Single-FP”,带这种后缀的指令通常是对最低位的单精度数进行操作的。
下面这个汇编函数是一行一行计算的,咱们先用类似于C的语法简述一下第一行的计算过
程:
    xmm0 = m1[0],m1[0],m1[0],m1[0];
    xmm1 = m1[1],m1[1],m1[1],m1[1];
    xmm2 = m1[2],m1[2],m1[2],m1[2];
    xmm3 = m1[3],m1[3],m1[3],m1[3];
    xmm4 = m2[0],m2[1],m2[2],m2[3];
    xmm5 = m2[4],m2[5],m2[6],m2[7];
    xmm6 = m2[8],m2[9],m2[10],m2[11];
    xmm7 = m2[12],m2[13],m2[14],m2[15];
    xmm0 *= xmm4;
    xmm1 *= xmm5;
    xmm2 *= xmm6;
    xmm3 *= xmm7;
    xmm1 += xmm0;
    xmm2 += xmm1;
    xmm3 += xmm2;
    dst[0],dst[1],dst[2],dst[3] = xmm3;
上面的代码可读性还是比较好的,因为只进行了第一行的计算。实际运算中,为了增强并行度,为了减小指令的延迟,实际上是两行并行计算的。而且,运算过程并不是象算法描述那样写得那么有规律。
        .686p
        .xmm
        .model flat
_TEXT segment public use32 'CODE'
public _MatMul_xmm@12
_MatMul_xmm@12 proc
;;parameters
retaddress = 0
dst = retaddress+4
m1 = dst+4
m2 = m1+4
        mov          edx,     [esp+m1]
        mov          ecx,     [esp+m2]
        mov          eax,     [esp+dst]
        movss        xmm0,    [edx+16*0+4*0]   ;读入第一行的数据
        movaps       xmm4,    [ecx+16*0]
        movss        xmm1,    [edx+16*0+4*1]
        shufps       xmm0,    xmm0,    00h
        movaps       xmm5,    [ecx+16*1]
        movss        xmm2,    [edx+16*0+4*2]
        shufps       xmm1,    xmm1,    00h
        mulps        xmm0,    xmm4
        movaps       xmm6,    [ecx+16*2]
        mulps        xmm1,    xmm5
        movss        xmm3,    [edx+16*0+4*3]
        shufps       xmm2,    xmm2,    00h
        movaps       xmm7,    [ecx+16*3]
        shufps       xmm3,    xmm3,    00h
        mulps        xmm2,    xmm6
        addps        xmm1,    xmm0
        movss        xmm0,    [edx+16*1+4*0]   ;读入第二行的数据
        mulps        xmm3,    xmm7
        shufps       xmm0,    xmm0,    00h
        addps        xmm2,    xmm1
        movss        xmm1,    [edx+16*1+4*1]
        mulps        xmm0,    xmm4
        shufps       xmm1,    xmm1,    00h
        addps        xmm3,    xmm2
        movss        xmm2,    [edx+16*1+4*2]
        mulps        xmm1,    xmm5
        shufps       xmm2,    xmm2,    00h
        movaps       [eax+16*0],    xmm3
        movss        xmm3,    [edx+16*1+4*3]
        mulps        xmm2,    xmm6
        shufps       xmm3,    xmm3,    00h
        addps        xmm1,    xmm0
        movss        xmm0,    [edx+16*2+4*0]   ;读入第三行的数据
        mulps        xmm3,    xmm7
        shufps       xmm0,    xmm0,    00h
        addps        xmm2,    xmm1
        movss        xmm1,    [edx+16*2+4*1]
        mulps        xmm0,    xmm4
        shufps       xmm1,    xmm1,    00h
        addps        xmm3,    xmm2
        movss        xmm2,    [edx+16*2+4*2]
        mulps        xmm1,    xmm5
        shufps       xmm2,    xmm2,    00h
        movaps       [eax+16*1],    xmm3
        movss        xmm3,    [edx+16*2+4*3]
        mulps        xmm2,    xmm6
        shufps       xmm3,    xmm3,    00h
        addps        xmm1,    xmm0
        movss        xmm0,    [edx+16*3+4*0]   ;读入第四行的数据
        mulps        xmm3,    xmm7
        shufps       xmm0,    xmm0,    00h
        addps        xmm2,    xmm1
        movss        xmm1,    [edx+16*3+4*1]
        mulps        xmm0,    xmm4
        shufps       xmm1,    xmm1,    00h
        addps        xmm3,    xmm2
        movss        xmm2,    [edx+16*3+4*2]
        mulps        xmm1,    xmm5
        shufps       xmm2,    xmm2,    00h
        movaps       [eax+16*2],    xmm3
        movss        xmm3,    [edx+16*3+4*3]
        mulps        xmm2,    xmm6
        shufps       xmm3,    xmm3,    00h
        addps        xmm1,    xmm0
        mulps        xmm3,    xmm7
        addps        xmm2,    xmm1
        addps        xmm3,    xmm2
        movaps       [eax+16*3],    xmm3
        ret          12
_MatMul_xmm@12 endp
_TEXT ends
        end
回复 支持 反对

使用道具 举报

23#
发表于 2007-9-29 22:50 | 只看该作者
原帖由 Ricepig 于 2007-9-29 22:49 发表

1) "the latter"是多久

the latter 是后者,不是多久
这是几光年前学过的了(_(
回复 支持 反对

使用道具 举报

24#
发表于 2007-9-29 22:51 | 只看该作者
上面的代码几乎没有加什么注释,只是在读入每行第一个数据时作了标记。因为,SSE的指令可读性还是比较好的,除了要加上一些后缀以外,它们和普通的整数运算指令很相似。
一些关键性的指令有必要解释一下:
movss和movaps:
movss是将一个单精度数传输到xmm寄存器的低32位,而movaps则是一次性向寄存器中写入四个单精度数。也许有些人会认为movaps效率更高一些,其实并不一定是这样。从处理器执行单元列表中,你可以查到这些指令的延迟。如果都是从寄存器中读取数据,两个指令的延迟是一样的。如果是从内存中读取数据,movss只有一个时钟周期的延迟,而movaps却有四个时钟周期的延迟。
上面的汇编代码混合使用了这两条指令。那么,应该在什么时候选择哪一条指令呢?这要看你对数据的需求了。如果你希望能够尽快地使用数据,就应当首选movss,因为它几乎能够让你立即使用数据。如果你并不急于使用某些数据,只是想先把它读入寄存器,那么毫无疑问movaps是你的最佳选择。 movaps使用端口2读取数据,如果在它执行完毕之前你不去使用它的数据,这条指令的实际延迟就只有一个时钟周期。考虑到处理器能够在5个端口并行执行微操作,那么这条指令的延迟可能还不到一个时钟周期。从上面的代码中,你可以看到,每一条movaps指令和它的相关指令之间都至少插入了四条指令,这样可以基本上避免延迟。
虽然movss指令只有一个时钟周期的延迟,但是这也并不意味着你可以把这条指令和它的相关指令写在一起,因为这有可能会影响处理器的并行度。虽然 Pentium III有着强大的乱序执行的能力,可是这毕竟是不太保险的,还是自己动手,丰衣足食吧。
SHUFPS
这是一条可以将操作数打乱顺序的指令。这一条指令有很多种用法,它根据常量参数的不同执行不同的功能。本文中只使用了一种用法:
    shufps      xmmreg,  xmmreg,  00h
这条指令的作用是把某个寄存器的最低位的单精度数传输到该寄存器的其它三个部分。

在某些时候,shufps和unpcklps(或unpckhps)可以执行相同的功能。这时,推荐使用
shufps,因为这条指令有两个时钟周期的延迟。unpcklps和unpckhps 都是有三个时钟周
期的延迟。
ADDPS和MULPS
这两条指令是很重要的计算指令,有必要弄清楚它们的执行情况。
addps有4个时钟周期的延迟,mulps有5个时钟周期的延迟,我们应该根据这些数据考虑
清楚,究竟在它们的相关代码中插入多少条指令。
这两条指令都是每两个时钟周期才允许执行一次,如果你把相同的两条这样的指令写在一起,第二条指令就有可能被延误一个时钟周期。应该插入一些其它指令来掩盖这段延迟。
mulps在端口0执行,addps在端口1执行,如果你的代码把乘法和加法指令写在一起,它们会被分配到不同的端口并行执行,这比只有一条流水线的FPU要高效的多。
优化思路:
下面将解释一下上面代码的优化思路。
打乱指令
在算法描述中,各条操作写得非常有规律,但是在真正编程的时候却不是这样。为了保证流水线的流畅运作,就要把相关的代码分离开来,尽量避免或减轻指令的延迟。这样就要打乱指令,在两条相关指令之间插入一些其它的指令,同时也要考虑指令之间是否存在资源的竞争。
并行算法
多个数据并行计算是解决指令延迟问题的有效方法。我们不能傻傻地等待一条指令的计算结果,而是要在等待的过程中进行其它数据的计算。在上面程序的算法中,每当寄存器有了空闲,就马上从内存中读入新的数据,尽量保证有两组数据在寄存器中并行计算。
内存访问
访问内存的指令不要过于密集,这一方面可以减轻对带宽的需求,另一方面也会提高解码的效率。访问内存的指令至少有两个微操作,这样的指令只能每个时钟周期解码一条,而Pentium III的解码极限可是每个时钟周期三条指令啊。为了提高处理器的并行度,
有必要在内存访问指令上下功夫。在我的代码中,内存访问指令的排布还是比较有规律的,差不多是每隔三条指令访问一次内存。当然,在计算第一行数据时,因为要读取一些初始化的数据,内存访问比后面的代码要频繁。
灵活性
矩阵的运算是一行一行进行的,每一行数据只被读取一次。这就意味着,我们可以把运算结果保存在任何一个矩阵里,即保存在m1或者m2中,因为这两个矩阵中的数据已经不会被再次读取了,也就不用担心破坏数据。这种灵活性可以是我们轻而易举地完成矩阵左乘或者右乘的代码。在Direct3D中,空间变换是按照如下方式进行计算的:
在进行多次变换时,只要在原有的矩阵上右乘一个变换矩阵就可以了。下面的代码就是
这样的一个例子:
MatMul_xmm(m1, m1, m2);
如果使用高级语言来实现恐怕就要麻烦一些,你要使用一些中间变量,程序如下所示:

void MatMul_Right_cpp(float *dest, float *m)
{
    float tmp[16];
    MatMul_cpp(tmp, dest, m)
    memcpy(dest, tmp, 16*4);
}

要理解SSE的威力,必需了解一些关于CPU结构方面的知识,特别是寄存器,流水线,并行化和乱序执行等方面的知识,很明显,楼主对此一无所知。所以根本不可能理解SSE能够给程序带来什么样的性能提高,更别说什么释放simd的威力了。
回复 支持 反对

使用道具 举报

25#
发表于 2007-9-29 22:52 | 只看该作者
原帖由 boris_lee 于 2007-9-29 22:50 发表

the latter 是后者,不是多久
这是几光年前学过的了(_(

哦,圡了,嘿嘿
回复 支持 反对

使用道具 举报

26#
发表于 2007-9-29 22:54 | 只看该作者
:huh: 给bc看代码干什么,看得懂才怪
就看看广告宣传的文字就可以了:
不认识e文说一下哦
我可以义务用google翻译成中文w00t)
The 64-bit and 128-bit SIMD instructions—SSE, SSE2, SSE3, SSE4a instructions—should be used to encode floating-point and packed integer operations.
The SIMD instructions use a flat register file rather than the stack register file used by x87 floating-point instructions. This allows arbitrary sequences of operations to map more efficiently to the instruction set.
•AMD Family 10h processors with 128-bit multipliers and adders achieve better throughput using SSE, SSE2, SSE3, and SSE4a instructions. (Double precision throughput is 2× and single precision is 4× the throughput of x87.)
•SSE, SSE2, SSE3, and SSE4a instructions work well in both 32-bit and 64-bit threads.
In 64-bit mode, there are twice as many XMM registers available as in 32-bit mode, however, the number of x87 registers is the same in both 32-bit mode and 64-bit mode.
The SIMD instructions provide a theoretical single-precision peak throughput of four additions and four multiplications per clock cycle, whereas x87 instructions can only sustain one addition and one multiplication per clock cycle. The double-precision peak throughput of the SSE, SSE2, SSE3, and SSE4a instructions is two additions and two multiplications per clock cycle.


so,via fanboy,get out:lol:

[ 本帖最后由 Dr.BT 于 2007-9-29 22:58 编辑 ]
回复 支持 反对

使用道具 举报

27#
发表于 2007-9-29 22:57 | 只看该作者
原帖由 Intelife 于 2007-9-29 22:51 发表
上面的代码几乎没有加什么注释,只是在读入每行第一个数据时作了标记。因为,SSE的指令可读性还是比较好的,除了要加上一些后缀以外,它们和普通的整数运算指令很相似。
一些关键性的指令有必要解释一下:
mo ...

把SSE和流水线与乱续执行扯到一起,不太对吧,莫非普通指令和超标量与乱序没关系?
说并行和SIMD还有点谱
回复 支持 反对

使用道具 举报

28#
发表于 2007-9-29 22:59 | 只看该作者
我查了一下Intel优化手册,似乎SSE和X87的最大差距就在80位浮点运算了,不过这个手册时间有点早,现在的SSE能搞定80位浮点数不?
回复 支持 反对

使用道具 举报

29#
 楼主| 发表于 2007-9-29 23:03 | 只看该作者
原帖由 Intelife 于 2007-9-29 22:50 发表
举例子是可以的,但就怕你看不懂。
举例详解
下面的程序是一个矩阵相乘的函数。在三维图形空间变换中,要用到4乘4的浮点矩阵,
而矩阵相乘的运算是很常用的。下面的函数的参数都是4乘4的浮点矩阵。写成这种形 ...


欢迎。

中山大学bbs这篇我也是看过的。可以说为sse而定制的程序。

那就请给出运行时间和编译选项,让大家看看这种类型的程序结果吧。
回复 支持 反对

使用道具 举报

30#
发表于 2007-9-29 23:03 | 只看该作者
原帖由 Ricepig 于 2007-9-29 22:59 发表
我查了一下Intel优化手册,似乎SSE和X87的最大差距就在80位浮点运算了,不过这个手册时间有点早,现在的SSE能搞定80位浮点数不?

是128位和64位吧:huh:
80位sse2/3打x87不爽
80bit当128bit玩的)_)
回复 支持 反对

使用道具 举报

31#
发表于 2007-9-29 23:05 | 只看该作者
原帖由 紫色 于 2007-9-29 23:03 发表


欢迎。

中山大学bbs这篇我也是看过的。可以说为sse而定制的程序。

那就请给出运行时间和编译选项,让大家看看这种类型的程序结果吧。


最直观的例子,还是看看去年年底Intel多核编程大赛吧,里面的优化手册里面有好多资料,SSE对那个程序的性能提高确实大
回复 支持 反对

使用道具 举报

32#
 楼主| 发表于 2007-9-29 23:07 | 只看该作者
原帖由 Dr.BT 于 2007-9-29 22:54 发表
:huh: 给bc看代码干什么,看得懂才怪
就看看广告宣传的文字就可以了:
不认识e文说一下哦
我可以义务用google翻译成中文w00t)
The 64-bit and 128-bit SIMD instructions—SSE, SSE2, SSE3, SSE4a instruc ...


拜托,别来这个了。conXE反复强调,地球人也早就知道了:x87需要压栈。

拿出点本领出来,给出运行时间对比,证明单指令多数据的威力。
回复 支持 反对

使用道具 举报

33#
发表于 2007-9-29 23:08 | 只看该作者
原帖由 Dr.BT 于 2007-9-29 23:03 发表

是128位和64位吧:huh:
80位sse2/3打x87不爽
80bit当128bit玩的)_)

不是,当时手册上说SSE不能算80位浮点
另外,我觉得你可能有点理解错了SSE的128位的意思,这128位其实是2个64位浮点拼起来的吧,不能支持单一大于64位的浮点运算.不知道现在的SSE能不能支持大于64位浮点了
回复 支持 反对

使用道具 举报

34#
发表于 2007-9-29 23:11 | 只看该作者
原帖由 Ricepig 于 2007-9-29 23:08 发表

不是,当时手册上说SSE不能算80位浮点
另外,我觉得你可能有点理解错了SSE的128位的意思,这128位其实是2个64位浮点拼起来的吧,不能支持单一大于64位的浮点运算.不知道现在的SSE能不能支持大于64位浮点了

sse当然可以直接128bit
否则c2d怎么玩转的,k10怎么玩转的
sse的确需要从架构上去看的
现在两家公司最新的产品都根本不需要拆分了
以前是没办法,现在么:whistling:
早期说的不能处理80bit你理解错了吧
是80bit处理效率没有优势
相比64bit

最近k10那个文档里也有不少sse优化的代码例子,也可以看一下

[ 本帖最后由 Dr.BT 于 2007-9-29 23:18 编辑 ]
回复 支持 反对

使用道具 举报

头像被屏蔽
35#
发表于 2007-9-29 23:17 | 只看该作者
提示: 作者被禁止或删除 内容自动屏蔽
回复 支持 反对

使用道具 举报

36#
发表于 2007-9-29 23:19 | 只看该作者
原帖由 1empress 于 2007-9-29 23:17 发表
扣肉以前 sse都是64bit的

sse2指令又没变过,只不过cpu处理的时候这样来一下罢了
k8就是这样处理的
p4好像不是哦:shifty:
回复 支持 反对

使用道具 举报

37#
发表于 2007-9-29 23:19 | 只看该作者
原帖由 Dr.BT 于 2007-9-29 23:11 发表

sse当然可以直接128bit
否则c2d怎么玩转的,k10怎么玩转的
sse的确需要从架构上去看的
现在两家公司最新的产品都根本不需要拆分了
以前是没办法,现在么:whistling:
早期说的不能处理80bit你理解错了吧 ...

IA-32 Intel Architecture Optimization Reference Manual 2-56页
"x87 supports 80-bit precision, double extended floating point.
Streaming SIMD Extensions support a maximum of 32-bit
precision, and Streaming SIMD Extensions 2 supports a maximum
of 64-bit precision."
所以我觉得你理解错了,所谓的128bit并不是128bit精度,而是两个64bit精度的运算,用一个指令算完吧

[ 本帖最后由 Ricepig 于 2007-9-29 23:20 编辑 ]
回复 支持 反对

使用道具 举报

38#
 楼主| 发表于 2007-9-29 23:19 | 只看该作者
请问:sse的128位能直接支持IEEE X_floating吗?
这问题也就是: sse能否在单周期完成两个128位的x_floating格式浮点数相乘?
如果是,那么确实是一大优势,将x87的80位浮点精度提高到128位。

恐怕不大可能。那就不是simd,而是真128位浮点了。

[ 本帖最后由 紫色 于 2007-9-29 23:27 编辑 ]
回复 支持 反对

使用道具 举报

39#
发表于 2007-9-29 23:21 | 只看该作者
原帖由 紫色 于 2007-9-29 23:19 发表
请问:sse的128位能直接支持IEEE X_floating吗?
也就是直接能否支持128位浮点操作?单周期是否能完成128位的x_floating浮点相乘?
如果是,那么这确实是sse的一大优势。将x87的80浮点精度提高到128位。

我 ...

目前从Intel的手册上来看,SSE2的最大精度是64bit
回复 支持 反对

使用道具 举报

40#
发表于 2007-9-29 23:27 | 只看该作者
要看SSE的威力,可以看这里http://blog.csdn.net/pengzhenwan ... /01/15/1483140.aspx
这是上次Intel多核优化大赛的一个报告,不算最快,但是已经比较快了,SSE的加速作用体现得比较充分,当然还有MKL和多核的威力.

[ 本帖最后由 Ricepig 于 2007-9-29 23:31 编辑 ]
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

广告投放或合作|网站地图|处罚通告|

GMT+8, 2025-8-6 10:35

Powered by Discuz! X3.4

© 2001-2017 POPPUR.

快速回复 返回顶部 返回列表