借助 SIMD 技术基于英特尔® 架构加快游戏代码开发

摘要

总所周知,大量复杂的数学运算是 2D 和 3D 游戏开发与执行的关键基础,而且这些运算通常需要大量对计算性能要求较高的矢量与矩阵变换。 在本文中,我们将讨论矩阵乘法,并使用中国大陆流行的基于 Android* 的 3D 射击游戏“最后的防线”作为实例介绍如何使用面向英特尔® 架构(IA)的 SIMD 技术:英特尔® SIMD 流指令扩展 (英特尔® SSE[XX]) 和补充 SIMD 流指令扩展(SSSE[XX])加快数学运算、提升游戏性能。 文档结尾将介绍一些常用优化解决方案和基于英特尔架构的代码。

背景

简单地说,单指令多数据流(SIMD)就是在一个指令中执行多个数据计算,是指令级别的并行计算技术。 由于能够充分利用并行计算,英特尔® SSE 十分适合用于游戏代码的矢量与矩阵运算,因为矢量或矩阵中的每个元素都可以独立运算(开发人员必须检查元素确保元素之间互不影响;元素之间偶尔会出现相互影响的情况)。 如果使用传统串行计算技术,每个元素需要一个指令周期,例如,一个 4x4 矩阵需要 16 个周期才能执行所有运算。 但是如果使用 SIMD 技术,并假设 SIMD 实施可在 1 个指令周期内并行处理 4 个数据运算,这意味着 4 个元素运算可在 1 个指令周期内同步完成,所有 16 个元素运算仅需 4 个指令周期!

英特尔 SSE 指令集广泛用于基于英特尔架构的 SIMD 实施;尤其用于游戏引擎中。 英特尔 SSE 包括很多扩展: SSE,SSE2,SSE3 和 SSE4.x,而且它们支持多种整数和浮点类型的运算。 它们还支持高效的内存访问。 在本文中,我们统统简称为“英特尔 SSE”;现在我们将介绍在矩阵乘法运算中如何使用。

内存中有两种矩阵存储模式:

1. 在列主序(图 1)矩阵中,所有元素在内存中是按照列顺序依次存储的。 如果内存是按照临近寻址方式访问,矩阵元素就会按照列的顺序访问。 众所周知,OpenGL* 使用这一方法在内存中存储矩阵。


图 1. 列主序矩阵

2. 在行主序(图 2)矩阵中,所有元素在内存中是按照行顺序依次存储的。 如果内存是按照临近寻址方式访问,矩阵元素就会按照行的顺序访问。 游戏“最后的防线”就是使用这一方法作为高级矩阵存储模式。


图 2. 行主序矩阵

由于本文重点介绍“最后的防线”(行主序)作为安卓架构游戏开发的使用案例,同时 OpenGL ES* (列主序)是安卓架构的唯一低级图形硬件加速 API,因此两种存储模式和相关优化我们均将介绍。

在我们下面讨论的 OpenGL ES 和“最后的防线”中,矩阵乘法运算(矢量变换)顺序是一样的。 它们都使用“矩阵自左乘”,方程表示如下(V 为矢量,M 为矩阵):

    Vo = Mn x ... x M2 x M1 x Vi

请参考图 3:


图 3. MxV

图 3 显示“MxV”结果是一个新矢量,一个 4x4 矩阵乘以一个 4 元素矢量需要 16 个乘法运算和 12 个加法运算。 这一运算负载相当大。 如果采用串行模式执行这些运算,就会需要很多指令周期,花费很长时间。 而且,游戏引擎涉及大量此类运算,因此是重点优化对象。

在图 3 中,蓝框中的列是 4 个可同步执行的元素运算,这意味着整个 MxV 运算仅需 4 个并行乘法运算和 3 个并行加法运算。 性能因此提升 4 倍。 事实上,英特尔 SSE 非常擅长同步执行每个列中的所有乘法运算和加法运算,如图 4 所示。


图 4. 矩阵并行运算


图 5. MxM

