【问题标题】:Efficient way to scan struct nested arrays扫描结构嵌套数组的有效方法
【发布时间】:2023-03-30 20:29:01
【问题描述】:

我有一个包含多个数组成员的结构:

typedef myData someStruct {
    uint16_t array1 [ARRAY_LENGTH]
    uint16_t array2 [ARRAY_LENGTH]
} myData;
myData testData = {0};  // Global struct

在我的程序中的某个时刻,我需要将数组设置为一组预定义的值,例如,将 array1 设置为全 0,将 array2 设置为全 0xFF,等等。我的第一直觉是写出一个 for 循环,例如:

void someFunction (myData * test) {
    for (uint16_t i = 0; i < ARRAY_LENGTH; ++i) {
        test->array1[i] = 0xFF;
        test->array2[i] = 0xCC;
    }
}

然后我推断程序执行此操作所需的操作类似于:

load address of array1 first position
set value 0xFF;
load far address of array2 first postion
set value 0xCC;
load far address of array1 second position
set value 0xFF;
// and so on...

然而,如果我为每个数组使用单独的循环,地址会彼此更接近(因为数组和结构是连续存储的),所以地址每次只加载到下一个字节,使代码实际上更高效如下:

void someFunction (myData * test) {
    uint16_t i = 0;
    for (i; i < ARRAY_LENGTH; ++i)
        test->array1[i] = 0xFF;
    for (i = 0; i < ARRAY_LENGTH; ++i)
        test->array2[i] = 0xCC;
}

我的推理是否正确,第二个更好吗?此外,编译器(例如 gcc)通常能够自己进行这种优化吗?

【问题讨论】:

  • 我假设数组在进程堆栈上
  • 为什么不喜欢这样的memset memset(&mys.a,0xff,sizeof(mys.a)); memset(&mys.b,0xcc,sizeof(mys.b));
  • 我的意思是:以下语句是否正确:“地址加载每次只到下一个字节,使代码实际上更高效如下”? [我实际上不知道答案...我假设从特定地址读取内存值应该花费相同的时间,而与地址值无关]
  • @kripanand memset 将设置每个字节。他需要设置单词。
  • @Toby,那你为什么不能从一开始就正确初始化它们呢?

标签: c arrays for-loop performance


【解决方案1】:

这将取决于您的系统架构。例如,在 SPARC 系统上,高速缓存行大小为 64 字节,两个阵列都有足够的高速缓存插槽,因此第一个版本会很有效。第一个数组元素的加载将填充缓存,随后的加载将非常快。如果编译器足够聪明,它也可以使用预取。

在支持偏移寻址的 ISA 上,它实际上并不是每次都获取数组元素的地址,它只是增加一个偏移量。所以它只获取数组的基地址一次,然后使用带有基址和偏移量的加载指令。每次通过循环时,它都会增加寄存器中的偏移量。一些指令集甚至具有自增功能。

最好的办法是编写一个示例程序/函数,然后尝试一下。如此低级别的优化需要对 CPU/系统有透彻的了解,或者需要大量的反复试验。

【讨论】:

    【解决方案2】:

    我的谦虚建议:试试看。一个循环解决方案节省了围绕 i 的增量和测试的算术运算。两个循环可能会受益于更好的缓存优化,特别是如果数组与内存页面对齐。在这种情况下,每次访问都可能导致缓存未命中和缓存重新加载。就个人而言,如果速度真的很重要,我更喜欢两个循环,有些展开。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-05
      • 1970-01-01
      • 2019-05-20
      • 2013-12-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多