【问题标题】:"Optimal" function parameterisation in CC中的“最佳”函数参数化
【发布时间】:2020-10-18 16:21:57
【问题描述】:

我的问题本质上是这样的:我何时以及为什么应该传递一个指针。例如,我知道如果我想使用函数在别处修改一些数据,我将需要传递一个指针,这样我才能真正更改那块内存。那么当我需要修改范围外的变量而不是其他情况时只传递指针的标准是什么?那么结构呢,它可能是相当大的内存块?传递一个地址大小的指针(通常是 8 个字节)作为参数还是我的 50 字节结构会更好吗?这还重要吗?

TL;DR 是我在定义函数的参数时需要记住的(除了显而易见的,即我需要对函数做什么)?

【问题讨论】:

  • @Adrian Mole 不,阅读 TL;DR。我知道按引用传递和按值传递之间的区别(该帖子的最佳答案似乎忘记了 C :P)
  • 示例:在嵌入式设备上编程,在许多函数中传递具有大数组的结构。您只想在堆栈上分配一次,因此使用指针,否则每个函数调用都会在堆栈上构建一个大数组。
  • 数组的大小应该不重要吧?这只是一个指针。这些东西也在堆上,我将同时拥有成千上万个这样的结构。

标签: c function parameters


【解决方案1】:

在早期,K&R C 甚至不允许您按值传递结构。创建副本的唯一方法是通过memcpy()(或您自己的实现)。 ISO C 随后定义了复制和分配,但传统观点认为您确实希望避免复制数据:内存访问成本很高。

这个原则在今天更加“真实”,但我们从中得出的结论却被颠覆了:我们有时会更多地显式地复制以避免不必要的隐式“通读”一直到 RAM。原因是现代内核具有缓存,可以以比 RAM 本身低得多的延迟进行访问,并且计算变得非常便宜。我们要确保缓存内容是独立的,并且不需要昂贵的刷新/直写。

在并行处理和多核 CPU 的情况下,aliasing(通过不同的标识符访问相同的内存,这就是你使用指针所做的)阻止了对本地缓存副本的独立操作内存,因为不同的线程或核心可能已写入它。同步缓存的成本比较大。

如果每个核心或线程都可以对自己的本地数据副本进行操作,他们就不需要等待,可以专注于手头的任务,可以这么说。这种独立性的好处往往超过了初始、明确复制的成本,达到了惊人的程度。本质上移动数据副本的函数式语言最近受到了更多关注,这正是因为它们的范式本质上使程序看起来像一个与数据无关的任务的集合,这使得并行化在某种程度上更容易,甚至允许自动并行化。

底线:即使在用命令式语言(如 C)编写的单线程程序中,处理数据副本也可以让编译器生成更高效的代码,这可能会超过一开始就显式复制所带来的损失。

【讨论】:

    【解决方案2】:

    正如您所说,指针只有 4 或 8 个字节,因此传递指针将需要更少的内存来复制。当然,指针访问会花费更多时间,但是当结构变得更大时,很难说对于相对较小的结构(对于现代机器来说 50 字节仍然有点小),什么会更快更好。

    总而言之,在大多数情况下,您不会注意到性能差异,除非您有更大尺寸的结构(假设从 100 多个字节开始)。

    当然,当您需要修改结构时,无论哪种方式都需要指针。

    编辑:在我得到 cmets 之前,100+ 字节是一个总的猜测,它可能比 50 还要多或已经值了。

    【讨论】:

    • 我没有考虑指针访问时间。关于结构也有好处,因为我的都不是特别庞大的,尽管有些可能包含结构数组,其中还包含结构数组等等......我可能有千字节的结构(可能是数百最疯狂的千字节)。这会开始严重影响性能吗?那时通过引用传递也会产生影响吗? (Adrian 指出编译器可能只是制作一个本地副本以简化频繁的取消引用)
    • 如果你有一个包含结构数组的结构,那没关系,因为数组是一个指针,所以也只会复制指针,不会增加结构的大小
    • 是的......我不知道我在想什么。来晚了
    • 当然编译器在优化方面会做得很好(比如本地副本),这就是为什么差异通常不会很明显,是的,千字节结构可能会损害性能,但我怀疑这种情况经常发生,但是引用肯定会更好(尽管如果编译器也为我们优化了它,我不会感到惊讶)
    猜你喜欢
    • 1970-01-01
    • 2017-03-28
    • 1970-01-01
    • 2023-03-08
    • 2022-01-25
    • 2014-12-10
    • 1970-01-01
    • 1970-01-01
    • 2013-11-21
    相关资源
    最近更新 更多