图 5 是两个矩阵相乘(MxM)的示例,它是上文介绍的矩阵乘矢量(MxV)的扩展。 但是我们应当了解一点: 这些运算受矩阵存储模式的影响。 图 5 中的不同颜色表示的是两种不同矩阵存储模式下的并行运算。 粉红色表示行主序模式下的并行运算,橙色表示列主序模式下的并行运算。 由于英特尔 SSE 还针对不同矩阵存储模式提供高效的内存访问指令集,我们应使用不同的算法来充分利用上述加速指令集。 然而必须注意内存访问指令集的内存对齐限制条件。

下面两个章节分别介绍基于两种矩阵存储模式的两个不同解决方案。 但是都使用英特尔 SSE 并行处理矩阵运算以加快代码执行速度。

行主序矩阵乘法优化 — 基于游戏“最后的防线”

在使用 英特尔 SSE 优化“最后的防线”游戏引擎代码之前,我们使用英特尔® VTune™ Amplifier 2011 for Android 配置了计算开销,尤其注释了“Matrix4f::mul”函数,如图 6 所示:


图 6. 初始(基准) Matrix4f::mul 计算开销参考

我们使用英特尔® 凌动™ 处理器 Z2480 在 Motorola MT788 智能手机上配置了我们的代码。 特定运算执行结束后,我们发现 Matrix4f::mul 计算开销参考是 83,340,000 — 相当费时的运算。 在实际代码中显示如下:

