Pentium III处理器的单指令多数据流扩展指令入门(三)

关键字 :
    Pentium,处理器,单指令多数据流扩展指令,SSE,指令集

概要 :
     随着Intel Pentium III处理器的发布,给程序设计人员又带来了许多新的特性。利用这些新特性,程序员可以为用户创造出更好的产品. Pentium III和Pentium III Xeon(至强处理器)的许多新特性,可以使她能够比Pentium II和Pentium II Xeon处理器有更快的运行速度,这些新特性包括一个处理器序列号(unique processor ID)和新增SSE处理器指令集,这些新的指令集就像Pentium II在经典Pentium的基础上添加的MMX指令集.

    1. Data Swizzling
    要利用Pentium III处理器SSE指令的加速功能也是要有一定的代价的.因为SSE指令只能操作她定义的新数据类型(128位).如果应用程序使用自己的数据类型格式,那么在进行SSE指令操作之前要将他转换为这种新数据类型,操作完成之后又要把他转换回来.

    这种把一种数据格式转换到另一种数据格式的操作就称为"data swizzling".

    这种转换是需要时间和耗费处理器核心周期的.如果一个应用程序频繁的进行数据格式转换,处理器核心周期的浪费是很严重的.因此,这种数据格式的转换是必须得到关注的.

    1.1 数据组织
    通常,3D应用程序将一个顶点保存在一个和他相配的数据结构中.当表达多个顶点时,应用程序使用这个结构的数组,也叫做AoS来表示.一个典型的操作是对表示顶点的x、y和z坐标进行的.下面的代码就给出了这样一个表示3D顶点的数据结构.如果要表示大量的这样的顶点,就要用到这个结构的一个数组了,如图9所示.

struct point {
    float x, y, z;
};
...
point dataset[...];

       
        图九 :结构数组 

    SSE的优点,就是可以同时对多个顶点的进行处理.这样我们就必须要能够方便的处理到表示多个顶点的数据(比如表示4个顶点x坐标的4个浮点数).这是可以实现的,我们可以把表示一个顶点的x、y和z三个坐标值分别集合在一起,然后应用程序对他们进行处理.要实现这些,应用程序必须重新组织数据到三个单独的数组中,或者创建一个数组结构,结构中每个数组对应一个这样的坐标值数组.这个数据结构也称为SoA结构.(我是这样理解的: 在不用SSE时是把表示一个顶点的三个坐标值组合到一个数据结构中,处理时一个值一个值的进行.而使用SSE以后,可以把所有点的各坐标值分别组合到3个数组中,处理时取出这样一个数组的4个值同时执行).

    下面的代码就定义了这样的一个数组结构,如图10用图表来表示的数组结构.

struct point {
    float *x, *y, *z;
};

       
        图十: 数组结构

    2. 内存问题
    2.1 对齐
    简单的处理和使用新数据类型变量是没有问题的.然而,一个推荐的做法是使用16字节边界对齐来定义新数据类型变量.可以通过设置一个特定的编译器参数或者在变量声名时显式的使用一个对齐标记来实现.

    声名变量时可以指定__declspec编译器标记来强制变量使用16字节边界对齐,就像下面的例子代码所示.下面的变量myVar在使用这个对齐标记以后将被强制到16字节边界对齐.但当在直接声明这个新数据类型变量(也就是SSE定义的128位新数据类型)时就不需要指定这个对齐标记了,编译器会在遇到这个新数据类型时自动按16字节对齐.下面是对齐标记的用法 :

__declspec(align(16)) float[4] myVar;

    2.2 动态分配
    由于新数据类型要求的16字节边界对齐,使得在动态分配内存或者用指针来分配内存时会产生问题.

    当使用指针访问数组时,我们必须保证这个指针是16字节边界对齐的.

    在运行时分配内存可以使用malloc或者new命令.默认情况下这两种方法分配的指针都不是16字节边界对齐的.因此我们必须把这个分配得到的指针转换到16字节对齐,或者使用_mm_malloc函数来分配内存.使用_mm_malloc函数分配的内存块是按16字节边界对齐的. 和malloc函数有一个free函数一样,_mm_malloc函数也有一个对应的函数_mm_free.分配内存块使用_mm_malloc函数而清除(free)内存块用_mm_free函数.

    2.3 自定义数据类型
    指针必须是16字节边界对齐的限制很麻烦,如果能够不理睬这种限制就好了.

    在使用128位新数据类型时,我们经常需要去访问储存在里面的(4个)浮点数.在使用汇编语言时我们没有许多的选择.但在使用C或者C++和intrinsics库时,我们总是用__mm128数据类型来表示这个128位的数据.使用这种数据类型(__mm128)时,一旦这个数值被设置以后,我们就不大可能直接去访问存储在里面的单独的浮点数了.有一个途径是把存储在里面的浮点数转移到一个浮点数数组里面,修改他们的数值以后再把他们转换回去.另一种途径是把这个新的数据类型映射到一个浮点数数组的方法来访问这些必要的元素.上面的第一种方法比较浪费时间而第二种方法如果使用不当则会发生问题.

    定义一个自定义的数据类型能够避免这个问题.这个自定义的数据类型用联合(union)的方法把新数据类型(__mm128)和一个4个元素的浮点数数组关联起来.下面给出代码,现在可以用sse4来声名新数据类型了.

