【问题标题】:Is class data brought into memory every time it is accessed from a member function?每次从成员函数访问类数据时是否将其带入内存?
【发布时间】:2018-02-06 06:55:51
【问题描述】:

在函数'doSomething'中,'number'每次递增都会被带入缓存吗?

编辑: 我问是因为我遇到了 Mike Acton 的一些 cmets。他发表了this 评论,然后建议使用本地寄存器。 Here 是 cmets 的全部集合。

    class a
    {
    public:
        int number;
        void doSomething();
    }

    void a::doSomething()
    {
        for (int i = 0; i < 50; ++i)
        {
            number += i;
        }
    }

    int main()
    {
        a;
        a.doSomething();
        return 0;
    }

【问题讨论】:

  • 我会说它会留在缓存中...这就是缓存的作用:将不断被访问的数据保存在处理器附近的快速内存中。跨度>
  • 就您而言,它已经在内存中。 操作系统 将根据使用情况对其进行分页和分页。它与编译器或语言没有任何关系。
  • 而且我当然不建议获取并浏览 57 页具有这样标题的文件,但迈克·阿克顿,不管他是谁,只是问了一个愚蠢的问题。在内部循环中写入类成员没有任何问题。 当且仅当确定这是一个性能瓶颈,您可能会考虑使用寄存器,但如果涉及其他线程,您将不得不担心语义正确性。一般原则是编写简单的代码,让编译器担心性能,除非另有说明。
  • @EJP - 是的,而且......我读了大约一半的内容并确定 Mike Acton,无论他是谁,都不是他认为的代码审查之神。有些建议很好(虽然很明显)......其他的不那么热门。 OP 应该深思熟虑,运行微实验并查看生成的代码(如有必要)

标签: c++ caching


【解决方案1】:

你问的是第 45 张幻灯片,但我会评论所有这些幻灯片。

  • 幻灯片 7:这里没有浮点到整数的转换。有一个整数到浮点数的转换。
  • 幻灯片 11:这里没有“疯狂的分支”。由于源代码冗余,我当然会省略elses,但无论如何,没有一个像样的编译器会为它们生成分支。即使这样做了,它们也不会被占用,因此它们只需要空间,而不是时间。
  • 幻灯片 12-16 和 17-20 和 21-23 是关于应用程序语义而非一般原则的单一投诉,而不是分别有五个/四个/三个。
  • 幻灯片 24-28 陈述了一种愚蠢的观点,即 virtual 的“良好”用途“很难获得”。
  • 幻灯片 29 是另一个无聊的咆哮。为显式常数使用名称并没有错,尤其是宇宙的物理常数或数学常数。有关常见示例,请参阅 PI。
  • 幻灯片 31 与幻灯片 12 至 23 完全相反。
  • 幻灯片 32 可能是毫无意义的微优化。 m 是一个归纳变量,因此很有可能它已经被编译器优化掉了。
  • 幻灯片 35 似乎也与幻灯片 12 到 23 相矛盾。
  • 幻灯片 45:在内部循环中写入类成员没有任何问题,并且隐含的建议使用寄存器或局部变量,无论此处打算使用什么,都有空间成本和语义风险。
  • 幻灯片 46:几十年来,C 和 C++ 编译器一直忽略 register 声明。考虑到语义约束的足够信息,编译器完全有能力为自己进行这种优化。
  • 幻灯片 47-50:难以理解。
  • 幻灯片 51:可能的语义变化,未解决。
  • 幻灯片 53-4:哦,这条评论没用。 enum 值本身并不是“强制 [e] 不需要的分支”。需要提供适当的解释,但未提供。
  • 幻灯片 55:引用不是“毫无意义的废话”,也不是返回一个。
  • 幻灯片 56:难以理解。
  • 幻灯片 57:不专业且令人反感。

我想我已经有 20 多年没有读过这样的垃圾了。我不得不认真质疑每期使用四张和五张便利贴的人的心态。而且他很少提供实际的解决方案。

【讨论】:

  • Mike Acton 尤其是 AAA 游戏开发者社区中的多产人物,他目前是 Unity DOTs 的负责人。在此之前,他是 Insomniac Games 的引擎总监。我绝对不会阻止他,他有非常真实的经历。虽然他可以有一种相当对抗的风格:)
  • 也许他擅长其他语言,但他对 C++ 内部原理知之甚少,更糟糕的是,他认为自己懂。正如一位优秀得多的程序员所说,过早的优化是万恶之源。
  • 他在高性能游戏引擎领域工作。像“过早的优化是万恶之源”这样的谚语在这种情况下是不合适的,判断引擎程序员价值的主要标准之一是性能。你为什么说他不懂 C++ 编译器的内部原理?再一次,他确实在控制台上发布了 AAA、开放世界游戏,并在 Unity 领导 DOTS --> 其核心技术是一种特殊的编译器 (BURST),它可以优化和 SIMID 化 C# 代码的子集......令人惊讶的是,这些都是多么具有争议性答案/cmets 是。
【解决方案2】:

最有可能的是,如果实际发出了循环,它会在第一次访问时被带入缓存,并一直留在那里直到循环结束(同样,可能 - 编译器对此没有特别的控制,缓存通常通过普通内存访问透明地引入)。

请注意,这里的编译器也可以将代码转换为:

int n = number;
for (int i = 0; i < 50; ++i)
{
    n += i;
}
number = n;

因为循环中没有函数调用可以看到number 的中间状态(也没有强制中间状态对其他线程可见的内存屏障)。 n 这里可能是一个寄存器,所以缓存不相关。

最后,几个现代编译器实际上会将其优化为:

number += 1225;

所以没有太多需要担心的缓存访问。

【讨论】:

  • 感谢您的回复,我怀疑会是这样,但是,是否保证编译器会这样做?我刚刚添加了一个编辑,希望能为我的问题提供更多背景信息。
  • 保持缓存,正如我所说,是处理器的工作,编译器对此无能为力。对于其他优化,一如既往地没有严格的保证。在诸如此类的微不足道的情况下,几乎可以肯定会应用某种程度的优化;在更复杂的情况下(例如存在内存屏障或对其他函数的调用),编译器可能没有被授权提升加载/存储(因为它可能会改变语义),或者可能不够聪明,无法看到它会无害。在这些情况下,您可以自己执行优化。
  • 不过,一般来说,尽可能保持对局部变量的修改是不错的建议;除了将内容放在寄存器中之外,通常还可以通过缩小其思想范围来帮助优化器。
猜你喜欢
  • 2022-01-10
  • 1970-01-01
  • 2015-04-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-03
  • 1970-01-01
  • 1970-01-01
  • 2012-10-08
相关资源
最近更新 更多