调试优化的代码

使用架构段和运行时架构检查

Comments

创建一个向后兼容早期的硬件型号、同时又能高效兼容最新硬件型号的应用程序,这可能是我们要解决的一个难题。在本文中,我们将探索此问题的各种解决方案,包括使用 XL C/C++ V2R1M1 中引入的 架构段 (Architecture Section)运行时架构检查 (Run-Time Architecture Check)

利用 z/OS XL C/C++ 编译器,应用程序可以通过使用 -qARCH 选项(比如 –qARCH=5)编译源文件来支持早期的 System z 架构。但是,通过使用 –qARCH 选项进行编译,会要求编译器为指定的架构级别生成代码,而无法利用新架构中的指令。

应用程序如何提供向后兼容的二进制代码,同时利用更高级的架构功能呢?

在引入架构段 之前,解决方案是创建一个特定于架构的源文件,如下所示:

  1. 使用 -qARCH(N) 编辑主要应用程序代码
  2. 使用 -qARCH(X) 编译用于架构调优的源文件,其中 X > N。
  3. 链接和执行

考虑下面这个示例:

main.c

#define _POSIX_SOURCE
#include <sys/utsname.h>

#include <stdlib.h>
#include <stdio.h>

#define ARCH9 2217

// Counts bits that have the value of 1 in a byte
unsigned long myBytePopcount(char op)
{
    unsigned long count = 0;
    for (int i = 0; i < 8; i++) {

        if ((op & 1) == 1) {
            count++;
        }
        op >>= 1;
    }
    return count;
}

int main()
{
    struct utsname runOn;
    uname(&runOn);
    int archLevel = atoi(runOn.machine);

    unsigned char input = 55;
    unsigned long output;

    // Check architecture level
    if (archLevel >= ARCH9) {
        // External call to ARCH(9) compiled function
        output = callArch9Builtin(input)&0xFF;
    } else {
        output = myBytePopcount(input);
    }
    printf("%lu\n", output);

    return 0;
}

popcnt.c

#include <builtins.h>

// Compiled with –qARCH=9 and use a z196 Hardware Builtin-In function
unsigned long callArch9Builtin(unsigned long op) {
    return __popcnt(op);
}

编译和执行:

 xlc –qARCH=9 –c –o popcnt.o popcnt.c  
 xlc –qARCH=5 –c –o main.o main.c 
xlc –o a.out popcnt.o main.o
./a.out

上面的示例一个字节中具有值 1 的位数(人口统计)。在运行时期间,在 main.c 中,使用 uname 库检查架构级别。如果检测到系统为 ARCH(9) (IBM z196) 或更高版本的系统,则会调用外部函数 callArch9Builtin。函数 callArch9Builtin 使用 __popcnt 内置函数 (BIF) 来进行人口统计。如果检测到架构级别小于 ARCH(9),则会调用用户定义的函数 myBytePopcount

尽管此解决方案解决了创建一个向后兼容 ARCH(5) 的应用程序的问题,并使用了硬件内置函数来提高效率,但它也有一些不足之处。

此方法的不足之处:

  • 为了在一个二进制文件中支持多种架构,特定于架构的代码必须放在专门的源文件中。在上面的例子中,ARCH(9) 定义位于一个单独的源文件 popcnt.c 中,而 main.c 代码是使用 ARCH(5) 编译的。尽管在此示例中可能不太明显,但此方法降低了代码可读性,增加了要维护的源文件数量,可能增加了代码重复,而且由于调用了函数而导致存在调用开销。
  • uname 库让此代码仅在 z/OS 上启用了 POSIX 的环境中启用。

架构段

如果首次尝试使用两个源文件,会得到两个编译单元,一个使用 ARCH(9) 编译,另一个使用 ARCH(5) 编译。

XL C/C++ V2R1M1 和 z/OS V2R2 XL C/C++ 编译器引入了一个称为架构段 的新特性,它使用户能够在一个编译单元中为多个架构生成代码。

架构段是一些代码块,可使用以下信息来标识:

#pragma arch_section(architecture_level) …statement…

在 arch_section 中指定的 architecture_level 必须大于所提供的架构选项。

下面这个示例修改了示例 1 的 main 函数,添加了一个架构段:

main.c

#define _POSIX_SOURCE
#include <sys/utsname.h>

