[C++] Насколько большой может быть потеря в скорости при использовании

Zoulla

виртуальных функций вместо switch-конструкции?
А то прогу одну так подправил, притормаживать стала

zzzzzzzzzzz

Ограничено только фантазией программиста

Zoulla

Оч смешно. Я лишнего кода не добавлял и бесконечных циклов никуда не вставлял.
Переформулирую вопрос: ее умешьшить можно каким-нибудь способом? Если да, то каким?

Helga87

Код покажи.

maggi14

и что у тебя за свич-конструкции, и какие вирт.функции? из твоих слов это (мне) непонятно

Zoulla

class X
{
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 { ... }
};

maggi14

и что тебя удивляет? одно дело, сделать сравнение, джамп и call по адресу, другое дело - залезть в таблицу вирт функций, сделать поиск по ней, и уже оттуда call

Dasar

какой еще нафиг поиск по таблице виртуальных функций?

Zoulla

Код рисования немаленький там, а скорость раза в два наверное упала.

maggi14

прастой. Познее, знаете ли, связывание

Zoulla

Там поиск не производится, смещение уже заранее известно.

maggi14

ага, отдельным дополнительным действием берется это самое смещение
конечно, если скорость падает значительно на фоне долгих вычислений, мое объяснение явно неудачно. А так, на холостой работе, по идее, вирт.функции работают медленнее

Dasar

что такое по-твоему позднее связывание?
и какое отношение позднее связывание имеет к виртуальным функциям?

pitrik2

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

Zoulla

Не важно сколько классов, draw в цикле вызывается по всем объектам этих классов (которые уже созданы).

Dasar

хм, оказывается "позднее связывание" - по разным источникам обозначает два совсем разных связывания.

zzzzzzzzzzz

Ну, от прыжка ты не избавишься, так что может от виртуальных функций придется отказаться.

bastii

не тупи

Zoulla

Чего-то не хочется, да и странно вообще, что вызов функции так резко программу замедляет.

Julie16

switch vs. virtual call может иметь преимущество в следующем:
1) Как я понимаю вызов виртуального метода всегда сбрасывает кэш.
2) Возможность инлайниться.
Все.

bastii

п1 поясни
А так про switch vs virtual call просто -- чем больше свитчей, тем эта конструкция медленнее, чем виртуальный вызов. Еще по идее, если методы простые, и на кэш нагрузка небольшая, то виртуальная таблица будет сидеть в кэше, в этом случае и несколько свитчей будет медленнее. Если же метод сложный, то тогда вообще не так важно как быстро делается вызов.

Julie16

Потому что у нас там прыжок по значению из памяти. Как ты предлагаешь предсказать переход по такому прыжку?

bastii

Просто меня сбило с толку упоминание слова "кэш", он тут причем?
ИМХО в общем случае и для свитчей предсказание ветвления не очень работает.

Julie16

Ну если мы прыгаем по адресу который не был предсказан, сбрасываются всякие кеши(вернее они правильно не заполняются ). Это же очевидно. Очередь инструкций, опять же...

bastii

ясно
короче все равно, в конце концов switch в такой ситуации это просто тупо по многим другим причинам

rosali

Ну если мы прыгаем по адресу который не был предсказан, сбрасываются всякие кеши
При чем тут кеши? Конвейер сбрасывается...

zzzzzzzzzzz

Мне тоже не ясно

bleyman

