Этот указатель и штраф за исполнение

void do_something() {....}

struct dummy
{
   //even I dont call this, compiler will call it fall me, they need it
   void call_do_something() { this->do_something_member(); } 
   void do_something() {....}
};

В соответствии с тем, что я знаю, каждый класс или структура в С++ будет подразумевать вызов этот указатель, если вы хотите получить доступ к элементу данных или функции-члена класса, это приведет к снижению производительности на С++?

Я имею в виду

int main()
{
  do_something(); //don't need this pointer
  dummy().call_do_something(); //assume the inline is prefect

  return 0;
}

call_do_something нужен этот указатель для вызова функции-члена, но C, как do_something, не нужен этот указатель, привел бы этот указатель некоторое снижение производительности при сравнении с C-подобной функцией?

У меня нет смысла делать какую-либо микро-оптимизацию, так как это может вызвать у меня столько но всегда не приносят мне хорошего результата, я всегда придерживаюсь правила "меры, не думаю". Я хочу знать, что этот указатель принесет штраф за исполнение или нет из-за любопытства.

Ответ 1

Зависит от ситуации, но обычно, если вы включили оптимизацию, она не должна быть более дорогой, чем версия C. Единственный раз, когда вы действительно "платите" за this и другие функции, - это когда вы используете наследование и виртуальные функции. Помимо этого, компилятор достаточно умен, чтобы не тратить время на this в функции, которую вы не используете. Рассмотрим следующее:

#include <iostream>

void globalDoStuff()
{
    std::cout << "Hello world!\n";
}

struct Dummy
{
    void doStuff() { callGlobalDoStuff(); }
    void callGlobalDoStuff() { globalDoStuff(); }
};

int main()
{
    globalDoStuff();

    Dummy d;
    d.doStuff();
}

Скомпилированный с уровнем оптимизации GCC O3, я получаю следующую разборку (сокращение лишнего мусора и просто показ main()):

_main:
0000000100000dd0    pushq   %rbp
0000000100000dd1    movq    %rsp,%rbp
0000000100000dd4    pushq   %r14
0000000100000dd6    pushq   %rbx
0000000100000dd7    movq    0x0000025a(%rip),%rbx
0000000100000dde    leaq    0x000000d1(%rip),%r14
0000000100000de5    movq    %rbx,%rdi
0000000100000de8    movq    %r14,%rsi
0000000100000deb    callq   0x100000e62 # bypasses globalDoStuff() and just prints "Hello world!\n"
0000000100000df0    movq    %rbx,%rdi
0000000100000df3    movq    %r14,%rsi
0000000100000df6    callq   0x100000e62 # bypasses globalDoStuff() and just prints "Hello world!\n"
0000000100000dfb    xorl    %eax,%eax
0000000100000dfd    popq    %rbx
0000000100000dfe    popq    %r14
0000000100000e00    popq    %rbp
0000000100000e01    ret

Обратите внимание, что он полностью оптимизировал как Dummy, так и globalDoStuff() и просто заменил его телом globalDoStuff(). globalDoStuff() никогда не называется, а Dummy никогда не строится. Вместо этого компилятор/оптимизатор заменяет этот код двумя системными вызовами, чтобы распечатать "Hello world!\n" напрямую. Урок состоит в том, что компилятор и оптимизатор довольно умны, и в целом вы не будете платить за то, что вам не нужно.

С другой стороны, представьте, что у вас есть функция-член, которая управляет переменной-членом Dummy. Вы могли бы подумать, что у этого есть штраф по сравнению с функцией C, правильно? Вероятно, нет, потому что функции C требуется указатель на объект для изменения, который, когда вы думаете об этом, является именно тем, с чего должен начинаться указатель this.

Итак, вы вообще не будете платить за this в С++ по сравнению с C. Виртуальные функции могут иметь (небольшое) наказание, поскольку он должен искать правильную функцию для вызова, но это не тот случай, re рассматривая здесь.

Если вы не включите оптимизацию в своем компиляторе, то да, конечно, может быть наказание, но... почему вы сравниваете неоптимизированный код?

Ответ 2

#include <iostream>
#include <stdint.h>
#include <limits.h>
struct Dummy {
  uint32_t counter;
  Dummy(): counter(0) {}
  void do_something() {
    counter++;
  }
};

uint32_t counter = 0;

void do_something() { counter++; }

int main(int argc, char **argv) {
  Dummy dummy;
  if (argc == 1) {
    for (int i = 0; i < INT_MAX - 1; i++) {
      for (int j = 0; j < 1; j++) {
        do_something();
      }   
    }   
  } else {
    for (int i = 0; i < INT_MAX - 1; i++) {
      for (int j = 0; j < 1; j++) {
        dummy.do_something();
      }   
    }   
    counter = dummy.counter;
  }
  std::cout << counter << std::endl;
  return 0;
}

Среднее значение 10 запусков на gcc версии 4.3.5 (Debian 4.3.5-4), 64 бит, без каких-либо флагов:

с глобальным счетчиком: 0m15.062s

с фиктивным объектом: 0m21.259s

Если я изменю код, подобный этому, Lyth предложил:

#include <iostream>
#include <stdint.h>
#include <limits.h>

uint32_t counter = 0;

struct Dummy {
  void do_something() {
    counter++;
  }
};


void do_something() { counter++; }

int main(int argc, char **argv) {
  Dummy dummy;
  if (argc == 1) {
    for (int i = 0; i < INT_MAX; i++) {
        do_something();
    }   
  } else {
    for (int i = 0; i < INT_MAX; i++) {
        dummy.do_something();
    }   
  }
  std::cout << counter << std::endl;
  return 0;
}

Тогда, как ни странно,

с глобальным счетчиком: 0m12.062s

с фиктивным объектом: 0m11.860s