union sse4 {
    __m128 m;
    float f[4];
};

    使用上面定义的数据类型,声明变量时不必使用对齐标记来强制16字节对齐,因为编译器在遇到这个数据联合里面的__m128数据类型时会自动按16字节对齐来分配内存的.这个数据类型的一个好处就是可以直接访问到存储在里面的单独的浮点数.

    2.4 侦测CPU
    使用SSE指令集需要有Pentium III处理器,应用程序一个首要的任务就是要去检查有没有Pentium III芯片.这可以用cpuid的指令完成.

    想要cpuid工作,需要把eax寄存器设置为特定的值.这里我们只想得到CPU的ID,需要把eax寄存器置为1然后调用cpuid指令. 下面给出检查PentiumIII处理器是否存在的代码.要想使下面的代码工作,还必须包含fvec.h头文件.

BOOL CheckP3HW()
{
    BOOL SSEHW = FALSE;
    _asm { 
        // Move the number 1 into eax - this will move the 
        // feature bits into EDX when a CPUID is issued, that 
        // is, EDX will then hold the key to the cpuid 
        mov eax, 1 

        // Does this processor have SSE support? 
        cpuid 

        // Perform CPUID (puts processor feature info in EDX) 
        // Shift the bits in edx to the right by 26, thus bit 25 
        // (SSE bit) is now in CF bit in EFLAGS register. 
        shr edx,0x1A 

        // If CF is not set, jump over next instruction 
        jnc nocarryflag 

        // set the return value to 1 if the CF flag is set 
        mov [SSEHW], 1 

        nocarryflag:
    }
    return SSEHW;
}

    SSE SDK还有一个Pentium III和SSE寄存器的异常模式.用下面的代码也可以用来检查是否有Pentium III处理器.要编译下面的代码,需要包含fvec.h头文件.

// Checking for SSE emulation support
BOOL CheckP3Emu()
{
    BOOL SSEEmu = TRUE;
    Fvec32 pNormal = (1.0, 2.0, 3.0, 4.0);
    Fvec32 pZero = 0.0;
    // Checking for SSE HW emulation
    __try {
        _asm {
            // Issue a move instruction that will cause exception 
            // w/out HW support emulation 
            movups xmm1, [pNormal] 

            // Issue a computational instruction that will cause 
            // exception w/out HW support emulation 
            divps xmm1, [pZero] 
        } 
    }
    // If there''s an exception, set emulation variable to false
    __except(EXCEPTION_EXECUTE_HANDLER) {
        SSEEmu = FALSE;
    }
    return SSEEmu;
}

    3. 参考
    For more details about the architecture of the Pentium III, refer [11], [12], [13] and [10].
    For more information about Processor identification and CPUID, refer [15] and [7]. 
    For more information about the Streaming SIMD Extensions, refer [19].
    For more information about the programming issue, the software conventions and the software development strategies, refer [9], [16] and [17] respectively.
    For more about application tuning for SSE and the VTune performance enhancement application, refer [1] and [8] respectively.
    For more details about VTune and the Intel C/C++ Compiler, refer [3] and [2] respectively.
    For additional information about the Pentium III processor and its capabilities, refer [14], [18], [20], [6], [5] and [4].

    4. 附加示例
    在这一节,我们将向你介绍几个SSE指令集(Streaming SIMD Extensions)使用的例子.

    4.1 数组操作
    下面的例子中,我们创建了两个数组,每个包含400个浮点数.将两个数组的每个元素相乘,结果保存在第三个数组中.前面两个数组作为操作数A和B,保存结果的数组为C.下面给出所有的代码.

#include < FVEC.H >
#define ARRSIZE 400
__declspec(align(16)) float a[ARRSIZE], b[ARRSIZE], c[ARRSIZE];

    4.1.1 Assembly Language

