#c #оптимизация #производительность
Все источники утверждают, что в циклах использование регистровых переменных очень
хорошо для производительности. Для меня остаётся не до конца понятен вопрос как компилятор
оптимизирует код в том месте, где происходит сравнение индексной переменной i с переменой
length.
double array[100ULL];
size_t length = 20ULL;
for(register size_t i = 0ULL; i < length - 1ULL; i++){
array[i] = (double)i;
}
В этом примере индексная переменная i - регистровая. Но сравнение внутри for происходит
с переменной length, которая НЕ объявлена как регистровая. Помимо прочего перед сравнением
происходит математическая операция.
Хочу для себя прояснить пару-тройку моментов:
Будет ли откомпиллированная программа при каждой итерации цикла
производить эту математическую операцию (вычитание единицы из
меременной length) раз за разом или эта операция будет произведена
только один раз перед началом цикла?
Догадается ли компилятор оптимизировать переменную length в
регстровую?
Какой код будет правильнее с точки зрения производительности и
однозначности для компилятора: тот, что выше, или нижеприведённый?
И есть ли вообще разница для современных компиляторов?
Второй вариант кода, в котором обе сравниваемые переменные регистровые и операция
вычитания однозначно производится перед циклом:
double array[100ULL];
register const size_t length = 10ULL - 1ULL;
for(register size_t i = 0ULL; i < length; i++){
array[i] = (double)i;
}
Трансляция кода в asm ситуацию не прояснила, поэтому задаю напрямую.
Ответы
Ответ 1
Забудьте об этом слове register, оно давно не имеет никакого смысла. Ответы на вопросы 1-2 зависят от компилятора; наверное, можно найти старый тупой компилятор, который не сумеет выполнить такие простые оптимизации.. А разумный - вообще может использовать какие-нибудь векторные команды процессора или что еще. Так что ответ на вопрос 3 - нет, такой простой цикл будет оптимизирован любым более-менее разумным компилятором. Кстати, VC++2017 просто развернул этот цикл в mov eax, 1 xor ebx, ebx cvtsi2sd xmm1, rbx movsd QWORD PTR array$[rsp], xmm1 xorps xmm1, xmm1 cvtsi2sd xmm1, rax mov eax, 2 cvtsi2sd xmm2, rax mov eax, 3 ..... movsd QWORD PTR array$[rsp+128], xmm2 movsd QWORD PTR array$[rsp+136], xmm1 xorps xmm1, xmm1 cvtsi2sd xmm1, rax movsd QWORD PTR array$[rsp+144], xmm1 ругнувшись при этом на слово register как давно не поддерживаемое... Результат видите сами. Очень старенький OpenWatcom пошел по циклическому пути - L$1: cmp eax,13H jae L$2 mov dword ptr 320H[esp],eax xor ecx,ecx add edx,8 mov dword ptr 324H[esp],ecx inc eax fild qword ptr 320H[esp] fstp qword ptr -8[esp+edx] jmp L$1 но, как видите, вычислив 19 сразу, и работая только с регистрами...Ответ 2
gcc 6 сосчитал при компиляции и использовал сравнение счётчика с константой: for(register size_t i = 0ULL; i < length - 1ULL; i++){ 583: 31 c0 xor eax,eax int main (void) { 585: 53 push rbx 586: 48 81 ec 20 03 00 00 sub rsp,0x320 58d: 48 89 e3 mov rbx,rsp array[i] = (double)i; 590: 66 0f ef c0 pxor xmm0,xmm0 594: f2 48 0f 2a c0 cvtsi2sd xmm0,rax 599: f2 0f 11 04 c3 movsd QWORD PTR [rbx+rax*8],xmm0 for(register size_t i = 0ULL; i < length - 1ULL; i++){ 59e: 48 83 c0 01 add rax,0x1 5a2: 48 83 f8 13 cmp rax,0x13 5a6: 75 e8 jne 5905a8: 4c 8d a3 98 00 00 00 lea r12,[rbx+0x98] } Ровно то же самое он выдал без объявления i как register. Ответ 3
Не надейтесь на компилятор, а оптимизируйте сами. Меньше пользуйтесь глобальными переменными. Самый популярный цикл в коде: double array[100ULL]; size_t length = 20ULL; for(size_t i = 0ULL; i < length - 1ULL; i++){ array[i] = (double)i;} пользуется глобальной переменной lenght. Этот доступ вы можете сами убрать лично: double array[100ULL]; size_t length = 20ULL; for(size_t i = length; i > 0; ){ -- i ; array[i] = (double)i;} В этом цикле будет только быстрая проверка на ноль. Уже быстрее. Дальше постоянно используется массив array с индексацией (сложение указателя с числом умноженный на размер double). Этот лишний код все убирают используя цикл по указателю. double array[100ULL]; size_t length = 20ULL; double * arrayj = &(array[length]) ; for(size_t i = length; i > 0; ){ -- i ; -- arrayj ; (*arrayj) = (double)i;} В данном цикле используется только вычитание и сравнение с нулём. Попробуйте все примеры переносить в ассемблер и сравнить коды. Тогда вы поймёте кто плохо оптимизирует программы : компилятор или вы.
Комментариев нет:
Отправить комментарий