void Matrix4f::mul(Matrix4f *m1, Matrix4f *m2)
	if (this != m1 && this != m2)
	{

		this->f[m00] = m1->f[m00] * m2->f[m00] + m1->f[m01] * m2->f[m10] + m1->f[m02] * m2->f[m20] + m1->f[m03] * m2->f[m30];
		this->f[m01] = m1->f[m00] * m2->f[m01] + m1->f[m01] * m2->f[m11] + m1->f[m02] * m2->f[m21] + m1->f[m03] * m2->f[m31];
		this->f[m02] = m1->f[m00] * m2->f[m02] + m1->f[m01] * m2->f[m12] + m1->f[m02] * m2->f[m22] + m1->f[m03] * m2->f[m32];
		this->f[m03] = m1->f[m00] * m2->f[m03] + m1->f[m01] * m2->f[m13] + m1->f[m02] * m2->f[m23] + m1->f[m03] * m2->f[m33];

		this->f[m10] = m1->f[m10] * m2->f[m00] + m1->f[m11] * m2->f[m10] + m1->f[m12] * m2->f[m20] + m1->f[m13] * m2->f[m30];
		this->f[m11] = m1->f[m10] * m2->f[m01] + m1->f[m11] * m2->f[m11] + m1->f[m12] * m2->f[m21] + m1->f[m13] * m2->f[m31];
		this->f[m12] = m1->f[m10] * m2->f[m02] + m1->f[m11] * m2->f[m12] + m1->f[m12] * m2->f[m22] + m1->f[m13] * m2->f[m32];
		this->f[m13] = m1->f[m10] * m2->f[m03] + m1->f[m11] * m2->f[m13] + m1->f[m12] * m2->f[m23] + m1->f[m13] * m2->f[m33];

		this->f[m20] = m1->f[m20] * m2->f[m00] + m1->f[m21] * m2->f[m10] + m1->f[m22] * m2->f[m20] + m1->f[m23] * m2->f[m30];
		this->f[m21] = m1->f[m20] * m2->f[m01] + m1->f[m21] * m2->f[m11] + m1->f[m22] * m2->f[m21] + m1->f[m23] * m2->f[m31];
		this->f[m22] = m1->f[m20] * m2->f[m02] + m1->f[m21] * m2->f[m12] + m1->f[m22] * m2->f[m22] + m1->f[m23] * m2->f[m32];
		this->f[m23] = m1->f[m20] * m2->f[m03] + m1->f[m21] * m2->f[m13] + m1->f[m22] * m2->f[m23] + m1->f[m23] * m2->f[m33];

		this->f[m30] = m1->f[m30] * m2->f[m00] + m1->f[m31] * m2->f[m10] + m1->f[m32] * m2->f[m20] + m1->f[m33] * m2->f[m30];
		this->f[m31] = m1->f[m30] * m2->f[m01] + m1->f[m31] * m2->f[m11] + m1->f[m32] * m2->f[m21] + m1->f[m33] * m2->f[m31];
		this->f[m32] = m1->f[m30] * m2->f[m02] + m1->f[m31] * m2->f[m12] + m1->f[m32] * m2->f[m22] + m1->f[m33] * m2->f[m32];
		this->f[m33] = m1->f[m30] * m2->f[m03] + m1->f[m31] * m2->f[m13] + m1->f[m32] * m2->f[m23] + m1->f[m33] * m2->f[m33];
	}
	else
	{
		float _m00, _m01, _m02, _m03, _m10, _m11, _m12, _m13, _m20, _m21, _m22, _m23, _m30, _m31, _m32, _m33; // vars
		// for
		// te_mp
		// result
		// _matrix
		_m00 = m1->f[m00] * m2->f[m00] + m1->f[m01] * m2->f[m10] + m1->f[m02] * m2->f[m20] + m1->f[m03] * m2->f[m30];
		_m01 = m1->f[m00] * m2->f[m01] + m1->f[m01] * m2->f[m11] + m1->f[m02] * m2->f[m21] + m1->f[m03] * m2->f[m31];
		_m02 = m1->f[m00] * m2->f[m02] + m1->f[m01] * m2->f[m12] + m1->f[m02] * m2->f[m22] + m1->f[m03] * m2->f[m32];
		_m03 = m1->f[m00] * m2->f[m03] + m1->f[m01] * m2->f[m13] + m1->f[m02] * m2->f[m23] + m1->f[m03] * m2->f[m33];

		_m10 = m1->f[m10] * m2->f[m00] + m1->f[m11] * m2->f[m10] + m1->f[m12] * m2->f[m20] + m1->f[m13] * m2->f[m30];
		_m11 = m1->f[m10] * m2->f[m01] + m1->f[m11] * m2->f[m11] + m1->f[m12] * m2->f[m21] + m1->f[m13] * m2->f[m31];
		_m12 = m1->f[m10] * m2->f[m02] + m1->f[m11] * m2->f[m12] + m1->f[m12] * m2->f[m22] + m1->f[m13] * m2->f[m32];
		_m13 = m1->f[m10] * m2->f[m03] + m1->f[m11] * m2->f[m13] + m1->f[m12] * m2->f[m23] + m1->f[m13] * m2->f[m33];

		_m20 = m1->f[m20] * m2->f[m00] + m1->f[m21] * m2->f[m10] + m1->f[m22] * m2->f[m20] + m1->f[m23] * m2->f[m30];
		_m21 = m1->f[m20] * m2->f[m01] + m1->f[m21] * m2->f[m11] + m1->f[m22] * m2->f[m21] + m1->f[m23] * m2->f[m31];
		_m22 = m1->f[m20] * m2->f[m02] + m1->f[m21] * m2->f[m12] + m1->f[m22] * m2->f[m22] + m1->f[m23] * m2->f[m32];
		_m23 = m1->f[m20] * m2->f[m03] + m1->f[m21] * m2->f[m13] + m1->f[m22] * m2->f[m23] + m1->f[m23] * m2->f[m33];

		_m30 = m1->f[m30] * m2->f[m00] + m1->f[m31] * m2->f[m10] + m1->f[m32] * m2->f[m20] + m1->f[m33] * m2->f[m30];
		_m31 = m1->f[m30] * m2->f[m01] + m1->f[m31] * m2->f[m11] + m1->f[m32] * m2->f[m21] + m1->f[m33] * m2->f[m31];
		_m32 = m1->f[m30] * m2->f[m02] + m1->f[m31] * m2->f[m12] + m1->f[m32] * m2->f[m22] + m1->f[m33] * m2->f[m32];
		_m33 = m1->f[m30] * m2->f[m03] + m1->f[m31] * m2->f[m13] + m1->f[m32] * m2->f[m23] + m1->f[m33] * m2->f[m33];

		this->f[m00] = _m00;
		this->f[m01] = _m01;
		this->f[m02] = _m02;
		this->f[m03] = _m03;
		this->f[m10] = _m10;
		this->f[m11] = _m11;
		this->f[m12] = _m12;
		this->f[m13] = _m13;
		this->f[m20] = _m20;
		this->f[m21] = _m21;
		this->f[m22] = _m22;
		this->f[m23] = _m23;
		this->f[m30] = _m30;
		this->f[m31] = _m31;
		this->f[m32] = _m32;
		this->f[m33] = _m33;
	}

