对象的性能影响

时间:2014-03-16 16:02:42

标签: c++ c performance

我是一名初学程序员,具有一些c和c ++编程经验。我被大学指派为物理模拟器,所以你可能会想到有很强调性能。

我的问题如下:

  1. 实例数据成员访问的程序集指令数 通过指针转换为(例如矢量 - > x)?
  2. 是不是说另一种方法,你只需访问 记忆通过说一个char *(在变量的相同内存位置) x),还是一样的?
  3. 对性能有很大影响 编译方式,如果我使用一个对象来访问该内存位置或 如果我只是访问它?
  4. 关于这个问题的另一个问题是 是否访问堆内存比堆栈内存更快 访问?

4 个答案:

答案 0 :(得分:7)

C ++是一种编译语言。无论是指向对象的指针还是指向char*的指针,通过指针访问存储器位置都是相同的 - 在任何一种情况下它都是一条指令。有几个地方C ++增加了开销,但它总是给你带来一些灵活性。例如,调用虚函数需要额外的间接级别。但是,如果要使用函数指针模拟虚函数,则无论如何都需要相同的间接,或者如果要使用switch或{{{{}序列模拟它,则需要花费相同数量的CPU周期。 1}} S上。

通常,在知道要优化的代码部分之前,不应该开始优化。通常,只有一小部分代码负责程序使用的大部分CPU时间。在分析代码之前,您不知道要优化哪个部分。几乎普遍的程序员代码,而不是C ++的语言特性,负责减速。确切知道的唯一方法是分析。

答案 1 :(得分:3)

  1. 在x86上,指针访问通常是一条额外的指令,超出了您通常需要执行操作的范围(ex y = object->x;将是object中地址的一次加载,和x的一个加载,以及y的一个存储 - 在x86汇编程序中,加载和存储都是带有内存目标的mov指令。有时它会零"指令,因为编译器可以优化掉对象指针的负载。在其他体系结构中,它实际上取决于体系结构的工作原理 - 一些体系结构访问内存和/或将地址加载到指针等方面的方式非常有限,这使得访问指针变得尴尬。

  2. 指令数完全相同 - 这适用于所有

  3. 作为#2 - 对象本身没有任何影响。

  4. 堆内存和堆栈内存是同一种类。一个答案说"堆栈内存总是在caceh",如果它位于堆栈顶部附近,那么这是真的,#34;所有活动都会继续,但是如果你有一个传递的对象是在main中创建的,并且指向它的指针用于传递几个函数调用层,然后通过指针访问,那么显然有机会这段记忆已经使用了很长时间,因此也没有真正的区别。最大的区别是"堆内存空间充足,堆栈有限"随着堆的耗尽,可以进行有限的恢复,堆栈的耗尽会立即执行[没有非常便携的技巧]"

  5. 如果你将class看作C中struct的同义词(除了一些细节,它们确实是这样),那么你会发现class和对象并不是真的添加任何额外的努力"到生成的代码。

    当然,如果使用得当,C ++可以更容易地编写代码来处理那些以非常类似的方式执行此操作的代码,但却略有不同"。在C中,您经常会得到:

       void drawStuff(Shape *shapes, int count)
       {
         for(i = 0; i < count; i++)
         {
            switch (shapes[i].shapeType)
           {
           case Circle:
             ... code to draw a circle ... 
             break;
           case Rectangle:
             ... code to draw a rectangle ... 
             break;
           case Square:
             ... 
             break;
           case Triangle:
             ...
             break;
           }
         }
       }
    

    在C ++中,我们可以在对象创建时和你的&#34; drawStuff&#34; becoems:

    void drawStuff(std::vector<Shape*> shapes)
    {
       for(auto s : shapes)
       {
          s->Draw();
       }
    }
    

    &#34;看马云,没有转换......&#34; ;) (当然,你确实需要一个开关或者其他东西来选择要创建的对象,但是一旦做出选择,假设你的对象和周围的体系结构都有明确的定义,那么一切都应该工作,并且神奇地等等。上面的例子)。

    最后,如果它对性能有重要意义,那么运行基准测试,运行性能分析并检查代码花费时间的位置。不要太早优化(但如果你有严格的性能标准,请密切关注它,因为决定项目的最后一周,你需要重新组织你的数据和代码,因为性能很糟糕一些不好的决定也不是最好的想法!)。并且不要针对个别指令进行优化,查看花费的时间,并在需要的地方提出更好的算法。 (在上面的例子中,使用const std::vector<Shape*>& shapes将有效地传递指向传入的形状向量的指针,而不是复制整个东西 - 如果shapes中有几千个元素,这可能会有所不同。

答案 2 :(得分:1)

  • 这取决于您的目标架构。 C中的结构(以及C ++中的类)只是一个包含顺序成员的内存块。通过指针访问这样的字段意味着向指针添加偏移量并从那里加载。许多体系结构允许加载已经指定了目标地址的偏移量,这意味着那里没有性能损失;但是即使在没有这种情况的极端RISC机器上,添加偏移应该是如此便宜,以至于负载完全遮住它。
  • 堆栈和堆内存实际上是一回事。只是不同的领域。因此,它们的基本访问速度是相同的。主要区别在于堆栈很可能已经在缓存中,无论如何,如果最近没有访问堆内存可能不是。

答案 3 :(得分:0)

  1. 变量。在大多数处理器上,指令被转换为称为微代码的东西,类似于Java字节代码在运行之前如何转换为特定于处理器的指令。您获得的实际指令数量因不同的处理器制造商和型号而异。
  2. 与上面相同,它取决于我们大多数人都知之甚少的处理器内部。
  3. 1 + 2。您应该问的是这些操作需要多少个时钟周期。在现代平台上,答案是一个。无论它们有多少指令都是无关紧要的,现代处理器都有优化,可以在一个时钟周期内运行。我不会在这里详细介绍。换句话说,在谈论CPU负载时,根本没有任何区别。

    1. 这里有一个棘手的部分。虽然指令本身占用的时钟周期没有区别,但它需要在内存运行之前从内存中获取数据 - 这可能需要大量的时钟周期。实际上有人在几年前证明,即使使用非常优化的程序,x86处理器也至少花费50%的时间等待内存访问。

    2. 当你使用堆栈内存时,实际上你正在创建一个结构数组。对于数据,除非您具有虚拟功能,否则不会复制指令。这使得数据对齐,如果您要进行顺序访问,您将获得最佳的缓存命中。使用堆内存时,您将创建一个指针数组,每个对象都有自己的内存。此内存不会对齐,因此顺序访问会有很多缓存未命中。缓存未命中是您的应用程序真正的速度,应该不惜一切代价避免。

    3. 我不确切知道你在做什么,但在很多情况下,即使使用对象也比普通数组慢得多。一个对象数组是对齐的[object1] [object2]等。如果你为每个对象执行类似伪代码的操作o {o.setX()= o.getX()+ 1}“......这意味着你只会因此,访问一个变量,您的顺序访问将跳过每个对象中的其他变量,并获得比您的X变量在其自己的数组中对齐的更多缓存未命中。如果您的代码使用对象中的所有变量,则标准数组不会比对象数组慢。它只会将不同的数组加载到不同的缓存块中。

      虽然标准数组在C ++中速度更快,但在Java等其他语言中它们要快得多,在这些语言中,您永远不应将批量数据存储在对象中 - 因为Java对象使用更多内存并始终存储在堆中。这是C ++程序员在Java中最常见的错误,然后抱怨Java很慢。但是,如果他们知道如何编写最佳的C ++程序,他们就会将数据存储在Java中与C ++一样快的数组中。

      我通常做的是一个存储数据的类,它包含数组。即使你使用堆,它只是一个对象,它变得和使用堆栈一样快。然后我有类“myitem {private:int pos; mydata data; public getVar1(){return data.getVar1(pos);}}”。我不会在这里写出所有代码,只是说明了我是如何做到这一点的。然后,当我迭代它时,迭代器类实际上不会为每个项返回一个新的myitem实例,它会增加pos值并返回相同的对象。这意味着您可以获得一个不错的OO API,而实际上只有一些对象和良好对齐的数组。这种模式是C ++中最快的模式,如果你不在Java中使用它,你就会知道痛苦。

      我们获得多个函数调用的事实并不重要。现代处理器具有称为分支预测的东西,这将消除绝大多数呼叫的成本。在代码实际运行之前很久,分支预测器就会弄清楚调用链的作用,并用生成的微码中的单个调用替换它们。

      即使所有调用都运行,每个调用它们所需的内存访问时间也会少得多,正如我所指出的那样,内存对齐是唯一一个应该打扰你的问题。