2015-08-26 2 views
1

Я пытаюсь оптимизировать математическую функцию, записывая встроенную сборку с GCC и ARM Cortex-A7. Мой код заключается в следующем:GCC inline asm с aapcs

__inline int __attribute__((pcs("aapcs"))) optAbsVal(int x) 
{ 
    asm("CMP R0, #0\n" 
     "IT LT\n" 
     "RSBLT R0, R0, #0"); 
    return(x); 
} 

Я не уточнил, какие входные/выходные параметры, ни затирает внутри блока инлайн ассемблерном, потому что, в соответствии с вызывающей конвенции, х должно быть в R0, а также в качестве возвращаемого значения , Проблема в том, что эта функция возвращает значение x без его модификации, что заставляет меня думать, что либо x не находится в R0, либо компилятор каким-то образом изменяет функцию. Я решил это, добавив параметры «= r» (x): «0» (x), но все же я не удовлетворен этим кодом, поскольку кажется, что делаю ненужные операции. Причина, по которой я делаю ПК («aapcs»), - это избегать операций загрузки/хранения, чтобы получить лучшие результаты, но это становится хуже.

+0

Проверьте свой результат с помощью objdump, поскольку asm не является изменчивым, может быть, компилятор удаляет его вместе. Важно проверить двоичный код, сгенерированный при выполнении таких функций. – auselen

+0

@auselen GCC автоматически обрабатывает встроенные операторы сборки без выходных операндов как изменчивые, иначе он всегда будет устранять их при включении оптимизации. Здесь не проблема. –

+0

Обратите внимание, что у вас есть функция, помеченная 'inline'; когда копия функции _body_ встроена на сайт вызова, больше нет _call_, поэтому, как только вы завершаете оптимизатор, все ставки не учитываются при распределении регистров, независимо от того, какое соглашение может видеть внешняя видимая копия функции , – Notlikethat

ответ

3

С x не является возвращаемым значением, которое не обязательно должно быть в R0. Возвращаемое значение является результатом вычисления выражения, указанного в операторе return. Таким образом, с return x возвращаемое значение не равно x, возвращаемым значением является значение x. Это важное различие, потому что это означает, что x не нужно проживать в R0, только то, что значение в x необходимо скопировать в R0 до возвращения функции.

Так как последний оператор, который будет исполнен в вашей функции, это return (x);, то это означает, что последнее, что делает ваша функция, это копия x в R0, которая сгибает значение, которое вы сохранили в R0 в своей встроенной инструкции сборки.

Вот почему вы всегда должны полностью описывать влияние на машину состояния своих встроенных операторов сборки. Компилятор не знает, что вы хотите сохранить значение в R0. Он не подозревает, что вы ожидаете, что значение, переданное в параметре x, будет в R0 при входе в оператор asm. Это может быть истинно из-за соглашения о вызове, но правила вызывающего соглашения применяются только при входе и выходе к функции, а не в середине функции, где указан оператор asm. Если ваша функция встроена в другую функцию, то соглашение о вызове вообще не применяется, поскольку нет фактического вызова функции.

Так что вы хотите что-то вроде этого:

__inline int optAbsVal(int x) 
{ 
    asm("CMP %0, #0\n" 
     "IT LT\n" 
     "RSBLT %0, %0, #0" 
     : "+r" (x) : : "cc"); 
    return(x); 
} 
+0

ОК, мне пришлось использовать asm volatile, но он отлично работает. – CDevel

+1

@ CDevel Вам не нужно 'volatile', и вы также не должны использовать его. GCC будет исключать только оператор asm, если выход не используется, и это то, что вы хотите. Когда результат не используется, наиболее оптимальная реализация функции абсолютного значения - ничто. Чтобы проверить, работает ли встроенная сборка, вам нужно написать более реалистичный пример, в котором фактически используется результат. Например, вы можете использовать 'printf' для печати. –

+0

Возможно, что-то я не могу понять об изменчивости.Я пробовал функцию в длинном цикле для выполнения теста, и если я не использую volatile, цикл становится бесконечным. Моя петля такова: volatile int result = 0;
volatile int number = 0;
для (int C = 0; C result = optAbsVal (number); – CDevel

1

Инлайн асмовый совершенно бессмысленно здесь. GCC уже знает, как оптимизировать абсолютную ценность, и скрытие этого процесса от компилятора внутри встроенного asm сделает ваш код более оптимистичным, а не лучше. https://gcc.gnu.org/wiki/DontUseInlineAsm

Дать абсолютное значение в чистых C всегда, по крайней мере так же хорошо (если компилятор не решает сделать код ветвистого после встраивания во что-то, и профилировании показывает, что разветвление было неправильным выбором.)

absval(int x) { 
    return x<0 ? -x : x; // ternary often compiles branchlessly 
} 

К преимуществам над inline asm относятся: компиляторы знает Результат неотрицателен и может оптимизироваться соответствующим образом. Например, он может делить на 2 с простым сдвигом вправо, вместо того, чтобы учитывать различное округление сдвигов против.С подписано разделение:

void foo_asm (int *arr, int len) { 
    for (int i=0 ; i<1024 ; i++){ 
     arr[i] = optAbsVal(arr[i])/4; // Using Ross's correct implementation 
    } 
} 

внутренний цикл (from gcc6.3 -O3 -mcpu=cortex-a7 -mthumb on the Godbolt compiler explorer):

.L4: 
    ldr  r3, [r2, #4] 
    CMP r3, #0    @@@@ Inline asm version 
IT LT 
RSBLT r3, r3, #0 
    adds r1, r3, #3 
    bics r3, r3, r3, asr #32 
    it  cs 
    movcs r3, r1   @ x = x<0 ? x+3 : x (I think, I didn't look up BICS) 
    asrs r3, r3, #2  @ x >>= 2 
    str  r3, [r2, #4]! 
    cmp  r2, r0 
    bne  .L4 

против

void foo_pure (int *arr, int len) { 
    for (int i=0 ; i<1024 ; i++){ 
     arr[i] = absval(arr[i])/4; // Using my pure C 
    } 
} 

.L8:    @@@@@@@@ Pure C version 
    ldr  r3, [r2, #4] 
    cmp  r3, #0   @ gcc emitted exactly your 3-insn sequence on its own 
    it  lt 
    rsblt r3, r3, #0 
    asrs r3, r3, #2  @ non-negative division by 4 is a trivial >> 2 
    str  r3, [r2, #4]! 
    cmp  r1, r2 
    bne  .L8 

Зная, что подписанный переменная является неотрицательным часто является ценным для компилятора.signed overflow is undefined behaviour, так что это позволено игнорировать тот факт, что 0 - 0x80000000 = 0x80000000, т.е. -INT_MIN все еще имеет свой знак установлен бит, потому что -INT_MIN является UB. most negative number является частным случаем для дополнения до 2.)


gcc мог бы сделать еще лучше, посмотрев флаги, уже установленные предыдущими инструкциями, вместо того, чтобы делать cmp. (Это также может обеспечить лучшее планирование инструкций для ячеек в порядке).

Но absval(100 + arr[i]) я вижу

adds r3, r3, #100 
    cmp  r3, #0 
    it  lt 
    rsblt r3, r3, #0 

вместо использования the sign flag alone for the MInus condition.

@ hand-written, IDK why gcc doesn't do this, probably missed optimization: 
    adds r3, r3, #100 # set flags 
    it  MI    # use the MInus condition instead of LessThan 
    rsbmi r3, r3, #0 

Инлайн ASM также не воспользоваться инструкциями 3-операнда руки. rsb может выдавать результат в другом регистре, чем вход (в режиме ARM, по крайней мере, и в унифицированном синтаксисе IT не требует режима большого пальца). Но вы не можете просто использовать отдельный выходной операнд для x, если вы хотите, чтобы ваш asm все еще собирался в режиме Thumb, где rsb r1, r0, #0 не собирался.

А также встроенные asm блокируют постоянное распространение. optAbsVal(-1) компилирует 4 инструкции, чтобы перевернуть их во время выполнения. absval(-1) компилируется до постоянной времени компиляции 1.

На объектах с NEON, inline-asm также нельзя авто-векторизовать. Он также может заставить компилятор не развернуть цикл, если он в противном случае имел бы.

Смежные вопросы