请看,这个代码简单明了。 但是显然花费时间太长,而且这个函数在游戏引擎中经常调用,因此影响了性能,亟需优化。

如上所述,可进行如下简单的 SSE 优化(行主序):

	__m128 m1_row_0 = _mm_setr_ps(m1->f[m00], m1->f[m01], m1->f[m02], m1->f[m03]);
	__m128 m1_row_1 = _mm_setr_ps(m1->f[m10], m1->f[m11], m1->f[m12], m1->f[m13]);
	__m128 m1_row_2 = _mm_setr_ps(m1->f[m20], m1->f[m21], m1->f[m22], m1->f[m23]);
	__m128 m1_row_3 = _mm_setr_ps(m1->f[m30], m1->f[m31], m1->f[m32], m1->f[m33]);

	__m128 m2_row_0 = _mm_setr_ps(m2->f[m00], m2->f[m01], m2->f[m02], m2->f[m03]);
	__m128 m2_row_1 = _mm_setr_ps(m2->f[m10], m2->f[m11], m2->f[m12], m2->f[m13]);
	__m128 m2_row_2 = _mm_setr_ps(m2->f[m20], m2->f[m21], m2->f[m22], m2->f[m23]);
	__m128 m2_row_3 = _mm_setr_ps(m2->f[m30], m2->f[m31], m2->f[m32], m2->f[m33]);

	__m128 out0;
	__m128 out1;
	__m128 out2;
	__m128 out3;

	out0 = _mm_mul_ps(m2_row_0, _mm_replicate_x_ps(m1_row_0));
	out1 = _mm_mul_ps(m2_row_0, _mm_replicate_x_ps(m1_row_1));
	out2 = _mm_mul_ps(m2_row_0, _mm_replicate_x_ps(m1_row_2));
	out3 = _mm_mul_ps(m2_row_0, _mm_replicate_x_ps(m1_row_3));

	out0 = _mm_madd_ps(m2_row_1, _mm_replicate_y_ps(m1_row_0), out0);
	out1 = _mm_madd_ps(m2_row_1, _mm_replicate_y_ps(m1_row_1), out1);
	out2 = _mm_madd_ps(m2_row_1, _mm_replicate_y_ps(m1_row_2), out2);
	out3 = _mm_madd_ps(m2_row_1, _mm_replicate_y_ps(m1_row_3), out3);

	out0 = _mm_madd_ps(m2_row_2, _mm_replicate_z_ps(m1_row_0), out0);
	out1 = _mm_madd_ps(m2_row_2, _mm_replicate_z_ps(m1_row_1), out1);
	out2 = _mm_madd_ps(m2_row_2, _mm_replicate_z_ps(m1_row_2), out2);
	out3 = _mm_madd_ps(m2_row_2, _mm_replicate_z_ps(m1_row_3), out3);

	out0 = _mm_madd_ps(m2_row_3, _mm_replicate_w_ps(m1_row_0), out0);
	out1 = _mm_madd_ps(m2_row_3, _mm_replicate_w_ps(m1_row_1), out1);
	out2 = _mm_madd_ps(m2_row_3, _mm_replicate_w_ps(m1_row_2), out2);
	out3 = _mm_madd_ps(m2_row_3, _mm_replicate_w_ps(m1_row_3), out3);

	_mm_store_ps(&this->f[0], out0);
	_mm_store_ps(&this->f[4], out1);
	_mm_store_ps(&this->f[8], out2);
	_mm_store_ps(&this->f[12], out3);

