数据对齐有助于实现向量化

签署人: Ronald W Green

已发布:04/09/2013   最后更新时间:04/09/2013

面向 MIC Compi 的英特尔® Composer XE

面向英特尔® MIC 架构的编译器方法

面向英特尔® MIC 架构的编译器方法
向量化要点、数据对齐有助于实现向量化

概述

数据对齐是一种强制编译器在特定字节边界的内存中创建数据对象的方法,旨在提高处理器进行数据加载和数据存储的效率。在无需了解详细信息的情况下,处理器可高效迁移特定字节边界上内存地址的数据。对于英特尔® 集成众核架构(英特尔® MIC 架构),如英特尔® 至强融核™ 协处理器而言,当数据的起始地址为 64 字节边界时,内存数据移动可达到最佳状态。因此,应强制编译器创建起始地址为 64 字节模的数据对象。

除了在对齐边界上创建数据,编译器能够在数据实现 64 字节对齐时进行优化。默认情况下,当数据在当前范围外创建时,编译器不知道也无法假定数据的对齐情况。因此,您必须通过指令 (C/C++) 或指示 (Fortran) 通知编译器数据对齐,这样编译器便可生成最佳代码。一个例外是,Fortran 模块数据在使用站点收到对齐信息。

综上所述,需要以下两个步骤:

  1. 对齐数据

  2. 在性能关键区使用指令/指示,用数据通知编译器参数已对齐

主题

1) 对齐数据

数据对齐对性能提升至关重要。通知编译器代码关键区的数据对齐信息也非常重要。如果您对齐数据后不通知编译器,可能会获得次优代码和/或更长的编译时间。

如何定义对齐的静态数组

静态声明 64 字节边界上 1000 元单精度浮点数组 A 的示例,适合

  • Windows C/C++,
    __declspec(align(64)) float A[1000];

  • Linux/Mac C/C++,
    float A[1000] __attribute__((aligned(64)));

  • Fortran 简单数组:-align arraynbyte:对于 Fortran,对齐数组数据最简单的方式是使用编译器选项 -align array64byte,使所有数组,包括静态数组和动态数组在内,在 64 位边界上对齐。该选项将对齐常用块中的数据,而不是派生类型的元素。您也可以使用指令对齐数组。借助您代码中的指令,无需使用 -align array64byte 编译器选项,在声明变量的代码中直接调用数据对齐。例如:

    real :: A(1000)
    !dir$ attributes align: 64:: A

  • Fortran 常用数据:-align zcommons 通过按需添加填充字节将所有公共块实体在 32 字节边界上对齐。这对于 MIC 而言不是理想的数据对齐方式,但却是 AVX 的理想之选。对于 MIC 而言,您有 50% 的机会实现全 64 字节对齐。
    注:填充字节可能会中断许多假定公共实体已封装且没有填充的传统应用。因此,请务必检查来自您应用的结果,以确保正确的编译器选项。

  • Fortran 派生类型数据元素:-align recnbyte:使用编译器选项 -align rec64byte,将包含在面向 MIC 的最佳 64 位边界上派生类型中的数据元素对齐。该选项将记录结构中派生类型和字段的组成部分在 (n) 规定的边界上对齐。第一个后面的所有派生类型元素储存在成员类型大小或 n 字节边界上,以较小者为准。

  • Fortran 模块数据:
    module mymod
    real, allocatable :: a(:), b(:)
    !dir$ attributes align:64 :: a
    !dir$ attributes align:64 :: b
    ...
    end module mymod

动态数据对齐

C/C++:使用指定对齐的 _mm_malloc() 和 _mm_free() 代替 malloc() 和 free()。 参数是相同的。这些英特尔 C++ Composer XE 提供的 _mm_ 替换项使用相同的参数并返回 malloc() 和 free() 类型。_mm_malloc() 返回的数据将是 64 字节对齐。

  • _aligned_malloc()

  • _mm_malloc() and _mm_free()

Fortran: 如上所述,使用 -align arraynbyte 和 -align recnbyte 编译器选项

2) 通知编译器数据对齐

现在,您已对齐了您的数据,这时需要通知编译器数据已在程序具体使用数据的地方对齐。例如,当您将数据作为参数传递至关键函数或子程序时,编译器如何知道该参数对齐或非对齐呢?该信息必须由用户提供,因为编译器没有任何关于参数的信息。

C/C++ 中的示例: 对于特定的变量,使用 __assume_aligned 宏,通知编译器特定变量或参数已对齐。例如,要通知编译器作为参数传输的数组或全局数据已对齐,您应执行以下操作:

void myfunc( double p[] )
{
   __assume_aligned(p, 64);
   for (int i=0; i<n; i++)
   {
      p[i]++;
   }
}

void myfunc2( double *p2, double *p3, double *p4, int n) 
{
   for (int j=0; j<n; j+=8) {
      __assume_aligned(p2, 64);
      __assume_aligned(p3, 64);
      __assume_aligned(p4, 64);
      p2[j:8] = p3[j:8] * p4[j:8];
   }
}

