Вопрос

Итак, сверхнизкоуровневый, как выглядит 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 реализован на конкретной микроархитектуре.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top