Мы не так давно с Thetrik обсуждали оптимизацию кода в высокоуровневых языках, где я показывал при каких условиях JIT-компилятор дотнета может опускать проверку на выход за границы массива. Так вот мне сегодня ютюб в предложку выплюнул интересный видос с примером того, как современные языки программирования сворачивают высокоуровневые генерик абстракции в достаточно эффективный машинный код. Если честно, это меня даже немного удивило, решил поделиться. Только не помню, где мы это обсуждали, да и это явно было из ряда офтопа, так что решил создать отдельную тему. В начале видоса обсуждается генерик абстракция на плюсах, растах и зигах, а в конце показывается машинный код. Довольно забавно:
Плохо только, что работает это лишь в хвалебных рекламных обзорах и синтетических тестах. Когда это в реальном софте использовать начинают, ничто там не оптимизируется и 100 метровый ехе на 90% состоит из нагенерированного компилем мусора, от которого можно избавиться без каких-либо потерь в функциональности. Даже при Код (C): typedef LPVOID HMODULE; typedef HMODULE HINSTANCE; компиль не понимает, что vector<LPVOID>, vector<HMODULE> и vector<HINSTANCE> это одно и то же и сгенерит три абсолютно бинарно одинаковых реализации класса. Не говоря уже о чем-нибудь посложнее.
О хоспаде, спецы - такие спецы: Код (C): #include <stdio.h> #include <vector> typedef void* LPVOID; typedef LPVOID HMODULE; typedef HMODULE HINSTANCE; using namespace std; void test() { vector<LPVOID> one; vector<HMODULE> two; vector<HINSTANCE> three; one.push_back(nullptr); two.push_back(nullptr); two.push_back(nullptr); three.push_back(nullptr); three.push_back(nullptr); three.push_back(nullptr); printf("раз: %lu\n", one.size()); printf("два: %lu\n", two.size()); printf("три: %lu\n", three.size()); } Код (ASM): test(): push rbp sub rsp, 80 mov rsi, rsp lea rdi, [rsp+8] mov QWORD PTR [rsp+8], 0 mov QWORD PTR [rsp+16], 0 mov QWORD PTR [rsp+24], 0 mov QWORD PTR [rsp+32], 0 mov QWORD PTR [rsp+40], 0 mov QWORD PTR [rsp+48], 0 mov QWORD PTR [rsp+56], 0 mov QWORD PTR [rsp+64], 0 mov QWORD PTR [rsp+72], 0 mov QWORD PTR [rsp], 0 call void*& std::vector<void*, std::allocator<void*> >::emplace_back<void*>(void*&&) [clone .isra.0] mov rsi, rsp lea rdi, [rsp+32] mov QWORD PTR [rsp], 0 call void*& std::vector<void*, std::allocator<void*> >::emplace_back<void*>(void*&&) [clone .isra.0] mov rsi, rsp lea rdi, [rsp+32] mov QWORD PTR [rsp], 0 call void*& std::vector<void*, std::allocator<void*> >::emplace_back<void*>(void*&&) [clone .isra.0] mov rsi, rsp lea rdi, [rsp+56] mov QWORD PTR [rsp], 0 call void*& std::vector<void*, std::allocator<void*> >::emplace_back<void*>(void*&&) [clone .isra.0] mov rsi, rsp lea rdi, [rsp+56] mov QWORD PTR [rsp], 0 call void*& std::vector<void*, std::allocator<void*> >::emplace_back<void*>(void*&&) [clone .isra.0] mov rsi, rsp lea rdi, [rsp+56] mov QWORD PTR [rsp], 0 call void*& std::vector<void*, std::allocator<void*> >::emplace_back<void*>(void*&&) [clone .isra.0] mov rsi, QWORD PTR [rsp+16] sub rsi, QWORD PTR [rsp+8] mov edi, OFFSET FLAT:.LC1 xor eax, eax sar rsi, 3 call printf mov rsi, QWORD PTR [rsp+40] sub rsi, QWORD PTR [rsp+32] mov edi, OFFSET FLAT:.LC2 xor eax, eax sar rsi, 3 call printf mov rsi, QWORD PTR [rsp+64] sub rsi, QWORD PTR [rsp+56] mov edi, OFFSET FLAT:.LC3 xor eax, eax sar rsi, 3 call printf lea rdi, [rsp+56] call std::_Vector_base<void*, std::allocator<void*> >::~_Vector_base() [base object destructor] lea rdi, [rsp+32] call std::_Vector_base<void*, std::allocator<void*> >::~_Vector_base() [base object destructor] lea rdi, [rsp+8] call std::_Vector_base<void*, std::allocator<void*> >::~_Vector_base() [base object destructor] add rsp, 80 pop rbp ret mov rbp, rax lea rdi, [rsp+56] call std::_Vector_base<void*, std::allocator<void*> >::~_Vector_base() [base object destructor] lea rdi, [rsp+32] call std::_Vector_base<void*, std::allocator<void*> >::~_Vector_base() [base object destructor] lea rdi, [rsp+8] call std::_Vector_base<void*, std::allocator<void*> >::~_Vector_base() [base object destructor] mov rdi, rbp call _Unwind_Resume
Rel, А теперь возьми иду и посмотри, как это в реальном софте выглядит, а не в хелловорлде из одной функции.
Вот да кстати M$шный компиль в последнее время объединяет методы с одной реализацией в одну процедуру, чем несколько напрягает при реверсе классов.
Да это давно уже и в GCC/MinGW реализовано, просто для цешных спецов нужно как-то возбухать против нецешных компиляторов. На самом деле даже интересно, с чего компилятору этого не делать? Особенно если вспомнить, что многие штуки в вендовых хедерах даже не тайпдефами определены, а дефайнами препроцессора. То есть компилятор даже не узнает, что это типа разные типы, так как их разрулит препроцессор до базового типа. Но цешные спецы - такие спецы.
Rel, Код (Text): 6. mov QWORD PTR [rsp+8], 0 7. mov QWORD PTR [rsp+16], 0 8. mov QWORD PTR [rsp+24], 0 9. mov QWORD PTR [rsp+32], 0 10. mov QWORD PTR [rsp+40], 0 11. mov QWORD PTR [rsp+48], 0 12. mov QWORD PTR [rsp+56], 0 13. mov QWORD PTR [rsp+64], 0 14. mov QWORD PTR [rsp+72], 0 15. mov QWORD PTR [rsp], 0 16. call void*& std::vector<void*, std::allocator<void*> >::emplace_back<void*>(void*&&) [clone .isra.0] 17. mov rsi, rsp 18. lea rdi, [rsp+32] 19. mov QWORD PTR [rsp], 0 20. call void*& std::vector<void*, std::allocator<void*> >::emplace_back<void*>(void*&&) [clone .isra.0] 21. mov rsi, rsp 22. lea rdi, [rsp+32] 23. mov QWORD PTR [rsp], 0 - оптимизация походу к этому выхлопу не применима, регистр нельзя было обнулить или заполнить область строковыми инструкциями. Это не код это высер.
Rel, Я понимаю что загрузка константы по профайлу медленна, чем загрузка регистра, тк для этого выборка нужна из памяти а не из регистров. Ну а по сути я хз чем вы тут занимаетесь