C++编程:使用GCC内置函数__builtin_prefetch优化内存访问的实测与分析

0. 引言

C++编程中,内存访问的效率往往是影响程序运行速度的重要因素之一。现代CPU为了减轻内存访问的瓶颈,采用了多级缓存机制,通过合理使用缓存可以显著提升程序的性能。
本文将探讨如何使用__builtin_prefetch这一GCC提供的内置函数,来优化内存访问模式,并通过实际测试评估其效果。

1. __builtin_prefetch的基本原理与用法

__builtin_prefetch是GCC编译器提供的一个内置函数,用于在数据被真正访问之前,将其提前加载到CPU缓存中,从而减少内存访问延迟,提高程序执行速度。其函数原型如下:

void __builtin_prefetch(const void *addr, int rw = 0, int locality = 3);
  • addr:指向需要预取的数据的指针。
  • rw:指定数据是用于读取(0)还是写入(1)。
  • locality:指定数据的时间局部性,取值范围为03,数值越高表示数据可能在短时间内被再次访问。

通过手动预取,__builtin_prefetch能够减少读取延迟并提高性能。然而,它也需要CPU的支持,且使用场景需要根据具体的硬件架构和程序特性来确定。以下是一个简单的使用示例:

#include <iostream>

void prefetchArray(const int* array, int size) {
    for (int i = 0; i < size; ++i) {
        __builtin_prefetch(&array[i + 1], 0, 3); // 预取下一个数组元素
        std::cout << array[i] << " "; // 假设这里有一些对数组元素的操作
    }
}

int main() {
    const int size = 10000;
    int array[size];

    // 初始化数组
    for (int i = 0; i < size; ++i) {
        array[i] = i;
    }

    // 调用带有预取指令的数组遍历函数
    prefetchArray(array, size);

    return 0;
}

在这个示例中,__builtin_prefetch用于提前请求处理器预取数组中的下一个元素。这种预取策略可以在数据访问的顺序性较强的情况下减少缓存未命中的概率。

2. __builtin_prefetch的高级用法与系统应用

在Linux内核中,预抓取技术被广泛应用,通常通过宏和包装器函数来使用__builtin_prefetch。以下是一个在Linux内核中实现流操作预抓取的辅助函数示例:

#ifndef ARCH_HAS_PREFETCH
#define prefetch(x) __builtin_prefetch(x)
#endif

static inline void prefetch_range(void *addr, size_t len) {
#ifdef ARCH_HAS_PREFETCH
    char *cp;
    char *end = addr + len;

    for (cp = addr; cp < end; cp += PREFETCH_STRIDE)
        prefetch(cp);
#endif
}

该函数使用内置函数的包装器(见./linux/include/linux/prefetch.h),实现了对指定地址范围的数据进行预抓取,通常可以减少缓存缺失和停顿,从而提高性能。

3. 实测__builtin_prefetch的性能影响

为了评估__builtin_prefetch的实际性能效果,我们进行了以下测试。在一个包含两百万个元素的数组上,分别在不使用和使用__builtin_prefetch的情况下,计算数组元素之和,并测量其耗时。测试在Linux ARM A53平台上进行,分别使用-O2和-O3编译优化选项。

测试代码如下:

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

#define ARRAY_SIZE 2000000

int main() {
    uint32_t arr[ARRAY_SIZE];

    // 随机生成一个数组
    srand(time(NULL));
    for (uint32_t i = 0; i < ARRAY_SIZE; i++) {
        arr[i] = rand() % 100;
    }

    // 计算数组元素之和,不使用预取
    clock_t start = clock();
    uint32_t sum = 0;
    for (uint32_t i = 0; i < ARRAY_SIZE; i++) {
        sum += arr[i];
    }
    clock_t end = clock();
    printf("Without prefetching, sum = %d, time = %ld\n", sum, end - start);

    // 计算数组元素之和,使用预取
    start = clock();
    sum = 0;
    for (uint32_t i = 0; i < ARRAY_SIZE; i++) {
        __builtin_prefetch(&arr[i + 10]);
        sum += arr[i];
    }
    end = clock();
    printf("With prefetching, sum = %d, time = %ld\n", sum, end - start);

    return 0;
}

测试结果如下:

  • O2优化:

    • 不使用预取:耗时约 5658 - 5866 ms
    • 使用预取:耗时约 5639 - 6182 ms
  • O3优化:

    • 不使用预取:耗时约 3977 - 6451 ms
    • 使用预取:耗时约 5599 - 6788 ms

从测试结果中可以观察到以下几点:

  1. 在-O2优化的情况下,使用__builtin_prefetch可以在某些情况下略微提升性能,但效果并不显著。
  2. 在-O3优化的情况下,编译器已经进行了大量优化,使用__builtin_prefetch反而可能导致性能下降。

4. __builtin_prefetch的适用场景与注意事项

虽然__builtin_prefetch在某些场景下可以优化内存访问,但其效果高度依赖于具体的硬件架构和数据访问模式。在现代处理器中,编译器的高级优化(如-O3)往往能够更智能地安排数据预取,因此手动使用__builtin_prefetch未必能带来额外的性能提升。

此外,在一些工业标准(如汽车行业的ISO 26262)中,可能出于安全性考虑不允许使用-O3优化。此时,使用__builtin_prefetch来手动优化程序的性能仍具有实际意义。

5. 结论

__builtin_prefetch可以用于在特定场景下优化内存访问模式,减少缓存未命中并提高程序性能。然而,其效果依赖于多种因素,手动预取并不总能带来显著的性能改进。进行实际测试和性能分析是判断其适用性的关键。

6. 参考

数据预取 __builtin_prefetch()
__builtin_prefetch(x)有什么用

  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值