> А так про switch vs virtual call просто -- чем больше свитчей, тем эта конструкция медленнее, чем виртуальный вызов.
Открываю страшную тайну: в 80х86 ассемблере есть инструкция jmp ax (есть ещё и jmp al, конечно, но мы не о ней=) так вот, если у тебя кейзы в свитче идут хорошо подряд (0..4096, например а компилятор грамотный, то прыжок займёт те же два такта.
Однако возможно получить линейное замедление производительности, конечно. Ещё можно забыть поставить break; (КСТАТИ! или даже запретить компайлеру реарранжить кейзы (есть такая опцыя).

bleyman

Кэшы ынструкцый?
Они всё равно сбрасываются...

rosali

Кэшы ынструкцый?
адно не хочу я с вами спорить, никто нихрена не знает, все только обобщают свои бесценные знания архитекутуры 8086 на всё подряд. Кароче в сад.

Marinavo_0507

В 8086 был кэш инструкций?

rosali

Ну хорошо, вот это например к чему вообще написано?
в 80х86 ассемблере есть инструкция jmp ax, ... то прыжок займёт те же два такта.
какие еще в жопу 2 такта? причем это говорится тоном очень осведомленного человека
Опять же возвращаемся к
Кэшы ынструкцый?
Куда и когда
Они всё равно сбрасываются...
В этом заявлении тоже надо искать смысл, или все таки его нет?

Marinavo_0507

Ну я не знаю, чему у него равен икс.

Zoulla

Всем спасибо, проблема похоже в другом.

pitrik2

дык это вроде сразу все поняли что проблема не в этом
вообще, вопрос был почему виртуальные медленнее
а обсуждать стали почему свичи медленнее
прикольно

bastii

Открываю страшную тайну: в 80х86 ассемблере есть инструкция jmp ax (есть ещё и jmp al, конечно, но мы не о ней=) так вот, если у тебя кейзы в свитче идут хорошо подряд (0..4096, например а компилятор грамотный, то прыжок займёт те же два такта.
Про это подробней. Как код будет выглядеть? Ты сам видел или это в теории?

rosali


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 все подряд и в нужный момент вернуться, чем пихнуть в него то, что нужно и вернуться. И по большому счету нет оснований ему не верить.

Julie16

если есть под рукой icc - покажи что генерит он, пожалуйста

bastii

А если код в кейзах неравномерный по длине, то как будет: выравнивание по самому длинному, джампы-редиректы или забъет и скомпилирует нормально?

rosali

Ну то что gcc делает elsif elsif это в любом случае ненормально. Бинарный поиск я думаю сделает, щас попробуем...

rosali

Во раскорячил!

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;
}
}

bastii

я имел в виду, что длина кода в кейзах будет разной

Olyalyau



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

Marinavo_0507

8 дней придумывал?

Olyalyau

Два часа после работы.
Это третий вариант

Marinavo_0507

> Два часа после работы.
gcc таки побыстрее работает, да и в любое время

Olyalyau

gcc таки побыстрее работает, да и в любое время
Кто б сомневался
Только за штучную и качественную ручную работу лучше платят
Как научат компилеры четыре кейза в одну инструкцию xorl $1, %eax превращать, на пенсию пойду

Flack_bfsp

А что такое 4(%ebp $2 ? Это вообще какой язык?

Olyalyau

А что такое 4(%ebp $2 ? Это вообще какой язык?
Это вообще-то ассемблер, AT&T синтаксис.
$2 -- константа, равная 2.
%ebp -- регистр EBP (в данном случае содержит адрес в стеке, по которому начинается стековый кадр функции xxxxx (int i)).
4(%ebp) -- ячейка памяти со смещением 4 от значения %ebp, в данном случае содержит значение переменной i.
А суффикс l (long) у инструкций работы с регистрами и памятью, означает работу с 4-х байтовыми величинами.

Flack_bfsp

Понял. Просто ни разу не видел такого синтаксиса.

Chupa

Flack_bfsp

mira-bella

gcc -O3 как всегда облажался:
А студия как всегда рулит:
с чего это ты решил, что GCC облажался?
производительность сравнил?
собственными глазами видел как GCC для бОльшего количества вариантов в switch точно так же прыгает по адресу из таблицы, может просто в этом случае (только 4х вариантов) не целесообразно так делать или просто версия GCC старая.
Оставить комментарий
Имя или ник:
Комментарий: