Я делаю цикл для суммирования двух массивов. Моя цель — сделать это, избегая переноса чеков c = a + b; carry = (c<a)
, Я потерял CF
когда я делаю тест петли, с cmp
инструкция.
В настоящее время я использую и JE
а также STC
проверить и установить ранее сохраненное состояние CF
, Но прыжок занимает более менее 7 циклов, чего много для того, чего я хочу.
//This one is working
asm(
"cmp $0,%0;""je 0f;""stc;""0:""adcq %2, %1;""setc %0"
: "+r" (carry), "+r" (anum)
: "r" (bnum)
);
Я уже пытался использовать SAHF
(2 + 2 (mov) циклов), но это не сработало.
//Do not works
asm(
"mov %0, %%ah;""sahf;""adcq %2, %1;""setc %0"
: "+r" (carry), "+r" (anum)
: "r" (bnum)
);
Кто-нибудь знает способ установить CF
быстрее? Как прямой ход или что-то подобное ..
Цикл без забивания CF
будет быстрее. Смотрите эту ссылку для некоторых лучших циклов asm.
Не пытайтесь писать только adc
со встроенным ассемблером внутри цикла C. Это невозможно для оптимальности, потому что вы не можете попросить gcc не забивать флаги. Попытка изучить asm с помощью встроенного asm в GNU C сложнее, чем написать отдельную функцию, особенно. в этом случае, когда вы пытаетесь сохранить флаг переноса.
Вы могли бы использовать setnc %[carry]
сохранить и subb $1, %[carry]
восстановить. (Или же cmpb $1, %[carry]
Я думаю.) Или, как указывает Стивен, negb %[carry]
,
0 - 1
производит перенос, но 1 - 1
не делает.
Использовать uint8_t
переменная для хранения переноса, так как вы никогда не добавите его непосредственно в %[anum]
, Это исключает любую возможность частичные задержки. например
uint8_t carry = 0;
int64_t numa, numb;
for (...) {
asm ( "negb %[carry]\n\t""adc %[bnum], %[anum]\n\t""setc %[carry]\n\t": [carry] "+&r" (carry), [anum] "+r" (anum)
: [bnum] "rme" (bnum)
: // no clobbers
);
}
Вы также можете предоставить альтернативный шаблон ограничений для источника регистра, reg / mem dest. я использовал х86 "e"
ограничение вместо "i"
потому что 64-битный режим все еще допускает только 32-битные расширения с расширением знака. gcc должен будет самостоятельно получать большие константы времени компиляции в регистр. Керри рано забит, так что даже если это и bnum
были оба 1
для начала, gcc не может использовать один и тот же регистр для обоих входов.
Это все еще ужасно и увеличивает длину цепочки зависимостей, переносимых циклами, с 2c до 4c (Intel pre-Broadwell) или с 1c до 3c (Intel BDW / Skylake и AMD).
Таким образом, ваш цикл работает с 1/3 скорости, потому что вы используете kludge вместо записи всего цикла в asm.
В предыдущей версии этого ответа предлагалось добавить перенос непосредственно, а не восстанавливать его в CF
, Этот подход имеет фатальный недостаток: он перепутал входящий перенос в эту итерацию с исходящим переносом, переходящим к следующей итерации.
Также, sahf
установлен AH от Flags. lahf
это загрузить AH в флаги (и он работает на целых младших 8 битах флагов. Соедините эти инструкции; не используйте lahf
на 0 или 1, что вы получили от setc
,
Прочтите справочное руководство по insn set для любых insns, которые, кажется, не выполняют то, что вы ожидаете. Увидеть https://stackoverflow.com/tags/x86/info
Если размер массива известен во время компиляции, вы можете сделать что-то вроде этого:
#include <inttypes.h>
#include <malloc.h>
#include <stdio.h>
#include <memory.h>
#define str(s) #s
#define xstr(s) str(s)
#define ARRAYSIZE 4
asm(".macro AddArray2 p1, p2, from, to\n\t""movq (\\from*8)(\\p2), %rax\n\t""adcq %rax, (\\from*8)(\\p1)\n\t"".if \\to-\\from\n\t"" AddArray2 \\p1, \\p2, \"(\\from+1)\", \\to\n\t"".endif\n\t"".endm\n");
asm(".macro AddArray p1, p2, p3\n\t""movq (\\p2), %rax\n\t""addq %rax, (\\p1)\n\t"".if \\p3-1\n\t"" AddArray2 \\p1, \\p2, 1, (\\p3-1)\n\t"".endif\n\t"".endm");
int main()
{
unsigned char carry;
// assert(ARRAYSIZE > 0);
// Create the arrays
uint64_t *anum = (uint64_t *)malloc(ARRAYSIZE * sizeof(uint64_t));
uint64_t *bnum = (uint64_t *)malloc(ARRAYSIZE * sizeof(uint64_t));
// Put some data in
memset(anum, 0xff, ARRAYSIZE * sizeof(uint64_t));
memset(bnum, 0, ARRAYSIZE * sizeof(uint64_t));
bnum[0] = 1;
// Print the arrays before the add
printf("anum: ");
for (int x=0; x < ARRAYSIZE; x++)
{
printf("%I64x ", anum[x]);
}
printf("\nbnum: ");
for (int x=0; x < ARRAYSIZE; x++)
{
printf("%I64x ", bnum[x]);
}
printf("\n");
// Add the arrays
asm ("AddArray %[anum], %[bnum], " xstr(ARRAYSIZE) "\n\t""setc %[carry]" // Get the flags from the final add
: [carry] "=q"(carry)
: [anum] "r" (anum), [bnum] "r" (bnum)
: "rax", "cc", "memory");
// Print the result
printf("Result: ");
for (int x=0; x < ARRAYSIZE; x++)
{
printf("%I64x ", anum[x]);
}
printf(": %d\n", carry);
}
Это дает такой код:
mov (%rsi),%rax
add %rax,(%rbx)
mov 0x8(%rsi),%rax
adc %rax,0x8(%rbx)
mov 0x10(%rsi),%rax
adc %rax,0x10(%rbx)
mov 0x18(%rsi),%rax
adc %rax,0x18(%rbx)
setb %bpl
Поскольку добавление 1 ко всем символам f полностью переполнит все, вывод из кода выше:
anum: ffffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffff
bnum: 1 0 0 0
Result: 0 0 0 0 : 1
Как написано, ARRAYSIZE может содержать до 100 элементов (из-за пределов вложенности макросов в gnu). Похоже, этого должно быть достаточно …