#include <builtins.h>

#include <stdlib.h>
#include <stdio.h>

#define ARCH9 2217

int main()
{
    unsigned char input = 55;
    unsigned long output = 0;

    struct utsname runOn;
    uname(&runOn);
    int archLevel = atoi(runOn.machine);

    if (archLevel >= ARCH9)
    #pragma arch_section(9){ 
        output = __popcnt(input)&0xFF;
    }  else { 
        output = myPopCountOnByte(input);
    }

    printf("%lu\n", output);
}

编译和执行:

 xlc –qARCH=5 –c –o main.o main.c 
xlc –o a.out main.o
./a.out

该架构段中的代码块会生成 ARCH(9) 指令,但该架构段外的代码会生成 ARCH(5) 指令。

使用 #pragma arch_section 指令时,有一些细节应该注意一下:

  • #pragma arch_section 指令只能用在语句级别上
  • #pragma arch_section 指令不能放在函数定义前面
  • 特定于架构的类型(比如 Vector 数据类型 (vector unsigned int))可在架构段外进行声明,但在架构编译器选项或架构段不支持这些类型时,不能在架构段外引用它们。
  • Decimal 和 Vector 等 IBM 扩展(如果用在 #pragma arch_section 中)需要在调用编译器时指定关联的选项。

仍然有一处潜在的改进,而且这一处改进与对 uname 库的依赖相关。

运行时架构检查

XL C/C++ V2R1M1 编译器添加了对一个运行时架构检查库的支持。该库提供了一组内置函数 (BIF),在进入架构段之前,这些函数在运行时执行了快速高效的安全检查。

要使用该库,首先要调用:

void __builtin_cpu_init ( void )

此函数将会检测硬件级别,所以应在您的应用程序中调用一次。

调用上述函数后,可使用两个函数来确定运行该应用程序的硬件。

  1. int __builtin_cpu_supports(const char* feature)

如果运行时 CPU 支持指定的特性,此内置函数返回一个正整数,否则返回 0。如果 __builtin_cpu_init function 未被调用,此内置函数将会返回 0。受支持的 feature 参数为:

  • "longdisplacemnt"
  • "etf2"
  • "etf3"
  • "dfp"
  • "prefetch"
  • "storeclockfast"
  • "loadstoreoncond"
  • "popcount"
  • "interlocked"
  • "tx"
  • "dfpzoned"
  • "vector128"
  • "5" through "11"

如果需要一个特定的架构,还可以使用下面这个函数:

  1. int __builtin_cpu_is(const char* cpumodel)

如果运行时 CPU 支持指定的 CPU 型号(架构级别),此内置函数返回一个正整数,否则返回 0。可用的参数为 "5"、"6"、"7"、"8"、"9"、"10"、"11" 中的一个。

我们现在可以将 uname 库替换为运行时架构检查库,将这些函数应用于我们之前的解决方案中,如下所示:

main.c

#include <builtins.h>

unsigned long myPopCountOnByte(unsigned long op)
{
    unsigned int count = 0;
    for (int i = 0; i < 8; i++) {
    
        if ((op & 1) == 1) {
            count++;
        }
        op >>= 1;
    }
    return count;
}

int main()
{
    unsigned char input = 55;
    unsigned long output;

    __builtin_cpu_init(); 

    if  (__builtin_cpu_supports("popcount") ) 
    #pragma arch_section(9) {
        output = __popcnt(input)&0xFF;
    } 
    else
    {
        output = myPopCountOnByte(input);
    }
    printf("%lu\n", output);

    return 0;
}

编译和执行:

xlc –c –qARCH=5 –o main.o main.c
xlc –o a.out main.o
./a.out

结束语

希望上述示例演示了架构段和运行时架构检查的潜在使用情形。

借助架构段,您无需在单独的源文件中维护为某个特定硬件型号而自定义的代码。此外,借助运行时架构检查,您拥有一个能快速高效地确定硬件级别的内置编译器。

有关架构段和运行时架构检查的更多信息,请访问下面的参考资料。

致谢

感谢以下人员对完成本文的:Zibi Sarbinowski、Visda Vokhshoori 和 Kobi Vinayagamoorthy。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Rational
ArticleID=1017687
ArticleTitle=调试优化的代码
publish-date=10212015