Paul Ivanov, вы поставили перед собой неимоверную задачу, как уже упомянули выше. Дело тут в двух моментах: 1) в том, как компилятор преобразует текст программы в код; 2) в том, как устроен и работает
современный процессор. Когда-то лет 30-40 назад ещё можно было написать код, который отключает прерывания засекает время и крутит тестовый образец в цикле на несколько сот тысяч итераций. Такой подход ещё работает на самых простых микроконтроллерах. Но в целом, это время ушло.
Как вам уже заметили, ваш код на ассемблере может выглядеть совсем не так, как текст программы. Я бы даже сказал, что он будет совершенно неузнаваем, если не иметь натренированный глаз. Поэтому нет смысла измерять время отдельных операндов, они весьма эфемерны. В различных условиях одни и те же операнды могут компилироваться в совершенно различный машинный код. Гораздо разумнее сосредоточиться на измерении времени работы отдельных блоков кода, выполняющих определённую задачу. И если есть возможность, то смотрите на выданный компилятором ассмеблер. Это по первому пункту.
По поводу второго. Современные процессоры невероятно умные.
Иногда даже слишком. У них есть такая вещь, как
внеочередное выполнение (
out-of-order execution). Пересказывать вики я вам не буду, сами почитаете. В двух словах: последовательные инструкции могут выполнятся параллельно и в произвольном порядке. Как это повлияет на ваше измеряемое время, попробуйте себе представить.
Затем, есть такая фича, как
спекулятивное исполнение (
Speculative execution, русской статьи в вики нет), о которой я сам узнал только сегодня в соседней теме. Смысл в том, что иногда процессор натыкается на условный переход, условие которого он ещё не знает и узнает не скоро. Чтобы не тупить и не бездельничать он берёт и угадывает как этот переход может разрешиться, а затем исполняет угаданную ветвь. Когда условие перехода становится известным, процессор сморит, верно ли он угадал или нет. Если нет, то все действия после перехода, которые он выполнил, отбрасываются, если да — вступают в законную силу.
Почему процессор может стоять и тупить ведёт нас к следующему моменту: процессор работает невероятно быстрее памяти (на порядки). По этой причине, если условие является проверкой какой-то редко используемой ячейки памяти, то потребуется значительное время, чтобы её достать. Чтобы побороть эту проблему медленной памяти используется
технология кэширования. Процессор имеет встроенную очень быструю память, называемой
кэшем, в которой он хранит часто используемые куски оперативной памяти.
Кэш бывает первого, второго и (иногда) последнего уровней. Они отличаются быстродействием, размером и принадлежностью ядру процессора. Кэш хранит как код программ, так и данные. Когда процессор обрабатывает большие объёмы данных, то он пытается подгружать их в кэш упредительно: следующую порцию, пока обрабатывает текущую. Для этого используются всякие эвристические приёмы, а так же специальные команды процессора, которые умный компилятор может вставлять в код. Но они не всегда срабатывают, это называется кэш-промахом. Код программ можно и нужно оптимизировать, чтобы таких промахов было как можно меньше, или, во всяком случае, они не сильно снижали быстродействие. Так что дерзайте! К сожалению, на этом мои познания пока заканчиваются. Я сам ещё в процессе изучения всех этих тонкостей.
Ух! Надеюсь, я чуть-чуть развеял перед вами завесу мрака. Так же надеюсь, что имеющиеся сложности и обширный материал не отпугнут вас от исследования этой интересной темы! В качестве продолжения почитайте
эту статью на Хабре, а так же комментарии к ней.