该实施是基于英特尔 SSE 内部指令。 如果编译器支持,我们建议开发人员使用 SSE 内部指令,而不要编写纯汇编语言。 与汇编语言相比,它们更具优越性,易于使用,而且不影响性能。

    __m128 是支持 SSE 内部指令的数据类型。 其长度是 128 个字节,可用于存储 4 个 32 位单浮点。
    __m128 _mm_setr_ps(float z , float y , float x , float w );

    这一内部指令可为 1 个 __m128 数据设定 4 个单浮点,r0 := z, r1 := y, r2 := x, r3 := w。
    __m128 _mm_mul_ps(__m128 a , __m128 b );

    这一内部指令可并行执行 4 个单浮点“a”乘以 4 个单浮点“b”:
    r0 := a0 * b0
    r1 := a1 * b1
    r2 := a2 * b2
    r3 := a3 * b3

    _mm_replicate_(x~w)_ps 是一个宏,包括:
    #define _mm_replicate_x_ps(v) \
    _mm_shuffle_ps((v), (v), SHUFFLE_PARAM(0, 0, 0, 0))

    #define _mm_replicate_y_ps(v) \
    _mm_shuffle_ps((v), (v), SHUFFLE_PARAM(1, 1, 1, 1))

    #define _mm_replicate_z_ps(v) \
    _mm_shuffle_ps((v), (v), SHUFFLE_PARAM(2, 2, 2, 2))

    #define _mm_replicate_w_ps(v) \
    _mm_shuffle_ps((v), (v), SHUFFLE_PARAM(3, 3, 3, 3))

    __m128 _mm_shuffle_ps(__m128 a , __m128 b , int i );

    这一内部指令是基于掩码“i”。 它从“a”和“ b”中选择 4 个单浮点合并一个新的基于掩码“i”的 __m128 数据。 掩码必须是一个最接近的数。 图 7 是详细规则:

    _MM_SHUFFLE(z, y, x, w)
    /* expands to the following value */
    (z<<6) | (y<<4) | (x<<2) | w


图 7. SHUFFLE 说明

    使用以下 SHUFFLE 宏优化“最后的防线”:
    #define SHUFFLE_PARAM(x, y, z, w) \
    ((x) | ((y) << 2) | ((z) << 4) | ((w) << 6))

    这可为 1 个 __m128 数据设定 4 个相同单浮点用于同步运算同一元素值。
    #define _mm_madd_ps(a, b, c) \
    _mm_add_ps(_mm_mul_ps((a), (b)), (c))

    这一宏可首先执行乘法合并,最后执行加法,简化代码编写。
    void _mm_store_ps(float *p, __m128 a );

    这一内部指令属于内存访问。 它存储 1 个 __m128 数据至“p”地址(该地址必须是 16 字节对齐)。
    p[0] := a0
    p[1] := a1
    p[2] := a2
    p[3] := a3

经过简单优化,我们使用英特尔 VTune Amplifier 2011 for Android 配置了同一运算,结果如图 8 所示。


图 8. 经过优化的 Matrix4f::mul 计算开销参考

这一计算开销参考从 83,340,000 降至 18,780,000,性能提升了 4 倍1 (我们在下述场景中执行了同样步骤与运算: 同样场面、同样敌人、同样车辆、同样武器、同样测试时长等等,但是由于 AI 和敌人数量的改变,测试结果略受影响)。 这一示例表明英特尔 SSE 并行计算能力十分强大。

列主序矩阵乘法优化 — OpenGL ES

对于 OpenGL ES 应用的矩阵运算,强烈推荐列主序存储模式。 该模式不仅满足 OpenGL ES 规范,而且便于并行化实施。 如上所述,开发人员可使用高效内存访问技术优化代码编写。 下面是从 ARM NEON* 到英特尔 SSE 的经典转换示例:

