AS>>Оба раза this собирается в ecx. Разницу видите?
AS>>А все почему — в месте вызова AA2::AA1::foo неизвестно смещение AA1 относительно АА2, именно ввиду того, что АА2 является виртуальной базой, а значит смещение определяется иерархией наследования.
E>1. Посмотри куда передаётся управление, при виртуальном вызове вот в таком вот случае:struct C1 {
E> int F1;
E> virtual int foo() = 0;
E>};
E>struct C2 { int F2; };
E>struct C3 : C2, C1 {
E> int F3;
E> virtual int foo() { return F1 + F2 + F3; }
E>};
E>int testFoo( C1* p ) { return p->foo(); }
E>int test()
E>{
E> C3 c;
E> return testFoo( &c );
E>}
Посмотрел. К сожалению, в этом примере студия перемещает С1 в начало объекта С3, поэтому пример не работает. Но не беда, изменим тест.
struct C1 {
int F1;
virtual int foo() = 0;
};
struct C2 { int F2;
virtual void foo1()
{
}
};
struct C3 : C2, C1 {
int F3;
virtual int foo()
{
printf("dd");
return F1 + F2 + F3;
}
};
int testFoo( C1* p, C3 *p1)
{
void *p_0 = &p1->F1;
void *p_1 = &p1->F2;
void *p_2 = &p1->F3;
alloca(100);
printf("cc");
DebugBreak();
p->foo();
return p1->foo();
}
int test()
{
C3 c;
return testFoo( &c, & c);
}
Итак смотрим, что же там. А там все тривиально:
; COMDAT ?test@@YAHXZ
_TEXT SEGMENT
_c$ = -20
?test@@YAHXZ PROC NEAR ; test, COMDAT
; 1472 : {
sub esp, 20 ; 00000014H
; 1473 : C3 c;
; 1474 : return testFoo( &c, & c);
lea eax, DWORD PTR _c$[esp+20]
lea ecx, DWORD PTR _c$[esp+28]
neg eax
sbb eax, eax
lea edx, DWORD PTR _c$[esp+20]
and eax, ecx
push edx
push eax
mov DWORD PTR _c$[esp+28], OFFSET FLAT:??_7C3@@6BC2@@@ ; C3::`vftable'
mov DWORD PTR _c$[esp+36], OFFSET FLAT:??_7C3@@6BC1@@@ ; C3::`vftable'
call ?testFoo@@YAHPAUC1@@PAUC3@@@Z ; testFoo
; 1475 : }
add esp, 28 ; 0000001cH
ret 0
?test@@YAHXZ ENDP ; test
_TEXT ENDS
; COMDAT ?testFoo@@YAHPAUC1@@PAUC3@@@Z
_TEXT SEGMENT
_p$ = 8
_p1$ = 12
?testFoo@@YAHPAUC1@@PAUC3@@@Z PROC NEAR ; testFoo, COMDAT
; 1460 : {
push ebp
mov ebp, esp
; 1461 : void *p_0 = &p1->F1;
; 1462 : void *p_1 = &p1->F2;
; 1463 : void *p_2 = &p1->F3;
; 1464 : alloca(100);
mov eax, 100 ; 00000064H
call __alloca_probe
; 1465 : printf("cc");
push OFFSET FLAT:??_C@_02EMJB@cc?$AA@ ; `string'
call DWORD PTR __imp__printf
add esp, 4
; 1466 : DebugBreak();
call DWORD PTR __imp__DebugBreak@0
; 1467 : p->foo();
mov ecx, DWORD PTR _p$[ebp]
mov eax, DWORD PTR [ecx]
call DWORD PTR [eax]
; 1468 : return p1->foo();
mov ecx, DWORD PTR _p1$[ebp]
add ecx, 8
mov edx, DWORD PTR [ecx]
call DWORD PTR [edx]
; 1469 : }
lea esp, DWORD PTR [ebp]
pop ebp
ret 0
?testFoo@@YAHPAUC1@@PAUC3@@@Z ENDP ; testFoo
_TEXT ENDS
Первая кооректировка this происходит при касте C3 к C1. Что и понятно. Далее в testFoo (я позволил себе вставить маркерные функции в код, чтобы было видно, где мы находимся и не позволить оптимизировать компилятору "слишком просто") происходит корректировка C3 к C1 во втором вызове.
Теперь посмотрим, что из себя представляет виртуальные таблицы С3. Из кода test видно, что их две. foo1 нас не интересует, а foo только в одном экземпляре. Посмотрим, что в ней.
CONST SEGMENT
??_7C3@@6BC2@@@ DD FLAT:?foo1@C2@@UAEXXZ ; C3::`vftable'
CONST ENDS
; COMDAT ??_7C3@@6BC1@@@
CONST SEGMENT
??_7C3@@6BC1@@@ DD FLAT:?foo@C3@@UAEHXZ ; C3::`vftable'
CONST ENDS
_TEXT SEGMENT
?foo@C3@@UAEHXZ PROC NEAR ; C3::foo, COMDAT
; 1453 : {
push esi
mov esi, ecx
; 1454 : printf("dd");
push OFFSET FLAT:??_C@_02HICB@dd?$AA@ ; `string'
call DWORD PTR __imp__printf
; 1455 : return F1 + F2 + F3;
mov eax, DWORD PTR [esi+8]
mov edx, DWORD PTR [esi-4]
mov ecx, DWORD PTR [esi+4]
add esp, 4
add eax, edx
add eax, ecx
pop esi
; 1456 : }
ret 0
?foo@C3@@UAEHXZ ENDP ; C3::foo
_TEXT ENDS
Таким образом foo, изначально появившееся в С1, _всегда_ вызывается с this подобъекта С1, где бы он не находился. А уже конкретная реализация foo корректирует this так, чтобы он соответствовал реальному типу, в данном случае, С3. Она то его всегда знает. А поскольку в месте вызова foo компилятор всегда тем или иным способом может вычислить this для подобъекта, то никаких переходников и не нужно.
Итого. Никаких переходников — тут они не нужны. С другой стороны, я допускаю, что могут быть реализации, имеющие переходники, корректирующие this. Но даже в этом случае для виртуального наследования еще один уровень косвенности для корректировки this необходим ввиду причин, которые я описал ранее.
Т.о., вернувшись к изначальному тезису что виртуальное наследование несет перфоманс пенальти, можно отметить:
1. По вызовам.
2. По кастам.
3. По памяти.
Не так уж и мало, чтобы несколько раз подумать, прежде чем это использовать. Даже без учета "странностей" поведения.
PS Потрясающий способ ведения дискуссий. Пожалуй, на этом надо закругляться.
Здравствуйте, Andrew S, Вы писали:
AS>>>Разница в том, что параметры по умолчанию в этом случае имеют значение. Для реализации.
E>>Зачем считалке ссылок параметры конструктора?
AS>Кто сказал что виртуально наследоваться будут только считалки? Я такого не говорил.
А разве не такую альтернативу решения из ATL мы обсуждаем примерно
отсюдаАвтор: Erop
Дата: 05.10.09
?..
AS>Ну вот к примеру базовые методы а-ля AddRef/Release должны быть максимально производительными.
Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?
AS>А для "абстрактных" примитивов синхронизации не то что виртуальное наследование — даже виртуальный вызов бывает виден.
А при чём тут тогда интерфейсы и альтернативы ATL?..
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Andrew S, Вы писали:
AS>Я обсуждаю проблемы виртуального наследования. А не частные случаи.
А я твой тезис о безальтернативности решения из ATL...
E>>Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?
AS>Егор, право, неужели поиск отменили?
AS>http://www.rsdn.ru/article/devtools/CppPerformance.xmlАвтор(ы): Сергей Сацкий, Роман Плеханов
Дата: 31.07.2007
Сравнение производительности кода, сгенерированного различными компиляторами С++ на различных аппаратных платформах. За основу статьи взят материал отчета Technical Report on C++ Performance комитета WG21. Набор тестов расширен, в некоторых случаях предлагаемый код модифицирован. Приведен более подробный анализ возникающих накладных расходов.
Что-то как-то я там не нашёл
1) данных по MSVC
2) вообще случаев, когда вызов метода виртуальной базы значительно бы проигрывал бы вызову метода обычной базы...
Тем более, что если бы мы хотели бы сравнить решение с невиртуальным методом виртуальной базы и виртуальным методом невиртуальной базы, то именно такого сравнения вообще вроде бы нет. Но в любом случае мы имеем отличие в 20-25% от времени просто пустого виртуального вызова. Обычно сами по себе вызовы в нормальной программе занимают очень незначительную часть времени работы (нормальные программы либо что-то полезное делают, либо чего-то ждут, а не "хлопают дверками"), так что 20% экономии от незначительной доли времени работы программы -- это, как-то не впечатляет...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
AS>>Я обсуждаю проблемы виртуального наследования. А не частные случаи.
E>А я твой тезис о безальтернативности решения из ATL...
E>>>Ну так поделишься данными по реальной стоимости виртуальной базы? Насколько дольше-то? По сравнению с виртуальным вызовом и прологом функции, например?
AS>>Егор, право, неужели поиск отменили?
AS>>http://www.rsdn.ru/article/devtools/CppPerformance.xmlАвтор(ы): Сергей Сацкий, Роман Плеханов
Дата: 31.07.2007
Сравнение производительности кода, сгенерированного различными компиляторами С++ на различных аппаратных платформах. За основу статьи взят материал отчета Technical Report on C++ Performance комитета WG21. Набор тестов расширен, в некоторых случаях предлагаемый код модифицирован. Приведен более подробный анализ возникающих накладных расходов.
E>Что-то как-то я там не нашёл
E>1) данных по MSVC
И что? Напомню, кто-то утверждал, что виртуальное наследование не несет накладных расходов на вызов метода.
E>2) вообще случаев, когда вызов метода виртуальной базы значительно бы проигрывал бы вызову метода обычной базы...
http://rsdn.ru/article/devtools/CppPerformance.xml#EGOBGАвтор(ы): Сергей Сацкий, Роман Плеханов
Дата: 31.07.2007
Сравнение производительности кода, сгенерированного различными компиляторами С++ на различных аппаратных платформах. За основу статьи взят материал отчета Technical Report on C++ Performance комитета WG21. Набор тестов расширен, в некоторых случаях предлагаемый код модифицирован. Приведен более подробный анализ возникающих накладных расходов.
E>Тем более, что если бы мы хотели бы сравнить решение с невиртуальным методом виртуальной базы и виртуальным методом невиртуальной базы, то именно такого сравнения вообще вроде бы нет. Но в любом случае мы имеем отличие в 20-25% от времени просто пустого виртуального вызова. Обычно сами по себе вызовы в нормальной программе занимают очень незначительную часть времени работы (нормальные программы либо что-то полезное делают, либо чего-то ждут, а не "хлопают дверками"), так что 20% экономии от незначительной доли времени работы программы -- это, как-то не впечатляет...
Мы хотим сравнить виртуальный метод, разговор шел про абстрактные интерфейсы. Иное сравнивать по мне нет смысла (кстати, есть сомнения, что будет разница для обычных методов?). И не 20% экономии. Отношение от 30 до 80 процентов. Т.е. в плохом случае в ТРИ раза медленнее. На самом деле — примерно в 2 раза, поскольку есть фактически еще один vtbl. Спрашивается, нафига это для абстрактных интерфейсов, а тем более для их реализации?
Итого. Егор, надо уметь проигрывать. А лучше просто не начинать, не будучи уверенным в своих словах. Предлагаю на этом закруглиться, ничего нового я не узнал, и в таком ключе продолжать беседу мне скучно — для меня данная ветка закрыта. Удачи.
Здравствуйте, Andrew S, Вы писали:
AS>И что? Напомню, кто-то утверждал, что виртуальное наследование не несет накладных расходов на вызов метода.
Зависит от реализации...
AS>Мы хотим сравнить виртуальный метод, разговор шел про абстрактные интерфейсы. Иное сравнивать по мне нет смысла (кстати, есть сомнения, что будет разница для обычных методов?). И не 20% экономии. Отношение от 30 до 80 процентов. Т.е. в плохом случае в ТРИ раза медленнее. На самом деле — примерно в 2 раза, поскольку есть фактически еще один vtbl. Спрашивается, нафига это для абстрактных интерфейсов, а тем более для их реализации?
80 процентов не в том сценарии, который предлагалось использовать вообще-то. Кроме того даже эти 80% от очень маленького времени...
AS>Итого. Егор, надо уметь проигрывать. А лучше просто не начинать, не будучи уверенным в своих словах. Предлагаю на этом закруглиться, ничего нового я не узнал, и в таком ключе продолжать беседу мне скучно — для меня данная ветка закрыта. Удачи.
Ты всегда можешь не отвечать, если тебе не интересно
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском