2014-07-25 4 views
3

Я использую GCC 4.8.1 для компиляции кода C, и мне нужно определить, происходит ли недочет при вычитании на архитектуре x86/64. Оба не подключены. Я знаю, что в сборке очень просто, но мне интересно, могу ли я сделать это в коде C и у GCC его оптимизировать, потому что я не могу его найти. Это очень полезная функция (или низкий уровень, это термин?), Поэтому мне нужно, чтобы она была эффективной, но GCC кажется слишком глупым, чтобы распознать эту простую операцию? Я пробовал так много способов дать ему подсказки на языке C, но он всегда использует два регистра, а не только суб и условный прыжок. И, честно говоря, я раздражаюсь, увидев такой глупый код, написанный так много раз (функция называется лот).Вычесть и обнаружить нижний поток, наиболее эффективным способом? (x86/64 с GCC)

Мой лучший подход в C казалось следующее:

if((a-=b)+b < b) { 
    // underflow here 
} 

В основном, вычесть из А, и если результат обнуляется, обнаружить его и сделать некоторые условную обработку (которая не имеет отношения к своей стоимости, например, , это приводит к ошибке и т. д.).

GCC кажется слишком глупым, чтобы уменьшить приведенное выше значение только до суб и условного перехода, и поверьте, я пробовал так много способов сделать это в коде C и пытался использовать множество параметров командной строки (-O3 и -Os включены конечно). Что GCC делает что-то вроде этого (сборки Intel синтаксис):

mov rax, rcx ; 'a' is in rcx 
sub rcx, rdx ; 'b' is in rdx 
cmp rax, rdx ; useless comparison since sub already sets flags 
jc underflow 

Излишне говорить выше глупо, когда все, что нужно это:

sub rcx, rdx 
jc underflow 

Это так раздражает, потому что GCC, понимает что sub изменяет флаги таким образом, так как, если я привожу его в «int», он будет генерировать точный выше, за исключением того, что использует «js», который является прыжком со знаком, а не переносом, который не будет работать, если разность значений без знака высокая достаточно, чтобы установить высокий бит. Тем не менее он показывает, что он знает о вспомогательной инструкции, влияющей на эти флаги.

Теперь, может быть, я должен отказаться от попыток сделать GCC оптимальным для этого правильно и сделать это с помощью встроенной сборки, с которой у меня нет проблем. К сожалению, для этого требуется «asm goto», потому что мне нужен условный JUMP, и asm goto не очень эффективен с выходом, потому что он изменчив.

Я пробовал что-то, но я понятия не имею, безопасно ли это использовать или нет. По какой-то причине asm goto не может иметь выход. Я не хочу, чтобы он сбрасывал все регистры в память, что убьет весь момент, когда я делаю это, что является эффективностью. Но если я использую пустые операторы asm с выходами, установленными в переменную 'a' до и после нее, это будет работать и безопасно? Вот мой макрос:

#define subchk(a,b,g) { typeof(a) _a=a; \ 
    asm("":"+rm"(_a)::"cc"); \ 
    asm goto("sub %1,%0;jc %l2"::"r,m,r"(_a),"r,r,m"(b):"cc":g); \ 
    asm("":"+rm"(_a)::"cc"); } 

и использовать его как это:

subchk(a,b,underflow) 
// normal code with no underflow 
// ... 

underflow: 
    // underflow occured here 

Это немного некрасиво, но это работает просто отлично. В моем тестовом сценарии он компилирует только FINE без изменчивых накладных расходов (сброс регистров в память), не создавая ничего плохого, и кажется, что он работает нормально, однако это всего лишь ограниченный тест, я не могу проверить это везде, где я использую эту функцию/macro, поскольку я сказал, что он используется LOT, поэтому я хотел бы знать, хорошо ли кто-то осведомлен, есть ли что-то небезопасное в вышеупомянутой конструкции?

В частности, значение «a» НЕ НЕОБХОДИМО, если происходит недостаточное течение, поэтому, имея в виду, есть ли какие-либо побочные эффекты или небезопасные вещи, которые могут произойти с моим встроенным макросом asm? Если нет, я буду использовать его без проблем, пока они не оптимизируют компилятор, поэтому я могу заменить его обратно после того, как я предполагаю.