_asm {
    push esi;
    push edi;
    mov edi, a;
    mov esi, b;
    mov edx, c;
    mov ecx, 100; 
loop:
    movaps xmm0, [edi];
    movups xmm1, [esi];
    mulps xmm0, xmm1;
    movups [edx], xmm0;
    add edi, 16;
    add esi, 16;
    add edx, 16;
    dec ecx;
    jnz loop;
    pop edi;
    pop esi;
}

    4.1.2 Intrinsics

__m128 m1, m2, m3; 
for ( int i = 0; i < ARRSIZE; i += 4 ) 

    m1= _mm_loadu_ps(a+i); 
    m2= _mm_loadu_ps(b+i); 
    m3= _mm_mul_ps(m1,m2); 
    _mm_storeu_ps(c+i, m3); 
}

    4.1.3 C++

F32vec4 f1, f2, f3;

for ( int i = 0; i < ARRSIZE; i +=4 )
{
    loadu(f1, a+i);
    loadu(f2, b+i);
    f3 = f1 * f2;
    storeu(c+i, f3);
}

    4.2 矢量3D
    下面的例子介绍3D矢量.矢量被封装在一个类中.在下面类的函数里面调用了intrinsics库.

类声明.

union sse4 {
	__m128 m;
	float f[4];
};

class sVector3 {
protected:
	sse4 val;
public:
	sVector3(float, float, float);
	float& operator [](int);
	sVector3& operator +=(const sVector3&);
	float length() const;
friend float dot(const sVector3&, const sVector3&);
};

类实现.

sVector3::sVector3(float x, float y, float z) {
	val.m = _mm_set_ps(0, z, y, x);
}
float& sgmVector3::operator [](int i) {
	return val.f[i];
}
sVector3& sVector3::operator +=(const sVector3& v) {
	val.m = _mm_add_ps(val.m, v.val.m);
	return *this;
}
float sVector3::length() const {
	sse4 m1;
	m1.m = _mm_sqrt_ps(_mm_mul_ps(val.m, val.m));
	return m1.f[0] + m1.f[1] + m1.f[2];
}
float dot(const sVector3& v1, const sVector3& v2) {
	sVector3 v(v1);
	v.val.m = _mm_mul_ps(v.val.m, v2.val.m);
	return v.val.f[0] + v.val.f[1] + v.val.f[2];
}

    4.3 4x4矩阵
    下面的例子介绍一个4x4的矩阵.矩阵被封装在一个类中.在下面类的函数里面调用了intrinsics库.

类声明.

float const sEPSILON = 1.0e-10f;

union sse16 {
	__m128 m[4];
	float f[4][4];
};

class sMatrix4 {
protected:
	sse16 val;
	sse4 sFuzzy;
public:
	sMatrix4(float*);
	float& operator()(int, int);
	sMatrix4& operator +=(const sMatrix4&);
	bool operator ==(const sMatrix4&) const;
	sVector4 operator *(const sVector4&) const;
private:
	float RCD(const sMatrix4& B, int i, int j) const;
};

类实现.

