我有两种情况,在这两种情况下,我分配78*2大小的内存(int(并将其初始化为0。表演方面有什么不同吗?
场景A:
int ** v = calloc(2 , sizeof(int*));
for (i=0; i<2; ++i)
{
v[i] = calloc(78, sizeof(int));
}
场景B:
int ** v = calloc(78 , sizeof(int*));
for (i=0; i<78; ++i)
{
v[i] = calloc(2, sizeof(int));
}
我认为,就性能而言,如果需要初始化数组,最好使用calloc,如果错误,请告诉我
首先,抽象地讨论优化有一些困难,因为编译器在优化方面越来越好。(出于某种原因,编译器开发人员不会停止改进它们。(我们并不总是知道给定的源代码会产生什么机器代码,尤其是当我们今天编写源代码并期望它在未来的许多年中使用时。优化可以将多个步骤合并为一个步骤,或者可以省略不必要的步骤(例如在存储器在for
循环中被完全重写之前立即用calloc
而不是malloc
清除存储器(。源代码名义上所说的("按照这个特定的顺序执行这些特定的步骤"(和它在语言抽象中所说的技术("以某种优化的方式计算与源代码相同的结果"(之间的差异越来越大。
然而,我们通常可以认为,在没有不必要的步骤的情况下编写源代码至少与在有不必要的过程的情况下写源代码一样好。考虑到这一点,让我们考虑一下您的场景中的标称步骤。
在场景A中,我们告诉计算机:
- 分配2个
int *
,清除它们,并将它们的地址放在v
中 - 两次,分配78个
int
,清除它们,并将它们的地址放在前面的int *
中
在场景B中,我们告诉计算机:
- 分配78个
int *
,清除它们,并将它们的地址放在v
中 - 78次,分配两个
int
,清除它们,并将它们的地址放在前一个int *
中
我们可以很容易地看到两件事:
- 这两种情况都清除了
int *
的内存,并立即用其他数据填充它。这是浪费;在将内存设置为其他值之前,不需要将其设置为零。只需将其设置为其他内容即可。使用malloc
,而不是calloc
。malloc
只取一个参数作为大小,而不是两个相乘的参数,因此用malloc(2 * sizeof (int *))
替换calloc(2, sizeof (int *))
。(此外,要将分配与正在分配的指针联系起来,请使用int **v = malloc(2 * sizeof *v);
,而不是单独重复类型。( - 在场景B做78件事的步骤中,场景A做两件事,但代码在其他方面非常相似,因此场景A的步骤更少。如果两者都能达到某种目的,那么A可能更可取
然而,这两种情况都暗示了另一个问题。据推测,所谓的数组将在稍后的程序中使用,其形式可能类似于v[i][j]
。使用此值表示:
- 获取指针
v
- 计算超出此范围的
i
元素 - 获取该位置的指针
- 计算超出此范围的
j
元素 - 在该位置获取
int
让我们考虑一种不同的方式来定义v
:int (*v)[78] = malloc(2 * sizeof *v);
。
上面写着:
- 为78个
int
的2个阵列分配空间,并将它们的地址放在v
中
我们立即看到,与场景A或场景B相比,这涉及的步骤更少。但也要看看它对使用v[i][j]
作为值的步骤有什么影响。因为v
是指向数组的指针,而不是指向指针的指针,所以计算机可以计算合适的元素在哪里,而不必从内存加载地址:
- 获取指针
v
- 计算
i
•78个元素 - 计算超出此范围的
j
元素 - 在该位置获取
int
所以这个指向数组版本的指针比指向指针版本的指针少一步。
此外,对于v[i][j]
的每次使用,指针到指针版本需要从存储器进行额外的提取。相对于乘法和加法等处理器内操作,从内存中获取可能会很昂贵,因此这是一个很好的消除步骤。必须获取指针可能会阻止处理器根据最近的使用模式预测下一次内存加载的位置。此外,指向数组的指针版本将2×78数组的所有元素放在内存中,这有利于提高缓存性能。处理器也被设计用于有效地使用连续存储器。对于指针到指针版本,单独的分配通常会导致行之间至少有一些分隔,并且可能有很多分隔,这可能会破坏连续使用内存的好处。