Ето една интересна лекция на Meyers, в която той обяснява много добре какво е std::move, std::forward и noexcept 🙂
Към средата има една част, в която е показано колко по-бърз може да е кода, когато е с noexcept там, където е възможно. Освен това имаше показани примери, как чрез проста смяна на компилатора със C++11/14 enabled такъв, кода става по-бърз. Във връзка с noexcept се появи в един момент и тази графика.
Колко хубаво, помислих си .. Ще направим сега едно NOEXCEPT макро (което ще е #define NOEXCEPT noexcept когато компилираме със С++11/14 компилатор), ще прекомпилираме всичко което имаме (и вкъщи и в офиса) с него и ще получим ей така едни проценти забързване от нищото ..
Та изрових една моя стара (С++98) микро библиотека за математика (вектор, точка, матрица, quaternion, в общи линии това) и реших да завъртя един цикъл с тях (тя всъщност е толкова проста, че не очаквах каквито и да е разлики в резултатите).
Ползвам clang-600.0.51(llvm 3.5svn), компилирам с -Os (което е по дефаулт) (с -O3 резултатите са почти същите) и C++14 enabled.
Кода от теста се намира тук.
Интересно, че противно на приказките на Meyers, Chandler (from LLVM) и не знам си кой друг, на моята машина (I7-3615QM) кода върви по-бавно с между 2 и 5 процента, когато функциите имат noexcept.
Или аз го ползвам много не както трябва, или някой някъде малко послъгва …
Meyers говори за съвсем друго нещо, което в общи линии е следното: ако някой (като вектора в неговия пример) се чуди дали да те мести или копира, ще предпочете да те мести, *само* ако си безопасен за местене, което се обявява с noexcept move constructor/move assignment. Та забързването в таз графика идва от там, че не се копират стринговете по време на resize(), а просто се вика move. Примерът му е много специфичен и показва една от главните причини noexcept да се включи в стандарта, иначе STL-а напълно се счупва откъм exception safety (а в STL-a се държи на exception safety, че току-виж някоя сонда отказала на половината път до Марс) и всичките move еквилибристики стават безсмислени. А твоят тест е напълно различен – просто си измерил overhead-а от слагането на noexcept навсякъде. Имаш само копиране, нямаш movable типове, нямаш предпоставки за move, тъй че просто не си дал възможност на магическия noexcept да се прояви 🙂 На мен друго ми е интересно – каква ще е разликата между throw() и noexcept…
Ако си готов да пожертваш 2000 неврона пък, може да разгледаш имплементацията на някой vector, ама не на майкрософтския…
Благодаря ти за разяснението (което и аз, и всеки които е писал някога на С++ и е гледал лекцията, най-вероятно, е разбрал :)) …
Както се вижда от текста на блог-поста ми, аз *не* питам това обаче … noexcept предполага повече възможности за оптимизиране *не само* заради move_if_noexcept (за което може да видиш на много места в bing). Тоест, такива забавяния, каквото и да става, *не са* очаквани ..
Ако на някой му се занимава да разбере защо са се случили, вилкомен 🙂
Напротив, напълно очаквани са забавянията (в случая) – очаква се от компилатора да сложи по някоя инструкция тук и там заради тоя noexcept. И да, noexcept наистина предполага повече възможности за оптимизиране, но спрямо throw(), а не спрямо exception-neutral код. А това move_if_noexcept може и да се окаже най-голямата печалба от цялата работа, не случайно Meyers точно това демонстрира…
Ако питаш мен, по-скоро се очаква компилатора да махне една-две инструкции (знае повече за кода => може да си извади по-добри изводи) …
Нали трябва да направи разни проверки дали все пак няма хвърлен exception и евентуално да извика terminate(). Един diff на асемблера ще реши мистерията 🙂
Кода го има, аз желание (и нерви) за асемблер нямам ..
За да има exception-и обаче, трябва да има други if-чета (или сходни екстри) .. тъй че все, си мисля, все тая.
Интересно, GCC 4.7.0 генерира напълно еднакъв код (независимо дали е с -О3 или -Оs).
А и като казва “пишете навсякъде, където може”, ако има дори най-малката вероятност да стане по-бавно поради някаква причина, следва да го спомене ..
Аз си мисля, че причината е следната – с noexcept наистина даваш повече информация на компилатора, обаче може (несъзнателно) да го излъжеш и някой трябва в тоя случай да те удари през ръцете. За нещастие, в С++ това се прави от runtime-a, а не от компилатора. Ако се правеше от компилатора, това нямаше да минава:
void foo() noexcept { throw new int(99); }
Така че стойността на тоя noexcept извън контекста на move_if_noexcept и разни library optimizations е наистина много съмнителна…
И слушай го внимателно какво казва: “my advise here is not “use noexcept everywhere” … use it whenever is possible, and it is possible if you’ve determined that the interface of the function should offer that guarantee”; сигурно затова е написал “whenever possible” с italic.
След това казва, че от exception-neutral кода се очаква да остане такъв.
Разликата в код е точно от извикването на някакви терминейт-и http://pastebin.com/seqnCtEG
Тока обяснение мен ме обърква .. В моят код е възможно, моите функции предполагат да не се случват exceptions в тях .. Това че кода става по-бавен, лично мене ме изненадва 🙂
Не е само тази разликата, цикъла е по-малко оптимизиран (редове 82-84 vs 29-30). Не бих очаквал 1 инструкция да дава толкова разлика, но какво да правиш – компютри 🙂