void NEON_Matrix4Mul(const float* a, const float* b, float* output )
{
    __asm__ volatile
    (
     // Store A & B leaving room for q4-q7, which should be preserved
     "vldmia %1, { q0-q3 } nt"
     "vldmia %2, { q8-q11 }nt"

     // result = first column of B x first row of A
     "vmul.f32 q12, q8, d0[0]nt"
     "vmul.f32 q13, q8, d2[0]nt"
     "vmul.f32 q14, q8, d4[0]nt"
     "vmul.f32 q15, q8, d6[0]nt"

     // result += second column of B x second row of A
     "vmla.f32 q12, q9, d0[1]nt"
     "vmla.f32 q13, q9, d2[1]nt"
     "vmla.f32 q14, q9, d4[1]nt"
     "vmla.f32 q15, q9, d6[1]nt"

     // result += third column of B x third row of A
     "vmla.f32 q12, q10, d1[0]nt"
     "vmla.f32 q13, q10, d3[0]nt"
     "vmla.f32 q14, q10, d5[0]nt"
     "vmla.f32 q15, q10, d7[0]nt"

     // result += last column of B x last row of A
     "vmla.f32 q12, q11, d1[1]nt"
     "vmla.f32 q13, q11, d3[1]nt"
     "vmla.f32 q14, q11, d5[1]nt"
     "vmla.f32 q15, q11, d7[1]nt"

     // output = result registers
     "vstmia %0, { q12-q15 }"
     : // no output
     : "r" (output), "r" (a), "r" (b)     // input - note *value* of pointer doesn't change
     : "memory", "q0", "q1", "q2", "q3", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" //clobber
     );
}
//####################################################################################################################
static inline void SSE_Matrix4Mul(const float* a, const float* b, float* output)
{
	// load matrices a and b as column-major order of OpenGL ES
	__m128 ma_col_0 = _mm_load_ps(a);
	__m128 ma_col_1 = _mm_load_ps(a + 4);
	__m128 ma_col_2 = _mm_load_ps(a + 8);
	__m128 ma_col_3 = _mm_load_ps(a + 12);

	__m128 mb_col_0 = _mm_load_ps(b);
	__m128 mb_col_1 = _mm_load_ps(b + 4);
	__m128 mb_col_2 = _mm_load_ps(b + 8);
	__m128 mb_col_3 = _mm_load_ps(b + 12);

	// get ready to store the result
	__m128 result0;
	__m128 result1;
	__m128 result2;
	__m128 result3;

	// result = first column of B x first row of A
	result0 = _mm_mul_ps(mb_col_0, _mm_replicate_x_ps(ma_col_0));
	result1 = _mm_mul_ps(mb_col_0, _mm_replicate_x_ps(ma_col_1));
	result2 = _mm_mul_ps(mb_col_0, _mm_replicate_x_ps(ma_col_2));
	result3 = _mm_mul_ps(mb_col_0, _mm_replicate_x_ps(ma_col_3));

	// result += second column of B x second row of A
	result0 = _mm_madd_ps(mb_col_1, _mm_replicate_y_ps(ma_col_0), result0);
	result1 = _mm_madd_ps(mb_col_1, _mm_replicate_y_ps(ma_col_1), result1);
	result2 = _mm_madd_ps(mb_col_1, _mm_replicate_y_ps(ma_col_2), result2);
	result3 = _mm_madd_ps(mb_col_1, _mm_replicate_y_ps(ma_col_3), result3);

	// result += third column of B x third row of A
	result0 = _mm_madd_ps(mb_col_2, _mm_replicate_z_ps(ma_col_0), result0);
	result1 = _mm_madd_ps(mb_col_2, _mm_replicate_z_ps(ma_col_1), result1);
	result2 = _mm_madd_ps(mb_col_2, _mm_replicate_z_ps(ma_col_2), result2);
	result3 = _mm_madd_ps(mb_col_2, _mm_replicate_z_ps(ma_col_3), result3);

	// result += last column of B x last row of A
	result0 = _mm_madd_ps(mb_col_3, _mm_replicate_w_ps(ma_col_0), result0);
	result1 = _mm_madd_ps(mb_col_3, _mm_replicate_w_ps(ma_col_1), result1);
	result2 = _mm_madd_ps(mb_col_3, _mm_replicate_w_ps(ma_col_2), result2);
	result3 = _mm_madd_ps(mb_col_3, _mm_replicate_w_ps(ma_col_3), result3);

	// store the result to memory
	_mm_store_ps(output, result0);
	_mm_store_ps(output+4, result1);
	_mm_store_ps(output+8, result2);
	_mm_store_ps(output+12, result3);
}

希望这一代码参考示例对您有所帮助。

    __m128 _mm_load_ps(float * p );
    这一内部指令从“p”地址(“p” 必须是 16 字节对齐) 到 1 个 __m128 数据加载 4 个临近单浮点数据。
    r0 := p[0]
    r1 := p[1]
    r2 := p[2]
    r3 := p[3]

其它优化技术

