Как процессор обрабатывает условия?
-
22-08-2019 - |
Вопрос
Итак, сверхнизкоуровневый, как выглядит IF(), как он обрабатывается процессором x86?
Решение
У процессора есть инструкции "Ответвляться, если", которые при выполнении определенного условия разветвляются, а в противном случае переходят к следующей инструкции.
Итак
if(A)
{
dosomething;
}
стал бы
load A into register 0
if the zero flag is set (ie, register 0 contains 0x00) then jump to endcondition)
dosomething
endcondition:
Более сложные условия ( if(A || B && C)
) становится последовательностью инструкций, которая оставляет регистр в состоянии 0 или ненулевом состоянии, поэтому команда branchif может переходить или не переходить на основе условных флагов.
Существует множество условных флагов (ноль, перенос, отрицание, переполнение и т.д.), И некоторые инструкции branchif также работают с более сложными условиями (т. Е. они могут фактически проверять, равен ли регистр другому регистру, а не просто просматривать флаги).Каждая архитектура отличается и находит компромиссы, поэтому набор команд является полным, но в то же время быстрым и компактным.
Как указывает moocha в комментариях, некоторые архитектуры позволяют вам применять условное выражение к некоторым, многим или даже ко всем инструкциям, поэтому у вас могут быть не только инструкции 'branch if', но также 'and if', 'add if', 'move if' и т.д.
x86 очень, очень, очень сложна за пределами этого простого объяснения, как только вы перейдете к конвейерной обработке, выполнению вне очереди, кэшированию, микрокод и всем другим продвинутым разделам.Для большинства целей приведенного выше объяснения достаточно.Однако, если вы пишете очень, очень тщательно продуманный алгоритм ручной работы, вам придется учитывать эти моменты для достижения максимальной производительности и пропускной способности.
Хотя это тема для другого вопроса...
-Адам
Другие советы
Довольно просто использовать выходные данные компилятора C (используйте -S
включите gcc), чтобы увидеть, какие выходные данные будет генерировать данный фрагмент C при компиляции.Однако будьте осторожны при использовании оптимизации в игрушечных программах.Если вы не будете осторожны, оптимизатор часто будет оптимизировать условные выражения, которые всегда будут идти тем или иным путем (см. эта статья о микробенчмарках для более подробного объяснения).
Например, тривиальная программа на языке Си:
#include <stdio.h>
int main (int argc, char **argv) {
int ii = 10;
int jj = 20;
if (jj > ii) {
puts ("jj > ii \n");
}
return 0;
}
компилируется на следующий язык ассемблера:
.file "foo.c"
.section .rodata
.LC0:
.string "jj > ii \n"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $20, %esp
movl $10, -8(%ebp)
movl $20, -12(%ebp)
movl -12(%ebp), %eax
cmpl -8(%ebp), %eax
jle .L2
movl $.LC0, (%esp)
call puts
.L2:
movl $0, %eax
addl $20, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.3.2-1ubuntu12) 4.3.2"
.section .note.GNU-stack,"",@progbits
Для краткого анализа того, что происходит:
Первый раздел (
.rodata
) объявляет константу со строкой 'jj > ii \n
')Второй раздел посвящен инициализации содержимого
ii
иjj
переменные в стеке.Кусочек из
cmpl -8(%ebp), %eax
проводит фактическое сравнение;вjle
инструкция пропускает вызов к 'puts
', что фактически является логикой 'if
' заявление отменено.После надписи '
.L2
"система приводит в порядок верхнюю часть стека и возвращается после вызова.
Это инструкция перехода, зависящая от конкретной архитектуры компьютера.В нем выясняется, как настроить ячейку памяти или регистр для проверки на конкретное низкоуровневое условие - например, ветвление, если не равно или ветвление, если не равно нулю, ...-- выполняет ли этот тест затем переход (или нет, если условие не выполняется) в другую часть памяти.Очевидно, что если у вас сложное условие, возможно, потребуется выполнить оценку множества различных условий и может включать в себя несколько инструкций перехода.
Обычно центральный процессор имеет то, что называется регистром команд, в котором хранится адрес памяти текущего кода операции машинного языка, который будет выполнен следующим...и множество других регистров для хранения данных.
Как правило, после того, как центральный процессор выполняет каждый код операции в регистре команд, он просто увеличивает его на единицу, чтобы перейти к следующей позиции в памяти, которая должна содержать следующий код операции в скомпилированном программном приложении.
Однако один код операции (на самом деле, вероятно, их несколько) позволяет процессору "ветвиться", "сравнивая" значения в двух других регистрах процессора, и если одно из них больше другого, он копирует один адрес памяти в регистр команд, тогда как если другое является самым большим, он копирует второй, другой адрес памяти в регистр команд.
Это примерно настолько "низкий" уровень, насколько это можно выразить, не говоря о реле и транзисторах...
Вот довольно хороший обзор того, как такие структуры могут компилироваться на архитектуре x86: http://en.wikibooks.org/wiki/X86_Disassembly/Branches#If-Then
Иногда существуют способы избежать ветвления (что часто имеет сильно негативные последствия для производительности из-за поломки конвейера).Например, набор инструкций i686 и далее (все, начиная с Pentium Pro и заканчивая текущим днем) содержит инструкцию условного перемещения, которая может скомпилировать это:
if (a==0) {
b= 1;
}
к чему-то вроде этого:
cmp 0, eax
cmovzl ebx, 1
без ветки, при условии, что ваш компилятор настроен на i686 + (и похоже, что это;компиляторы сложны и непостижимы).SET[условие] - это другая, аналогичная условная инструкция.
Удачливые старые программисты ARM могут указывать любую инструкцию условно, что значительно сокращает количество ветвей.
Хотя большинство операторов if в конечном итоге будут условными ответвлениями, для очень простых случаев, когда ни одна из ветвей не имеет побочных эффектов, оптимизирующий компилятор может сгенерировать код, который выполняет оба и вычисляет результат, а не выполняет только одно.Это может иметь преимущества в конвейерных архитектурах, где средняя стоимость вычисления обеих ветвей меньше средней стоимости из-за промахов в прогнозировании ветвей.
Например, код:
int x;
if ( y < 5 )
x = 5;
else
x = y;
может быть скомпилирован так, как если бы он был написан:
y -= 5
int r = y < 0; // r is 1 if y < 5, 0 otherwise
r -= 1 // r is 0x00000000 if y < 5, 0xffffffff otherwise
x = y & r // x is 0 if y < 5, (y-5) otherwise
x += 5; // x is 5 if y < 5, y otherwise
который может быть преобразован в машинный код без каких-либо ответвлений
По сути, у вас есть куча электронов, передаваемых между различными атомами внутри вашего процессора.Из-за структуры атомов кремния в вашем процессоре электроны движутся по определенным траекториям, которые определяют направление выполнения, которому будет следовать компьютер.
Редактировать: Кажется, мне следует объяснить немного менее расплывчато.Потерпите, я специализировался в области компьютерных наук, а не электротехники, так что у меня не очень глубокое понимание этих вещей:
Ваш процессор изготовлен из материала, обычно кремния, который называется "полупроводником".Одним из замечательных свойств полупроводников является то, что их электрические свойства можно легко изменять с помощью "легирования" или нанесения примесей, которые создают на материале области отрицательных или положительных "носителей заряда".Линии, где эти участки сходятся вместе, известны как "перекрестки", и электричеству гораздо легче проходить через эти перекрестки в одну сторону, чем в другую.Это свойство используется для создания диодов, которые позволяют электричеству течь только в одном направлении, и транзисторов, которые можно рассматривать как крошечные переключатели, позволяющие одному электрическому току управлять другим электрическим током.Эти транзисторы и диоды комбинируются множеством способов для создания логических элементов вашего процессора.
Многие логические элементы внутри вашего процессора предназначены для выполнения функций "блока управления", который отвечает за извлечение и декодирование инструкций, сообщает остальной части процессора, что делать, и, наконец, получает следующую инструкцию.На x86 блок управления фактически запускает "микрокод", который сообщает ему, как справляться с ветвлением, конвейерной обработкой и так далее.Вам действительно нужно быть невероятно конкретным в отношении конкретной линейки процессоров, чтобы разобраться в том, как ISA x86 реализован на конкретной микроархитектуре.