sMatrix4::sMatrix4(float* fv) {
	val.m[0] = _mm_set_ps(fv[3], fv[2], fv[1], fv[0]);
	val.m[1] = _mm_set_ps(fv[7], fv[6], fv[5], fv[4]);
	val.m[2] = _mm_set_ps(fv[11], fv[10], fv[9], fv[8]);
	val.m[3] = _mm_set_ps(fv[15], fv[14], fv[13], fv[12]);
	float f = sEPSILON;
	sFuzzy.m = _mm_set_ps(f, f, f, f);
}
float& sMatrix4::operator()(int i, int j) {
	return val.f[i][j];
}
sMatrix4& sMatrix4::operator +=(const sMatrix4& M) {
	val.m[0] = _mm_add_ps(val.m[0], M.val.m[0]);
	val.m[1] = _mm_add_ps(val.m[1], M.val.m[1]);
	val.m[2] = _mm_add_ps(val.m[2], M.val.m[2]);
	val.m[3] = _mm_add_ps(val.m[3], M.val.m[3]);
	return *this;
}
bool sMatrix4::operator ==(const sMatrix4& M) const {
	int res[4];
	res[0] = res[1] = res[2] = res[3] = 0;
	res[0] = _mm_movemask_ps(_mm_cmplt_ps(_mm_sub_ps(
		_mm_max_ps(val.m[0], M.val.m[0]),
		_mm_min_ps(val.m[0], M.val.m[0])), sFuzzy.m));
	res[1] = _mm_movemask_ps(_mm_cmplt_ps(_mm_sub_ps(
		_mm_max_ps(val.m[1], M.val.m[1]),
		_mm_min_ps(val.m[1], M.val.m[1])), sFuzzy.m));
	res[2] = _mm_movemask_ps(_mm_cmplt_ps(_mm_sub_ps(
		_mm_max_ps(val.m[2], M.val.m[2]),
		_mm_min_ps(val.m[2], M.val.m[2])), sFuzzy.m));
	res[3] = _mm_movemask_ps(_mm_cmplt_ps(_mm_sub_ps(
		_mm_max_ps(val.m[3], M.val.m[3]),
		_mm_min_ps(val.m[3], M.val.m[3])), sFuzzy.m));
	if ( (15 == res[0]) && (15 == res[1])
			&& (15 == res[2]) && (15 == res[3]) )
		return 1;
	return 0;
}
sVector4 sMatrix4::operator *(const sVector4& v) const {
	return sVector4(
		val.f[0][0] * v[0] + val.f[0][1] * v[1]
			+ val.f[0][2] * v[2] + val.f[0][3] * v[3],
		val.f[1][0] * v[0] + val.f[1][1] * v[1]
			+ val.f[1][2] * v[2] + val.f[1][3] * v[3],
		val.f[2][0] * v[0] + val.f[2][1] * v[1]
			+ val.f[2][2] * v[2] + val.f[2][3] * v[3],
		val.f[3][0] * v[0] + val.f[3][1] * v[1]
			+ val.f[3][2] * v[2] + val.f[3][3] * v[3]);
}
float sMatrix4::RCD(const sMatrix4& B, int i, int j) const {
	return val.f[i][0] * B.val.f[0][j] + val.f[i][1] * B.val.f[1][j]
		+ val.f[i][2] * B.val.f[2][j] + val.f[i][3] * B.val.f[3][j];
}

[参考书目]

[1] James Abel, Kumar Balasubramanian, Mike Bargeron, Tom Craver, and Mike Phlipot. Applications tuning for streaming simd extensions. Technical report, Intel Corporation, 1999.

[2] Intel Corporation. Intel C/C++ Compiler Web Site. http://developer.intel.com/vtune/icl.

[3] Intel Corporation. Vtune Performance Analyzer Web Site. http://developer.intel.com/vtune/performance.

[4] Intel Corpotation. Developer Relations Group Web Site. http://developer.intel.com/drg.

[5] Intel Corpotation. Intel Developer Web Site. http://developer.intel.com.

[6] Intel Corpotation. Web site. http://www.intel.com.

[7] Stephan Fischer, James Mi, and Albert Tang. Pentium iii processor serial number feature and applications. Technical report, Intel Corporation, 1999.

[8] Joe Wolf III. Programming methods for the pentium iii processor streaming simd extensions using the vtune performance enhancement environment. Technical report, Intel Corporation, 1999.

[9] Intel Corporation. Data Alignment and Programming Issues for the Streaming SIMD Extensions with the Intel C/C++ Compiler, 1999. App Note ap833.

[10] Intel Corporation. Intel Architecture Optimization Reference Manual, 1999.

[11] Intel Corporation. Intel Architecture Software Development Manual. Volume 1: Basic Architecture, 1999.

[12] Intel Corporation. Intel Architecture Software Development Manual. Volume 2: Instruction Set Reference, 1999.

[13] Intel Corporation. Intel Architecture Software Development Manual. Volume 3: Systems Programming Guide, 1999.

[14] Intel Corporation. Intel Pentium III Processor Performance Brief, 1999.

[15] Intel Corporation. Intel Processor Identification and CPUID Instruction, 1999. App Note Ap-485.

[16] Intel Corporation. Software Conventions for Streaming SIMD Extensions, 1999. App Note AP589.

[17] Intel Corporation. Software Development Strategies for Streaming SIMD Extensions, 1999. App Note AP814.

[18] Jagannath Keshavan and Vladimir Penkovski. Pentium iii processor implementation trade-offs. Technical report, Intel Corporation, 1999.

[19] Shreekant Thakkar and Tom Huff. Internet streaming simd extensions. Technical report, Intel Corporation, 1999.

[20] Paul Zagacki, Deep Duch, Emil Hsiech, Daniel Melaku, and Vladimir Pentkovski. Architecture of 3d software stack for peak pentium iii processor performance. Technical report, Intel Corporation, 1999.

 

字母检索 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z