Заинтересованы, чтобы увидеть, если человек может бить оптимизирующий компилятор, я написал свой алгоритм двумя способами:
Здесь вы выражаете намерение, как если бы вы писали машинный код
std::uint32_t foo1(std::uint8_t uMsgByte,
std::uint32_t crc,
const std::uint32_t* pChkTableOffset)
{
if (uMsgByte & 0x80) crc ^= *pChkTableOffset; pChkTableOffset++;
if (uMsgByte & 0x40) crc ^= *pChkTableOffset; pChkTableOffset++;
if (uMsgByte & 0x20) crc ^= *pChkTableOffset; pChkTableOffset++;
if (uMsgByte & 0x10) crc ^= *pChkTableOffset; pChkTableOffset++;
if (uMsgByte & 0x08) crc ^= *pChkTableOffset; pChkTableOffset++;
if (uMsgByte & 0x04) crc ^= *pChkTableOffset; pChkTableOffset++;
if (uMsgByte & 0x02) crc ^= *pChkTableOffset; pChkTableOffset++;
if (uMsgByte & 0x01) crc ^= *pChkTableOffset; pChkTableOffset++;
return crc;
}
Здесь выражаю намерение в более алгоритмический ...
std::uint32_t foo2(std::uint8_t uMsgByte,
std::uint32_t crc,
const std::uint32_t* pChkTableOffset)
{
for (int i = 0 ; i < 7 ; ++i) {
if (uMsgByte & (0x01 << (7-i)))
crc ^= pChkTableOffset[i];
}
return crc;
}
Тогда я скомпилирован с использованием г ++ -O3 и результат был ...
точно такой же код объекта в обеих функциях
Морали: выбрать правильный алгоритм, избежать повторений, писать элегантный код, и пусть оптимизатор делать свое дело.
вот доказательство:
__Z4foo1hjPKj: ## @_Z4foo1hjPKj
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
testb $-128, %dil
je LBB0_2
## BB#1:
xorl (%rdx), %esi
LBB0_2:
testb $64, %dil
je LBB0_4
## BB#3:
xorl 4(%rdx), %esi
LBB0_4:
testb $32, %dil
je LBB0_6
## BB#5:
xorl 8(%rdx), %esi
LBB0_6:
testb $16, %dil
je LBB0_8
## BB#7:
xorl 12(%rdx), %esi
LBB0_8:
testb $8, %dil
je LBB0_10
## BB#9:
xorl 16(%rdx), %esi
LBB0_10:
testb $4, %dil
je LBB0_12
## BB#11:
xorl 20(%rdx), %esi
LBB0_12:
testb $2, %dil
je LBB0_14
## BB#13:
xorl 24(%rdx), %esi
LBB0_14:
testb $1, %dil
je LBB0_16
## BB#15:
xorl 28(%rdx), %esi
LBB0_16:
movl %esi, %eax
popq %rbp
retq
.cfi_endproc
.globl __Z4foo2hjPKj
.align 4, 0x90
__Z4foo2hjPKj: ## @_Z4foo2hjPKj
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp3:
.cfi_def_cfa_offset 16
Ltmp4:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp5:
.cfi_def_cfa_register %rbp
testb $-128, %dil
je LBB1_2
## BB#1:
xorl (%rdx), %esi
LBB1_2:
testb $64, %dil
je LBB1_4
## BB#3:
xorl 4(%rdx), %esi
LBB1_4:
testb $32, %dil
je LBB1_6
## BB#5:
xorl 8(%rdx), %esi
LBB1_6:
testb $16, %dil
je LBB1_8
## BB#7:
xorl 12(%rdx), %esi
LBB1_8:
testb $8, %dil
je LBB1_10
## BB#9:
xorl 16(%rdx), %esi
LBB1_10:
testb $4, %dil
je LBB1_12
## BB#11:
xorl 20(%rdx), %esi
LBB1_12:
testb $2, %dil
je LBB1_14
## BB#13:
xorl 24(%rdx), %esi
LBB1_14:
movl %esi, %eax
popq %rbp
retq
.cfi_endproc
Было бы интересно посмотреть, если компилятор выполняет также так хорошо с версией кода, который использует логические операции, а не условных операторов.
Дано:
std::uint32_t logical1(std::uint8_t uMsgByte,
std::uint32_t crc,
const std::uint32_t* pChkTableOffset)
{
crc ^= *pChkTableOffset++ & (!(uMsgByte & 0x80) - 1);
crc ^= *pChkTableOffset++ & (!(uMsgByte & 0x40) - 1);
crc ^= *pChkTableOffset++ & (!(uMsgByte & 0x20) - 1);
crc ^= *pChkTableOffset++ & (!(uMsgByte & 0x10) - 1);
crc ^= *pChkTableOffset++ & (!(uMsgByte & 0x8) - 1);
crc ^= *pChkTableOffset++ & (!(uMsgByte & 0x4) - 1);
crc ^= *pChkTableOffset++ & (!(uMsgByte & 0x2) - 1);
crc ^= *pChkTableOffset++ & (!(uMsgByte & 0x1) - 1);
return crc;
}
полученный машинный код:
8 много:
movl %edi, %eax ; get uMsgByte into eax
shll $24, %eax ; shift it left 24 bits so that bit 7 is in the sign bit
sarl $31, %eax ; arithmetic shift right to copy the sign bit into all other bits
andl (%rdx), %eax ; and the result with the value from the table
xorl %esi, %eax ; exclusive-or into crc
так короткий ответ - да она выполняет очень хорошо (eliding избыточные приращения pChkTableOffset)
Быстрее ли это? кто знает. Вероятно, не измеримо - количество извлечений памяти в обоих случаях одинаково. Компилятор может решить, лучше ли избегать ветвей или не намного лучше, чем вы можете (в зависимости от архитектуры, которую оптимизирует компилятор).
Является ли оно более изящным и удобочитаемым? Для меня нет. Это своего рода код, который я имел обыкновение писать, когда:
- с был еще молодой язык
- процессоры были достаточно просты, что я мог бы сделать лучшую работу по оптимизации
- процессоров настолько медленно, что я должен был
Ни один из них не применяется.
Вы должны сравнить выход asm, чтобы увидеть, есть ли какая-либо разница. –
Я скомпилирую и покажу разницу ... Но я сомневаюсь, что есть много ... –
Если вас интересует только x86, и вы серьезно относитесь к оптимизации этого то я предлагаю использовать SSE. –