[C++] Насколько большой может быть потеря в скорости при использовании
Ограничено только фантазией программиста
Переформулирую вопрос: ее умешьшить можно каким-нибудь способом? Если да, то каким?
Код покажи.
и что у тебя за свич-конструкции, и какие вирт.функции? из твоих слов это (мне) непонятно
{
public:
...
int type;
};
void draw(const X& x)
{
switch(x.type)
{
case 1: ...; break;
case 2: ...; break;
case N: ...; break;
default: ...;
}
}
Заменяется на
class X
{
public:
virtual void draw = 0;
};
class X1 : public X
{
public:
virtual void draw { ... }
};
class XN : public X
{
public:
virtual void draw { ... }
};
и что тебя удивляет? одно дело, сделать сравнение, джамп и call по адресу, другое дело - залезть в таблицу вирт функций, сделать поиск по ней, и уже оттуда call
какой еще нафиг поиск по таблице виртуальных функций?
Код рисования немаленький там, а скорость раза в два наверное упала.
прастой. Познее, знаете ли, связывание
Там поиск не производится, смещение уже заранее известно.
конечно, если скорость падает значительно на фоне долгих вычислений, мое объяснение явно неудачно. А так, на холостой работе, по идее, вирт.функции работают медленнее
и какое отношение позднее связывание имеет к виртуальным функциям?
навреное, объекты в памяти создаваться будут дольше во втором случае
ну блин
скока же у тебя классов? в смылсе чему N равно?
думается мне что дажа миллион классов будут создаваться меньше секунды
Не важно сколько классов, draw в цикле вызывается по всем объектам этих классов (которые уже созданы).
хм, оказывается "позднее связывание" - по разным источникам обозначает два совсем разных связывания.
Ну, от прыжка ты не избавишься, так что может от виртуальных функций придется отказаться.
не тупи
Чего-то не хочется, да и странно вообще, что вызов функции так резко программу замедляет.
1) Как я понимаю вызов виртуального метода всегда сбрасывает кэш.
2) Возможность инлайниться.
Все.
А так про switch vs virtual call просто -- чем больше свитчей, тем эта конструкция медленнее, чем виртуальный вызов. Еще по идее, если методы простые, и на кэш нагрузка небольшая, то виртуальная таблица будет сидеть в кэше, в этом случае и несколько свитчей будет медленнее. Если же метод сложный, то тогда вообще не так важно как быстро делается вызов.
Потому что у нас там прыжок по значению из памяти. Как ты предлагаешь предсказать переход по такому прыжку?
ИМХО в общем случае и для свитчей предсказание ветвления не очень работает.
Ну если мы прыгаем по адресу который не был предсказан, сбрасываются всякие кеши(вернее они правильно не заполняются ). Это же очевидно. Очередь инструкций, опять же...
короче все равно, в конце концов switch в такой ситуации это просто тупо по многим другим причинам
Ну если мы прыгаем по адресу который не был предсказан, сбрасываются всякие кешиПри чем тут кеши? Конвейер сбрасывается...
Мне тоже не ясно
Открываю страшную тайну: в 80х86 ассемблере есть инструкция jmp ax (есть ещё и jmp al, конечно, но мы не о ней=) так вот, если у тебя кейзы в свитче идут хорошо подряд (0..4096, например а компилятор грамотный, то прыжок займёт те же два такта.
Однако возможно получить линейное замедление производительности, конечно. Ещё можно забыть поставить break; (КСТАТИ! или даже запретить компайлеру реарранжить кейзы (есть такая опцыя).
Они всё равно сбрасываются...
Кэшы ынструкцый?адно не хочу я с вами спорить, никто нихрена не знает, все только обобщают свои бесценные знания архитекутуры 8086 на всё подряд. Кароче в сад.
В 8086 был кэш инструкций?
в 80х86 ассемблере есть инструкция jmp ax, ... то прыжок займёт те же два такта.какие еще в жопу 2 такта? причем это говорится тоном очень осведомленного человека
Опять же возвращаемся к
Кэшы ынструкцый?Куда и когда
Они всё равно сбрасываются...В этом заявлении тоже надо искать смысл, или все таки его нет?
Ну я не знаю, чему у него равен икс.
Всем спасибо, проблема похоже в другом.
вообще, вопрос был почему виртуальные медленнее
а обсуждать стали почему свичи медленнее
прикольно
Открываю страшную тайну: в 80х86 ассемблере есть инструкция jmp ax (есть ещё и jmp al, конечно, но мы не о ней=) так вот, если у тебя кейзы в свитче идут хорошо подряд (0..4096, например а компилятор грамотный, то прыжок займёт те же два такта.Про это подробней. Как код будет выглядеть? Ты сам видел или это в теории?
int xxxxx(int n) {
switch(n) {
case 0: return 1;
case 1: return 0;
case 2: return 3;
case 3: return 2;
default: return 4;
}
}
gcc -O3 как всегда облажался:
.globl _Z5xxxxxi
.type _Z5xxxxxi, @function
_Z5xxxxxi:
.LFB1744:
pushl %ebp
.LCFI14:
xorl %eax, %eax
movl %esp, %ebp
.LCFI15:
movl 8(%ebp %edx
cmpl $1, %edx
je .L274
jle .L284
cmpl $2, %edx
movl $3, %eax
je .L274
movl $2, %eax
cmpl $3, %edx
.L283:
je .L274
movl $4, %eax
.L274:
popl %ebp
ret
.p2align 47
.L284:
movl $1, %eax
testl %edx, %edx
jmp .L283
А студия как всегда рулит:
int __declspec(noinline) xxxxx(int n) {
switch(n) {
00401870 mov eax,dword ptr [esp+4]
00401874 cmp eax,3
00401877 ja $L66450+6 (401895h)
00401879 jmp dword ptr [eax*4+40189Ch]
case 0: return 1;
00401880 mov eax,1
}
}
00401885 ret
case 1: return 0;
00401886 xor eax,eax
}
}
00401888 ret
case 2: return 3;
00401889 mov eax,3
}
}
0040188E ret
case 3: return 2;
0040188F mov eax,2
}
}
00401894 ret
default: return 4;
00401895 mov eax,4
}
}
0040189A ret
Да здраствует open source
PS. Прыжок НЕ занимает 2 такта. К современному процессору вообще такое понятие не применимо "инструкция X занимает Y тактов"... Вон у gcc например зацените, ему показалось выгоднее пихать в eax все подряд и в нужный момент вернуться, чем пихнуть в него то, что нужно и вернуться. И по большому счету нет оснований ему не верить.
если есть под рукой icc - покажи что генерит он, пожалуйста
А если код в кейзах неравномерный по длине, то как будет: выравнивание по самому длинному, джампы-редиректы или забъет и скомпилирует нормально?
Ну то что gcc делает elsif elsif это в любом случае ненормально. Бинарный поиск я думаю сделает, щас попробуем...
int __declspec(noinline) xxxxx(int n) {
switch(n) {
00401870 mov eax,dword ptr [esp+4]
00401874 cmp eax,3E8h
00401879 jg xxxxx+32h (4018A2h)
0040187B je xxxxx+2Ch (40189Ch)
0040187D dec eax
0040187E je xxxxx+26h (401896h)
00401880 sub eax,9
00401883 je xxxxx+20h (401890h)
00401885 sub eax,5Ah
00401888 jne xxxxx+47h (4018B7h)
case 100: return 3;
0040188A mov eax,3
}
}
0040188F ret
case 10: return 2;
00401890 mov eax,2
}
}
00401895 ret
case 1: return 1;
00401896 mov eax,1
}
}
0040189B ret
case 1000: return 4;
0040189C mov eax,4
}
}
004018A1 ret
int __declspec(noinline) xxxxx(int n) {
switch(n) {
004018A2 cmp eax,2710h
004018A7 je xxxxx+59h (4018C9h)
004018A9 cmp eax,186A0h
004018AE je xxxxx+53h (4018C3h)
004018B0 cmp eax,0F4240h
004018B5 je xxxxx+4Dh (4018BDh)
default: return 8;
004018B7 mov eax,8
}
}
004018BC ret
case 1000000: return 7;
004018BD mov eax,7
}
}
004018C2 ret
case 100000: return 6;
004018C3 mov eax,6
}
}
004018C8 ret
case 10000: return 5;
004018C9 mov eax,5
}
}
004018CE ret
Ну да, по идее это и есть бинарный поиск.
А было по началу вот так:
int __declspec(noinline) xxxxx(int n) {
switch(n) {
case 1: return 1;
case 10: return 2;
case 100: return 3;
case 1000: return 4;
case 10000: return 5;
case 100000: return 6;
case 1000000: return 7;
default: return 8;
}
}
я имел в виду, что длина кода в кейзах будет разной
Компилеры, как всегда, тормозят, человек, как всегда, рулит:
int xxxxx(int n) {
switch(n) {
case 0: return 1;
case 1: return 0;
case 2: return 3;
case 3: return 2;
default: return 4;
}
}
movl 4(%ebp %eax
xorl $1, %eax
movl $4, %ebx
cmpl $3, %eax
cmoval %ebx, %eax
ret
8 дней придумывал?
Это третий вариант
gcc таки побыстрее работает, да и в любое время
gcc таки побыстрее работает, да и в любое времяКто б сомневался
Только за штучную и качественную ручную работу лучше платят
Как научат компилеры четыре кейза в одну инструкцию xorl $1, %eax превращать, на пенсию пойду
А что такое 4(%ebp $2 ? Это вообще какой язык?
А что такое 4(%ebp $2 ? Это вообще какой язык?Это вообще-то ассемблер, AT&T синтаксис.
$2 -- константа, равная 2.
%ebp -- регистр EBP (в данном случае содержит адрес в стеке, по которому начинается стековый кадр функции xxxxx (int i)).
4(%ebp) -- ячейка памяти со смещением 4 от значения %ebp, в данном случае содержит значение переменной i.
А суффикс l (long) у инструкций работы с регистрами и памятью, означает работу с 4-х байтовыми величинами.
Понял. Просто ни разу не видел такого синтаксиса.
gcc -O3 как всегда облажался:
А студия как всегда рулит:с чего это ты решил, что GCC облажался?
производительность сравнил?
собственными глазами видел как GCC для бОльшего количества вариантов в switch точно так же прыгает по адресу из таблицы, может просто в этом случае (только 4х вариантов) не целесообразно так делать или просто версия GCC старая.
Оставить комментарий
Zoulla
виртуальных функций вместо switch-конструкции?А то прогу одну так подправил, притормаживать стала