游戏代码编写还有更多优化技术与技巧。 其中一个是面向 Android* 的英特尔® C++ 编译器,这一出色、易用的工具可用来编写游戏代码的 NDK 部分。 面向安卓操作系统的英特尔 C++ 编译器可为英特尔® CPU 架构提供众多特殊优化,例如,管线、缓存和内存利用率。 我们还使用 GCC 编译代码,但是我们需要按照如下方法设置编译选项以优化性能、缓存和内存利用率:
GCC 优化编译选项
LOCAL_CFLAGS := -O3 -ffast-math -mtune=atom -msse3 -mfpmath=sse
面向安卓操作系统的英特尔 C++ 编译器的优化编译选项
LOCAL_CFLAGS := -O3 -xSSSE3_atom -ipo -no-prec-div

工欲善其事,必先利其器。面向安卓操作系统的英特尔 VTune Amplifier 2011 可帮助开发人员快速定位程序的热点(原本十分费时),并查看缓存与内存使用状态,提高性能和质量。 英特尔® 图形性能分析器(英特尔® GPA)也是强大的工具集。 它可帮助开发人员监控整个系统的软件执行的实时状态并查找瓶颈,包括 CPU、GPU、内存、IO、图形 API,等等。 英特尔 GPA 是出色的游戏开发工具!


图形 9. 英特尔® 图形性能分析器

总结

通过结合使用英特尔® SSE 和面向安卓操作系统的英特尔 C++ 编译器以及英特尔 GPA 指令集,我们显著改进了“最后的防线”的游戏性能。 在同样的测试场景中,FPS 从 30 提高到 39,约为 30%2!


图 10. 未经优化的“最后的防线”截图(FPS 可通过游戏设置开启)


图 11. 经过优化的“最后的防线”截图(FPS 可通过游戏设置开启)

使用英特尔® SSE 技术为游戏代码提速极具激情和挑战。 虽然本文的介绍不够详细,但是我们希望能够启发安卓游戏开发人员充分利用 IA 相关特性优化游戏代码开发,提升游戏速度和用户体验!

谢谢!

 

作者简介

YANG Yi 是来自英特尔公司的软件应用工程师,目前主攻项目是面向中国大陆、Android* 操作系统和英特尔架构(IA)开发游戏引擎并提供图形相关支持。 在众多先进英特尔® 技术的基础上,他积极推动中国大陆的独立软件厂商(ISV)交付更高性能、更高质量的游戏引擎,更多基于英特尔® x86 Android* 平台的精彩游戏产品。

其它相关文章与资源

基于英特尔® 架构的 Android* 模拟器的加速
初步了解英特尔® Composer XE 2013、编译器指示与指南
概述: 英特尔® SIMD 流指令扩展 2 (英特尔® SSE2)
使用 #pragma SIMD 进行循环矢量化的要求
使用 SIMD 流指令扩展创建粒子系统

 

如需深入了解面向安卓开发人员的英特尔工具,请访问:面向安卓的英特尔® 开发人员专区

英特尔、英特尔标识和凌动是英特尔公司在美国和/或其他国家的商标。
版权所有 © 2013 年英特尔公司。 保留所有权利。
*其他的名称和品牌可能是其他所有者的资产。

 

1在性能检测过程中涉及的软件及其性能只有在英特尔微处理器的架构下方能得到优化。 诸如 SYSmark* 和 MobileMark* 等性能测试均系基于特定计算机系统、组件、软件、操作及功能。 上述任何要素的变动都有可能导致测试结果的变化。 请参考其他信息及性能测试(包括结合其他产品使用时的运行性能)以对目标产品进行全面评估。
配置: [描述配置 + 测试内容 + 测试者]。 如欲了解更多信息,请访问:http://www.intel.com/performance

2在性能检测过程中涉及的软件及其性能只有在英特尔微处理器的架构下方能得到优化。 诸如 SYSmark* 和 MobileMark* 等性能测试均系基于特定计算机系统、组件、软件、操作及功能。 上述任何要素的变动都有可能导致测试结果的变化。 请参考其他信息及性能测试(包括结合其他产品使用时的运行性能)以对目标产品进行全面评估。
配置: [描述配置 + 测试内容 + 测试者]。 如欲了解更多信息,请访问:http://www.intel.com/performance

Per informazioni complete sulle ottimizzazioni del compilatore, consultare l'Avviso sull'ottimizzazione