Fortran 中的示例:使用指令 ASSUME_ALIGNED.通用语法:

cDEC$ ASSUME_ALIGNED address1:n1 [, address2:n2]...

c

是 c、C、! 或 *。(请参阅编译器指令的语法规则)

地址

内存引用。它可以是任何数据类型、种类或等级。不可以是以下几种:

  • 公共实体(相当于公共资源的实体)
  • 派生类型或记录字段引用变量的组成部分
  • 通过使用或主机关联访问的实体

n

正整数初始化表达式。它的值必须是 1 到 256 之间 2 的幂数,即 1、2、4、8、16、32、64、128 和 256。

如果您规定多个 address:n 项目,必须用逗号隔开。如果地址是 Cray 指针或拥有指针属性,则它是指针而不是被指向的对象或假定对齐的对象。如果您指定了一个无效的 n 值,则会显示错误信息。

!DIR$ ASSUME_ALIGNED A: 64

do i=1, N

A(I) = A(I) + 1

end do

!DIR$ ASSUME_ALIGNED A: 64
A = A + 1

如何通知向量化工具所有内存引用都已面向对象对齐

(使用英特尔 Composer XE 2011)可将更常见的指令或指示放在循环的前面,以通知编译器循环中的所有数据都已对齐。这样,您就无需使用上述方法指定每个变量。

C/C++ 中的示例

#pragma vector aligned

for (i=0; i<n; i++){

A[i] = B[i] * C[i] + D[i];

}

//在阵列符号 stmt 前添加指令,指定被使用数组的对齐
#pragma vector aligned

A[0:n] = B[0:n] * C[0:n] + D[0:n];

Examples in Fortran:

!DIR$ VECTOR ALIGNED

do I=1, N

A(I) = B(I) * C(I) + D(I)

end do

!DIR$ VECTOR ALIGNED
A = B * C + D

一些注释:这些语句会忽略编译器向量化工具的启发式效率。这些语句导致编译器针对所有数组引用使用对齐数据移动指令。这些语句禁用编译器的所有高级对齐优化,如从程序环境确定对齐属性,或使用动态循环剥离来对齐引用。请谨慎使用这些语句。如果某些访问模式实际上没有对齐,利用对齐数据移动指令指示编译器实施所有数组引用将导致运行时异常

混合的对齐与非对齐访问:如何通知向量化工具所有 RHS 内存引用均为 64 字节对齐。

float p, p1;
__assume_aligned(p,64);
__assume_aligned(p1,64);
__assume(n1%16==0);
__assume(n2%16==0);
__assume(n3%16==0);
__assume(n4%16==0);
__assume(n5%16==0);

for(i=0;i<n;i++){
   q[i] = p[i]+p[i+n1]+p[i+n2]+p[i+n3]+p[i+n4]+p[i+n5];
}
for(i=0;i<n;i++){
   q1[i] = p1[i]+p1[i+n1]+p1[i+n2]+p1[i+n3]+p1[i+n4]+p1[i+n5];
}

对于 C++ 程序(使用 icpc 编译), 存在 __assume 语句无法按照预期运行的已知问题(在 C++ 中, bool 类型为 8 位,会误导向量化工具)。该问题在13.0 编译器产品更新 1 中得到了解决。

如果您使用早期的编译器,请不要使用:

__assume(idx_B % 16 == 0);
__assume(loc1 % 16 == 0);
__assume(loc2 % 16 == 0);
__assume(stride % 16 == 0);

您可以使用以下方法:

__assume_aligned((void*)idx_A, 16);
__assume_aligned((void*)idx_B, 16);
__assume_aligned((void*)loc1, 16);
__assume_aligned((void*)loc2, 16);

__assume_aligned((void*)stride, 16);

要点

数据对齐是强制编译器在特定字节边界的内存上创建数据对象的一种方法。旨在提高处理器进行数据加载和数据存储的效率。对于英特尔® 集成众核架构(英特尔® MIC 架构),如英特尔® 至强融核™ 协处理器而言,当数据的起始地址为 64 字节边界时,内存数据移动可达到最佳状态。

除了在对齐边界上创建数据,编译器能够在数据实现 64 字节对齐时进行优化。因此,您必须通过指令 (C/C++) 或指示 (Fortran) 通知编译器数据对齐,这样编译器便可生成最佳代码。一个例外是,Fortran 模块数据在使用站点收到对齐信息。

下一步

要在英特尔® 至强融核™ 架构上成功调试您的应用,请务必通读此指南,并点击文中的超链接查看相关内容。本指南提供了实现最佳应用性能所要执行的步骤。

返回到主章节向量化要素

产品和性能信息

1

性能因用途、配置和其他因素而异。请访问 www.Intel.com/PerformanceIndex 了解更多信息。