【问题标题】:for-loop optimization using pointer使用指针的for循环优化
【发布时间】:2013-08-20 04:27:02
【问题描述】:

我正在尝试优化代码以在 7 秒内运行。我把它降到了 8,现在我正在尝试使用指针来加速代码。但是当我尝试编译时 gcc 给出了一个错误:

.c:29: 警告:来自不兼容指针类型的赋值 .c:29: 警告:不同指针类型的比较缺少强制转换

这是我在尝试使用指针之前所拥有的:

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

#define N_TIMES     600000
#define ARRAY_SIZE   10000

int main (void)
{
    double  *array = calloc(ARRAY_SIZE, sizeof(double));
    double  sum = 0;
    int     i;

    double sum1 = 0;

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

        int     j;

        for (j = 0; j < ARRAY_SIZE; j += 20) {
            sum += array[j] + array[j+1] + array[j+2] + array[j+3] + array[j+4] + array[j+5] + array[j+6] + array[j+7] + array[j+8] + array[j+9];
            sum1 += array[j+10] + array[j+11] + array[j+12] + array[j+13] + array[j+14] + array[j+15] + array[j+16] + array[j+17] + array[j+18] + array[j+19];
            }

        }

    sum += sum1;

    return 0;
}

这是我使用指针时的结果(此代码会产生错误):

int     *j;

        for (j = array; j < &array[ARRAY_SIZE]; j += 20) {
            sum += *j + *(j+1) + *(j+2) + *(j+3) + *(j+4) + *(j+5) + *(j+6) + *(j+7) + *(j+8) + *(j+9);
            sum1 += *(j+10) + *(j+11) + *(j+12) + *(j+13) + *(j+14) + *(j+15) + *(j+16) + *(j+17) + *(j+18) + *(j+19);
            }

如何解决此错误?顺便说一句,我不想​​要关于尝试优化代码的替代方法的建议。这是一个家庭作业问题,限制了我可以做什么。我想一旦我修复了这个指针问题,它会在 7 秒内运行,我会很高兴的。

【问题讨论】:

标签: c pointers optimization for-loop


【解决方案1】:

不同指针类型的比较缺少强制转换

这意味着您尝试将一种类型的指针与另一种类型的指针进行比较,并且没有进行强制转换。

double  *array = calloc(ARRAY_SIZE, sizeof(double));
int     *j;

指向double 的指针和指向int 的指针不能直接比较。出于这个原因,您不能将jarray 进行比较。也许您打算将j 声明为指向double 的指针?

【讨论】:

  • 但是我不是将一个 int 指针与一个同样是 int 的地址进行比较吗?
  • @chillpenguin:指针通常实现为地址。从概念上讲,指针不是地址。考虑一个指向 int "x" 的指针——表达式++x 必须将“地址值”x 增加sizeof(int),而不是 1(假设地址被用于实现指针)。
  • 如果我把 j 做成一个双指针,这样行吗?还是我必须投射东西?
  • @chillpenguin:最常见的可能不将指针作为内存地址实现的机器是那些使用分段的机器。 en.wikipedia.org/wiki/Memory_segmentation
  • @chillpenguin:如果你打算从j读取doubles,那么j确实需要是指向double的指针。正如现在实现的那样,假设 int 是 32 位,double 是 64 位(典型),您不会添加 doubles。 :)
【解决方案2】:

C 是一种静态类型语言,跨指针类型的比较会给你错误。在某些情况下存在一些隐式转换,例如将 double 与 int 进行比较,因为比较数字是一种常见操作。比较不同类型的指针不是。

此外,当您在数组上增加指针时,它会使用其取消引用元素的大小来了解要在内存中移动多远。在双精度数组上移动 int 会导致问题。

double 会比 int 移动得更远,所以无论如何你都会得到更多与 int 指针的交互。

您可以显式地强制转换,但实际上您应该使用双 * 来表示双精度数组。

【讨论】:

  • 没有“隐式转换”这样的东西。强制转换是对编译器的请求,要么忽略类型系统中的某些内容,要么生成代码以执行转换。也许您的意思是指“通常的算术 conversions”(C11 6.3.1.8)?
  • 你是对的。我的老师和书称这些隐式转换,但这不是它们在 C 标准中的描述方式。您介意我编辑我的评论以更准确地反映这一点吗?
【解决方案3】:

如果从数组表示转移到指针表示会产生很多(如果有的话)加速,我会感到非常惊讶,因为两者都是最终输出代码中的内存地址(和内存偏移量)。请记住,数组表示实际上也是不同服装中的指针表示。

相反,我会考虑以下两种技术之一:

  1. 嵌入式 MMX 表示,在同一时钟周期下在同一寄存器内执行多个加法运算。然后,您需要在接近尾声时进行一次操作,以将高双精度与低双精度结合起来。

  2. 分散/聚集算法将加法运算分散到多个内核(如今几乎每个 CPU 都有 4 个内核可用,如果不是 16 个伪内核(超线程))

除此之外,您还可以尝试进行一些缓存分析,并将中间体存储在不同的寄存器中。在您的每个计算中似乎都有一个很深的加法链。将它们分解可能会产生将 CPU 上的存储分布到更多寄存器的能力。

大多数操作都受内存限制。 20 是循环展开的一个非常奇怪的边界。双精度数可能是 16 位,因此 20 个双精度数是 320 位,这可能与您的内存高速缓存行大小不一致。尝试确保展开循环的倍数与架构的 1 级缓存完全对齐,并且在跨缓存边界读取时可能会避免页面错误。这样做会加快你的程序一些(但谁知道多少)。

【讨论】:

  • 在 C 中,int[]int* 是不同的类型。数组不是指针,即使它们在传递给函数时衰减为指针。对于这段代码最重要的是,优化器可以对数组做出假设,而对指针则无法做出假设。
  • 我同意,由于数组在 C 类型系统中允许的额外提示,编译器可能会更好地处理数组。 C 类型系统无法在转换为汇编/机器代码时存活下来(太糟糕了)。从我组装的日子开始,如果发现数组偏移量以address + offset 以外的任何其他形式实现,那将是相当令人震惊的,这是指针数学所做的,但没有数组做出的额外保证。
  • 我修复了错误,现在可以正常编译了。顺便说一句,它确实运行得更快。从 8 秒缩短到 5.8 秒。
  • 嗯,证明在测试中(当涉及到优化时)。感谢您的更新,我会认为我的假设完全错误。这样分析很有趣。很高兴听到您的成功。
  • 我在课堂上学习了如何使用指针来遍历数组,但我不记得为什么它更快...我只记得我的老师说你需要在这个作业中使用它来打破7 秒。就像你说的,证据正在测试中!
【解决方案4】:

“当你在一个数组上增加一个指针时,它使用它的取消引用元素的大小来知道在内存中移动多远。在一个双精度数组上移动一个 int 会导致问题”。

为了避免您的警告:执行以下操作

for (j= (int *)array; j < (int *)&array[ARRAY_SIZE]; j += 20)

【讨论】:

  • 相反,我只是将 j 设为双倍。效果也不错吧?
  • @chillpenguin,正确,因为两个变量具有相同的数据类型,因此无需转换。好电话!
猜你喜欢
  • 2021-06-22
  • 1970-01-01
  • 2011-08-30
  • 1970-01-01
  • 2018-07-27
  • 1970-01-01
  • 1970-01-01
  • 2015-04-15
相关资源
最近更新 更多