The Nameless One
|
|
« : 16-12-2009 11:56 » |
|
Привет! Есть приложение, 99% времени свой работы оно выполняет некий набор инструкций более 100 раз в секунду - такие требования к скорости. Это реализовано так: есть главный цикл, внутри которого этот самый набор инструкций - и этот цикл должен выполнят Ься не менее 100 раз в секунду. Наборы инструкций разные в зависимости от состояния определяющей переменной. В определенные моменты нужно переключать этот самый набор - например, сейчас приложение выполняет набор команд А, потом переключается на выполнение команд B - и т.д. Вопрос - как лучше это переключение организовать - через switch и case или через указатель на фунцию? Допустим: enum {A, B, C} State; while(true) //главный цикл приложения { switch(State) { case A: /*Инструкции А*/ break; case B: /*Инструкции B*/ break; case C: /*Инструкции C*/ break; default: break; } }
Таким образом, работа приложения определяется значением перечислимой константой Sate. Цикл всегда выполяняется 100 раз в секунду - но что именно в нем выполяется - зависит от State. Другой вариант - указатель на функцию, каждый набор инструкций записать в тело отдельной функции с одинаковой сигнатурой - а в цикле крутить вывоз функции по указателю, а когда надо, вместо переключения State менять этот указатель. Как грамотнее и быстрее? Или, может, вообще другой вариант? Спасибо:) Правка: грамотный выход из цикла, естественно, есть - в примере я этого не отразил. Правка 2: case'ов может быть около 10 - не больше.
|
|
« Последнее редактирование: 16-12-2009 13:21 от Sel »
|
Записан
|
|
|
|
Sla
|
|
« Ответ #1 : 16-12-2009 12:27 » |
|
Реализация зависит от платформы. Как по мне, так вариант с CASE более красив и удобен для последующего сопровождения кода.
Если "развернуть" в асм код CASE, можно увидеть кучу условных переходов или же "табличную" адресацию"?
|
|
|
Записан
|
Мы все учились понемногу... Чему-нибудь и как-нибудь.
|
|
|
Вад
|
|
« Ответ #2 : 16-12-2009 12:29 » |
|
Имхо, switch очевиднее и проще: если потеря в производительности не играет большой роли, и case-ов практически 100% не станет со временем на порядки больше, - то я сделал бы через switch. С другой стороны, если заведомо число вариантов неизвестно, или этот код вызывается сотни тысяч-миллионы раз в секунду, то указатели на функции будут предпочтительнее/быстрее. Хотя насчёт быстрее я теперь уже не уверен Другой случай, когда указатели более удобны, чем свитч, - это когда, скажем, нужно сделать фабрику для развивающейся иерархии типов. То есть, чтобы при добавлении нового типа не требовалось заносить изменения в свитч и плодить зависимости между фабрикой и порождаемыми ею типами.
|
|
« Последнее редактирование: 16-12-2009 12:32 от Вад »
|
Записан
|
|
|
|
The Nameless One
|
|
« Ответ #3 : 16-12-2009 19:25 » |
|
Sla, Если "развернуть" в асм код CASE, можно увидеть кучу условных переходов или же "табличную" адресацию"? Я вот не знаю - подскажите - как это увидеть? Как посмотреть на асм-код? Пользуюсь Студией. Реализация зависит от платформы.
Windows, + возможно портирование на MacOS. Вад, пример с фабрикой иерархии типов - что-то близкое:) Только у меня будут добавляться новые наборы команд - т.е. действительно придется вносить изменение и в enum и в switch - каждый раз добавлять. Вопрос быстродействия, похоже, не имеет очевидного ответа. Но case'ов не будет больше 10-ти - 100%. Что же выбрать) Чем будет менее затратно по производительности - тем лучше.
|
|
|
Записан
|
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #4 : 16-12-2009 20:24 » |
|
За сколько времени у тебя выполняется набор команд? И это время соизмеримо со временем на переключение в другую систему команд?
В принципе тут можно сделать С++ way. Создать родительский базовый класс. С виртуальной функцией run. Для своего набора команд, наследоваться от родительского класса. Теперь только останется подменять классы. При этом исключаются всякие хаки.
|
|
« Последнее редактирование: 16-12-2009 20:26 от Finch »
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
The Nameless One
|
|
« Ответ #5 : 16-12-2009 22:18 » |
|
Finch, За сколько времени у тебя выполняется набор команд? И это время соизмеримо со временем на переключение в другую систему команд? Честно говоря - не измерял, за сколько времени выполняется один набор команд - но точно могу сказать, что это время НЕ соизмеримо со временем, затрачиваемым на переключение в другую систему команд - последнее происходит существенно быстрее) А вот сделать абстрактный базовый класс с виртуальным методом и от него наследовать классы, в методы которых "рассовать" наборы команд, а в главном цикле просто "крутить" указатель на базовый класс с вызовом метода - думал над этим. Только с быстродействием вопрос - позднее связывание - сначала надо определить - на какой именно класс смотрит указатель - потом надо вызвать соответствующую функцию - так это вроде работает? А ведь в ней только самое "мясо" и будет - со сложными расчетами и вложенными вызовами функций. Не будет ли это затратней, чем case?
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #6 : 17-12-2009 06:44 » |
|
Так проверь, чего спрашивать7 Покрути 2 цикла с одинаково большим количеством итераций и определи, который выполняется дольше. Тебе ж про switch тоже говорили, что там череда сравнений выполняется. 1 или 2 раза взять содержимое по указателю или выполнить 1-10 сравнений?
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
The Nameless One
|
|
« Ответ #7 : 17-12-2009 09:12 » |
|
Dimka, просто все еще на стадии проектирования в основном - крутить нечего. Пока только один набор операций. Но так и сделаю - раз по-другому не понять:) Хотя думаю, что с указателем будет быстрее.
Спасибо всем за ответы!
|
|
|
Записан
|
|
|
|
Sla
|
|
« Ответ #8 : 17-12-2009 09:31 » |
|
Я еще раз повторюсь, потому что уже говорил на эту тему в предыдущем твоем топике. При сегодняшних скоростях не имеет смысла ловить блох на мизерной оптимизации, тем более, учитывая НачальныеУсловия (мах 10 состояний).
Про платформу я говорил, понимая не ОС, а аппаратную часть. Если это МК, то оптимизация имеет смысл. Но когда речь идет об С++, то понятно, что об МК и речи быть не может.
|
|
|
Записан
|
Мы все учились понемногу... Чему-нибудь и как-нибудь.
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #9 : 17-12-2009 09:37 » |
|
. Тебе ж про switch тоже говорили, что там череда сравнений выполняется. 1 или 2 раза взять содержимое по указателю или выполнить 1-10 сравнений?
странно, всегда полагал, что там "табличный" переход int i=3; switch(i) { case 0: { int iii0=0; } break;
case 1: { int iii1=0; } break;
case 2: { int iii2=0; } break;
case 3: { int iii3=0; } break; }
int iii=1;
alt+8 int i=3; 005AD4A2 mov dword ptr [ebp-20h],3 switch(i) 005AD4A9 mov eax,dword ptr [ebp-20h] 005AD4AC mov dword ptr [ebp-174h],eax 005AD4B2 cmp dword ptr [ebp-174h],3 005AD4B9 ja $LN2+7 (5AD4EAh) 005AD4BB mov ecx,dword ptr [ebp-174h] 005AD4C1 jmp dword ptr (5AD8F4h)[ecx*4] { case 0: { int iii0=0; 005AD4C8 mov dword ptr [iii0],0 } break; 005AD4CF jmp $LN2+7 (5AD4EAh)
case 1: { int iii1=0; 005AD4D1 mov dword ptr [iii1],0 } break; 005AD4D8 jmp $LN2+7 (5AD4EAh)
case 2: { int iii2=0; 005AD4DA mov dword ptr [iii2],0 } break; 005AD4E1 jmp $LN2+7 (5AD4EAh)
case 3: { int iii3=0; 005AD4E3 mov dword ptr [iii3],0 } break; }
int iii=1; 005AD4EA mov dword ptr [ebp-5Ch],1
вычисление адреса перехода - один раз: 005AD4BB mov ecx,dword ptr [ebp-174h] 005AD4C1 jmp dword ptr (5AD8F4h)[ecx*4]
а после каждого блока - выход через одну точку 005AD4CF jmp $LN2+7 (5AD4EAh)
|
|
|
Записан
|
|
|
|
The Nameless One
|
|
« Ответ #10 : 17-12-2009 09:40 » |
|
Sla, понимаю, просто целевые компьютеры начинаются от компьютеров 5-7 летней давности в том числе - надо, чтобы и на них все работало прекрасно. И просто я слабо еще представляю - где будут большие затраты, а где маленькие, поэтому для меня не очевидно, что я пытаюсь "ловить блох на мизерной оптимизации", пока вы мне не подскажите:) В книжках написано, что виртуальные функции работают медленнее невиртуальных из-за позднего связывания.
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #11 : 17-12-2009 09:41 » |
|
The Nameless One, быстрее свича ничего не сделаешь
|
|
|
Записан
|
|
|
|
The Nameless One
|
|
« Ответ #12 : 17-12-2009 09:46 » |
|
Алексей1153++, спасибо
|
|
|
Записан
|
|
|
|
Sla
|
|
« Ответ #13 : 17-12-2009 09:49 » |
|
Алексей1153++, ты бы подсказал человеку, как ты получил asm код. А он спрашивал (а я не знаю).
|
|
|
Записан
|
Мы все учились понемногу... Чему-нибудь и как-нибудь.
|
|
|
The Nameless One
|
|
« Ответ #14 : 17-12-2009 09:52 » |
|
Sla, если не ошибаюсь, то там в посту у Алексей1153++ перед asm кодом чтоит alt+8 - наверное, это есть оно?
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #15 : 17-12-2009 09:55 » |
|
туда можно попасть только в запущенном на дебаг коде - ставим курсор на интерисующее место и жмём alt+8 (по дефолтной настройке в стиле VS6)
|
|
|
Записан
|
|
|
|
|
Sla
|
|
« Ответ #17 : 17-12-2009 10:07 » |
|
Алексей1153++, "табличный переход" это частный случай условного перехода.
А теперь представь не строготипизированный язык и попытайся развернуть switch. Или же отсутствие конструкций типа dword ptr
|
|
|
Записан
|
Мы все учились понемногу... Чему-нибудь и как-нибудь.
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #18 : 17-12-2009 10:13 » |
|
Слав, в ТЗ такого не было
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #19 : 17-12-2009 14:55 » |
|
Алексей1153++, не понял насчёт таблицы. Берём VS 2008 C++ native без оптимизации #include <iostream>
int main() { int x; std::cin >> x; switch(x) { case 0: std::cout << "A"; break; case 1: std::cout << "B"; break; case 2: std::cout << "C"; break; default: std::cout << "Z"; break; } return 0; }
Смотрим дизассемблер: #include <iostream>
int main() { 00B014A0 push ebp 00B014A1 mov ebp,esp 00B014A3 sub esp,0D0h 00B014A9 push ebx 00B014AA push esi 00B014AB push edi 00B014AC lea edi,[ebp-0D0h] 00B014B2 mov ecx,34h 00B014B7 mov eax,0CCCCCCCCh 00B014BC rep stos dword ptr es:[edi] int x; std::cin >> x; 00B014BE mov esi,esp 00B014C0 lea eax,[x] 00B014C3 push eax 00B014C4 mov ecx,dword ptr [__imp_std::cin (0B0A338h)] 00B014CA call dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (0B0A33Ch)] 00B014D0 cmp esi,esp 00B014D2 call @ILT+405(__RTC_CheckEsp) (0B0119Ah) switch(x) 00B014D7 mov eax,dword ptr [x] 00B014DA mov dword ptr [ebp-0D0h],eax 00B014E0 cmp dword ptr [ebp-0D0h],0 00B014E7 je main+5Dh (0B014FDh) 00B014E9 cmp dword ptr [ebp-0D0h],1 00B014F0 je main+72h (0B01512h) 00B014F2 cmp dword ptr [ebp-0D0h],2 00B014F9 je main+87h (0B01527h) 00B014FB jmp main+9Ch (0B0153Ch) { case 0: std::cout << "A"; 00B014FD push offset string "A" (0B0780Ch) 00B01502 mov eax,dword ptr [__imp_std::cout (0B0A320h)] 00B01507 push eax 00B01508 call std::operator<<<std::char_traits<char> > (0B0114Fh) 00B0150D add esp,8 break; 00B01510 jmp main+0AFh (0B0154Fh) case 1: std::cout << "B"; 00B01512 push offset string "B" (0B07808h) 00B01517 mov eax,dword ptr [__imp_std::cout (0B0A320h)] 00B0151C push eax 00B0151D call std::operator<<<std::char_traits<char> > (0B0114Fh) 00B01522 add esp,8 break; 00B01525 jmp main+0AFh (0B0154Fh) case 2: std::cout << "C"; 00B01527 push offset string "C" (0B07804h) 00B0152C mov eax,dword ptr [__imp_std::cout (0B0A320h)] 00B01531 push eax 00B01532 call std::operator<<<std::char_traits<char> > (0B0114Fh) 00B01537 add esp,8 break; 00B0153A jmp main+0AFh (0B0154Fh) default: std::cout << "Z"; 00B0153C push offset string "Z" (0B07800h) 00B01541 mov eax,dword ptr [__imp_std::cout (0B0A320h)] 00B01546 push eax 00B01547 call std::operator<<<std::char_traits<char> > (0B0114Fh) 00B0154C add esp,8 break; } return 0; 00B0154F xor eax,eax } 00B01551 push edx 00B01552 mov ecx,ebp 00B01554 push eax 00B01555 lea edx,[ (0B01578h)] 00B0155B call @ILT+160(@_RTC_CheckStackVars@8) (0B010A5h) 00B01560 pop eax 00B01561 pop edx 00B01562 pop edi 00B01563 pop esi 00B01564 pop ebx 00B01565 add esp,0D0h 00B0156B cmp ebp,esp 00B0156D call @ILT+405(__RTC_CheckEsp) (0B0119Ah) 00B01572 mov esp,ebp 00B01574 pop ebp 00B01575 ret 00B01576 mov edi,edi 00B01578 db 01h 00B01579 db 00h 00B0157A db 00h 00B0157B db 00h 00B0157C db 80h 00B0157D db 15h 00B0157E db b0h 00B0157F db 00h 00B01580 db f8h 00B01581 db ffh 00B01582 db ffh 00B01583 db ffh 00B01584 db 04h 00B01585 db 00h 00B01586 db 00h 00B01587 db 00h 00B01588 db 8ch 00B01589 db 15h 00B0158A db b0h 00B0158B db 00h 00B0158C db 78h 00B0158D db 00h
Имеем на switch цепочку из cmp-je операторов.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #20 : 17-12-2009 19:56 » |
|
А вот сделать абстрактный базовый класс с виртуальным методом и от него наследовать классы, в методы которых "рассовать" наборы команд, а в главном цикле просто "крутить" указатель на базовый класс с вызовом метода - думал над этим. Только с быстродействием вопрос - позднее связывание - сначала надо определить - на какой именно класс смотрит указатель - потом надо вызвать соответствующую функцию - так это вроде работает? А ведь в ней только самое "мясо" и будет - со сложными расчетами и вложенными вызовами функций. Не будет ли это затратней, чем case?
Ты слишком усложняеш. Вот простенкий пример программы. Которая показывает мою мысль. include <stdio.h> class absAction { public: absAction() {}; virtual ~absAction() {}; virtual void run() {}; };
class firstAction :public absAction { public: firstAction() : absAction() {}; virtual ~firstAction() {}; virtual void run() { //some action printf("First action class\n"); }; };
class secondAction :public absAction { public: secondAction() : absAction() {}; virtual ~secondAction() {}; virtual void run() { //some action printf("Second action class\n"); }; };
class somePointer { public: somePointer() { action=NULL; setAction(new absAction()); } somePointer(absAction *val) { action=NULL; if (!setAction(val)) setAction(new absAction()); } bool setAction(absAction *val) { bool res=false; if (val != NULL) { delete action; action = val; res=true; } return res; }; ~somePointer() {delete action;}; void run() {action->run();}; private: absAction *action; };
int main() { somePointer ptr(new firstAction()); ptr.run(); ptr.setAction(new secondAction()); ptr.run(); return 0; }
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
The Nameless One
|
|
« Ответ #21 : 17-12-2009 21:16 » |
|
Finch, благодарю за пояснение)
|
|
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #22 : 18-12-2009 04:07 » |
|
Dimka, все претензии - к компилятору У меня получился код, который у меня в посте - я его не выдумал. upd Dimka, предположение из области фантастики: у меня все кейсы всегда блоками {} отделены , попробуй, поставь - может, это влияет ?
|
|
« Последнее редактирование: 18-12-2009 04:09 от Алексей1153++ »
|
Записан
|
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #23 : 18-12-2009 04:18 » |
|
Dimka, вот и ответ: если добавить кейсов побольше, то случается "табличный" переход Твой код у меня так же построился, как и у тебя, а вот такой #include <iostream>
int main() { int x; std::cin >> x; switch(x) { case 0: std::cout << "A"; break; case 1: std::cout << "B"; break; case 2: std::cout << "C"; break; case 3: std::cout << "C"; break; case 4: std::cout << "C"; break; case 5: std::cout << "C"; break; case 6: std::cout << "C"; break; default: std::cout << "Z"; break; } return 0; }
уже так ... ... 5: int x; 6: std::cin >> x; 004016A8 lea eax,[ebp-4] 004016AB push eax 004016AC mov ecx,offset std::cin (00478888) 004016B1 call @ILT+580(std::basic_istream<char,std::char_traits<char> >::operator>>) (00401249) 7: switch(x) 8: { 004016B6 mov ecx,dword ptr [ebp-4] 004016B9 mov dword ptr [ebp-8],ecx 004016BC cmp dword ptr [ebp-8],6 004016C0 ja $L7578+14h (0040175f) 004016C6 mov edx,dword ptr [ebp-8] 004016C9 jmp dword ptr [edx*4+401784h] 9: case 0: 10: std::cout << "A"; 004016D0 push offset string "A" (0046d028) 004016D5 push offset std::cout (004787f8) 004016DA call @ILT+770(std::operator<<) (00401307) 004016DF add esp,8 11: break; 004016E2 jmp $L7578+26h (00401771) 12: case 1: 13: std::cout << "B"; ... ...
полагаю, что компилятор прикидывает, что будет быстрее - три джампа быстрее, чем рассчёт перехода, а 4 джампа, скажем, уже медленнее
|
|
« Последнее редактирование: 18-12-2009 04:55 от Алексей1153++ »
|
Записан
|
|
|
|
Антон (LogRus)
|
|
« Ответ #24 : 18-12-2009 08:25 » |
|
а я бы сделал так ......... enum EStates { eStateA = 0, eStateB, eStateC, .............., eStateCount} .........
FunctionType state_handlers[eStateCount] = {0}; // вместо указетелей можно использовать функторы // вместо массива вектор, если нужно динамически добавлять обработчики ............... while(true) //главный цикл приложения { state_handlers[State](params); } ................
|
|
|
Записан
|
Странно всё это....
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #25 : 18-12-2009 08:27 » |
|
LogRus, ну автор же хочет "как можно быстрее", а у тебя это ведь надо извлечь из массива указатель, заполнить стек параметрами, вызвать функцию )
|
|
|
Записан
|
|
|
|
Антон (LogRus)
|
|
« Ответ #26 : 18-12-2009 09:08 » |
|
Алексей ну ты же знаешь, что я отвечу это называется предварительная оптимизация, т.е. "Давайте ради двух тактов процессора нарисуем уродский не удобный код" полагаю код выполняющийся внутри функции гораздо жирнее и прожорливей, ИМХО, овчинка выделки не стоит. в случае, если автор нарисует в кейсах жесткий и длинный код, стоимость прыжка в switch до дальних элементов может быть дорогой, а выдернуть указатель по индексу не так уж и дорого.
|
|
|
Записан
|
Странно всё это....
|
|
|
Алексей++
глобальный и пушистый
Глобальный модератор
Offline
Сообщений: 13
|
|
« Ответ #27 : 18-12-2009 09:19 » |
|
если код тяжёлый - то да. А если код сам на сотню тактов ? ) Ну об этом можно и не спорить - автор сам решит, что ему "быстрее", а что удобнее ))
|
|
|
Записан
|
|
|
|
|