Пожалуйста, не превращайте это в дискуссию о преждевременных оптимизациях, а что нет, оставайтесь на теме вопроса, я полностью об этом знаю, так что спасибо.

+0

Я думаю, что проблема заключается в том, что вы предполагаете, что компиляторы всегда оптимизируют «лучший» способ сделать что-то, недостаток - это ваше предположение, а не компилятор или оптимизатор. –

+0

gcc, это с открытым исходным кодом ... Если вам это не нравится, измените его ... –

+0

Узор if ((r = x - y)> x) лучше. На самом деле, это один из ответов ниже. –

ответ

3

Возможно, я пропустил нечто очевидное, но почему это не так хорошо?

extern void underflow(void) __attribute__((noreturn)); 
unsigned foo(unsigned a, unsigned b) 
{ 
    unsigned r = a - b; 
    if (r > a) 
    { 
     underflow(); 
    } 
    return r; 
} 

Я проверил, НКУ оптимизирует его, что вы хотите:

foo: 
    movl %edi, %eax 
    subl %esi, %eax 
    jb  .L6 
    rep 
    ret 
.L6: 
    pushq %rax 
    call underflow 

Конечно, вы можете обрабатывать сгущенного однако вы хотите, я только что сделал это, чтобы сохранить ассемблерный просто.

+0

Большое спасибо, кажется, что GCC достаточно умен, чтобы оптимизировать только этот конкретный случай «(a-b)> a», но не то, что я пробовал с помощью _a;}) Тогда я просто использую if (subchk (A , B)) {underflow; }. Я использую макрос, потому что хочу, чтобы он был агностиком для типов. По какой-то причине GCC иногда в моих тестах теперь использует «mov rsi, local_var», а затем «sub rsi, B» (суб, я хочу) и возвращает значение в локальный стек «mov local_var, rsi», а затем делает прыжок. Конечно, сейчас это совсем другое дело. – kktsuri

0

Как насчет следующего кода сборки (вы можете обернуть его в формат НКУ):

sub rcx, rdx ; assuming operands are in rcx, rdx 
    setc al  ; capture carry bit int AL (see Intel "setxx" instructions) 
    ; return AL as boolean to compiler 

Затем вы вызываете/встроенный код сборки, и ветви на результирующее логическое значение.

+0

Ваше решение хорошо, что это перешло мне в голову, единственная проблема заключается в том, что использование inline asm таким образом заставляет компилятор не оптимизировать такой код (потому что он не может знать), а в данном конкретном случае нижний поток обнаружение выполняется с помощью специальной обработки кода/ошибок, которая не может быть выполнена только путем изменения возвращаемого значения - если только я не проверю возвращаемое значение, а затем скачу в соответствии с ним, но в этом случае он будет медленнее, чем «GCC»/cmp "я старался избегать. Спасибо хоть. – kktsuri

0

Вы проверили, действительно ли это быстрее? Современные x86-микроархитекторы используют микрокод, превращая отдельные инструкции по сборке в последовательности более простых микроопераций. Некоторые из них также делают микро-op fusion, в котором последовательность инструкций сборки превращается в один микрооператор. В частности, последовательности, подобные test %reg, %reg; jcc target, слиты, вероятно, потому, что глобальные флаги процессора являются провалом производительности.
Если cmp %reg, %reg; jcc target является mOp-fused, gcc может использовать его для получения более быстрого кода. По моему опыту, gcc очень хорошо подходит для планирования и аналогичных низкоуровневых оптимизаций.

+0

Я предполагал, что это быстрее, потому что sub - это точно как cmp, за исключением того, что он сохраняет результат, и я не думаю, что GCC знает что-то скрытое здесь, потому что, если я подписываю чек (т. Е. Выводит их на подпись, тогда проверьте,), он будет делать только суб, а затем «js» для перехода (знак бит = отрицательный), если комбинация cmp/sub была бы быстрее, это было бы сделано именно так. Спасибо за понимание микроопераций, хотя, я не знал об этом, я сделал небольшое исследование, теперь выглядит очень интересным. – kktsuri