Могут ли компиляторы C++ оптимизировать повторяющиеся вызовы виртуальных функций для одного и того же указателя?

Предположим, у меня есть следующий код

void f(PolymorphicType *p)
{
    for (int i = 0; i < 1000; ++i)
    {
        p->virtualMethod(something);
    }
}

Будет ли сгенерированный компилятором код разыменовывать запись p vtable virtualMethod 1 или 1000 раз? Я использую компилятор Microsoft.

изменить

вот сгенерированная сборка для реального случая, который я рассматриваю. line->addPoint() - это виртуальный метод беспокойства. У меня нет опыта сборки, поэтому я потихоньку осваиваю...

; 369  :        for (int i = 0; i < numPts; ++i)

    test    ebx, ebx
    je  SHORT $LN1@RDS_SCANNE
    lea edi, DWORD PTR [ecx+32]
    npad    2
$LL3@RDS_SCANNE:

; 370  :        {
; 371  :            double *pts = pPoints[i].SystemXYZ;
; 372  :            line->addPoint(pts[0], pts[1], pts[2]);

    fld QWORD PTR [edi+8]
    mov eax, DWORD PTR [esi]
    mov edx, DWORD PTR [eax+16]
    sub esp, 24                 ; 00000018H
    fstp    QWORD PTR [esp+16]
    mov ecx, esi
    fld QWORD PTR [edi]
    fstp    QWORD PTR [esp+8]
    fld QWORD PTR [edi-8]
    fstp    QWORD PTR [esp]
    call    edx
    add edi, 96                 ; 00000060H
    dec ebx
    jne SHORT $LL3@RDS_SCANNE
$LN314@RDS_SCANNE:

; 365  :        }

person japreiss    schedule 07.02.2013    source источник
comment
Попросите компилятор сгенерировать ассемблерный код и проверьте.   -  person Some programmer dude    schedule 07.02.2013
comment
Скомпилируйте его с оптимизацией и посмотрите на получившийся сгенерированный код.   -  person Petesh    schedule 07.02.2013
comment
Чтобы добавить к комментарию Иоахима - нет другого способа узнать, кроме проверки.   -  person Bartek Banachewicz    schedule 07.02.2013
comment
Кстати, у p нет виртуальной таблицы. PolymorphicType имеет виртуальную таблицу, а *p имеет указатель на эту виртуальную таблицу.   -  person Kerrek SB    schedule 07.02.2013
comment
... и даже если он разыменует указатель 1000 раз, это все равно займет всего микросекунду или две.   -  person Mr Lister    schedule 07.02.2013
comment
@MrLister: Ваша оценка высока. Поскольку вызов попадает в BTB, единственными накладными расходами является дополнительное декодирование (загруженное значение используется только для подтверждения попадания BTB), поэтому я ожидаю накладных расходов в 1/3 цикла на итерацию на современном процессоре x86. Или около 160 нс на частоте 2 ГГц.   -  person Chris Dodd    schedule 07.02.2013
comment
Я не уверен, что компилятору разрешено предполагать и здесь. Вызов -›virtualFunction может изменить то, на что указывает p, что, если объект, на который указывает p, будет удален, а в этом месте будет создан новый объект другого подтипа? (Я не знаю, возможно ли это или законно, но это то, о чем разработчикам компиляторов приходится думать, когда они генерируют код...), они должны быть ОЧЕНЬ осторожны в своих предположениях...   -  person jcoder    schedule 07.02.2013
comment
@ChrisDodd Ах, оптимизация на аппаратном уровне.   -  person Mr Lister    schedule 07.02.2013
comment
@jcoder Я полагаю, что какое-то правило псевдонима обходит эту проблему.   -  person Cory Nelson    schedule 07.02.2013
comment
Я предполагаю, что ответ может также зависеть от того, имеет ли указатель p или line автоматическую продолжительность хранения и создается ли когда-либо указатель или ссылка на этот указатель.   -  person aschepler    schedule 07.02.2013
comment
@Cory В самом деле, я просто подумал, что если бы вы писали компилятор, хотели бы вы предположить, что ничто из того, что делает вызываемая функция, не может изменить то, куда идет следующий вызов, учитывая все сложности С++, или вы бы просто решите загружать его каждый раз.... :) Я хочу сказать, что компилятору может быть очень сложно доказать, что это действительная оптимизация, если он не может видеть определение всего кода, вызываемого прямо и косвенно по функции.   -  person jcoder    schedule 07.02.2013
comment
Итак, mov edx, DWORD PTR [eax+16] ... call edx внутри цикла. Я думаю, он каждый раз смотрит на vtable.   -  person japreiss    schedule 07.02.2013
comment
@jcoder, я согласен. На вопрос достаточно легко ответить (скомпилировать в asm и проверить). Более важный вопрос, который ускользает от ответа, заключается в том, разрешено ли такое действие стандартом, охвачено ли оно вообще, или же оно полностью оставлено на усмотрение реализации. Предположение, что код, выдаваемый компилятором, является синонимом правил, установленных стандартом, является грандиозной идеей, но мне кажется, что в подобных вопросах это прикосновение телеги впереди лошади.   -  person WhozCraig    schedule 07.02.2013


Ответы (2)


В общем, нет, нельзя. Функция может уничтожить *this и разместить новый какой-либо другой объект, производный от той же базы в этом пространстве.

Редактировать: еще проще, функция может просто изменить p. Компилятор не может знать, у кого есть адрес p, если только он не является локальным для рассматриваемой единицы оптимизации.

person n. 1.8e9-where's-my-share m.    schedule 07.02.2013

В общем случае невозможно, но есть особые случаи, которые можно оптимизировать, особенно при межпроцедурном анализе. VS2012 с полной оптимизацией и оптимизацией всей программы компилирует эту программу:

#include <iostream>

using namespace std;

namespace {
struct A {
  virtual void foo() { cout << "A::foo\n"; }
};

struct B : public A {
  virtual void foo() { cout << "B::foo\n"; }
};

void test(A& a) {
  for (int i = 0; i < 100; ++i)
    a.foo();
}
}

int main() {
  B b;
  test(b);
}

to:

01251221  mov         esi,64h  
01251226  jmp         main+10h (01251230h)  
01251228  lea         esp,[esp]  
0125122F  nop  
01251230  mov         ecx,dword ptr ds:[1253044h]  
01251236  mov         edx,12531ACh  
0125123B  call        std::operator<<<std::char_traits<char> > (012516B0h)  
01251240  dec         esi  
01251241  jne         main+10h (01251230h)  

поэтому он эффективно оптимизировал цикл для:

for(int i = 0; i < 100; ++i)
  cout << "B::foo()\n";
person Casey    schedule 07.02.2013
comment
плюс 1 за оптимизацию всей программы/времени компоновки, что делает «невозможное» часто тривиально возможным. - person underscore_d; 20.05.2017