Полировка глюкал
Jul. 16th, 2013 10:56 pmРассмотрим целочисленное деление, точнее, вычисление остатка. Эта операция долгая, многотактная, поэтому при каждой удобной возможности - например, при вычислении остатка от деления на константу - ее нужно оптимизировать. Тривиальный случай остатка от деления на степень двойки исключаем; а деление 32-битного целого на произвольное наперед заданное число M делается просто: умножаем на "обратную величину" - число 2N/M операцией, дающей в результате 64-битное число, и берем старшие разряды (N и количество старших разрядов определяются общим количеством бит M и количеством младших нулевых бит в нем, вручную это делать не нужно, компилятор уже сам умеет). Если нужно найти остаток, то умножаем результат на M и вычитаем из исходного числа. К примеру,
unsigned mod(unsigned x) {
return x % 2037795097;
} с помощью весьма нового GCC 4.7.2 превращается в movl 4(%esp), %ecx movl $-2031890883, %edx movl %ecx, %eax mull %edx ; умножаем на "обратную величину" shrl $30, %edx ; берем старшие разряды imull $2037795097, %edx, %edx ; умножаем частное на аргумент subl %edx, %ecx ; вычитаем movl %ecx, %eax retНетрудно проверить, что -2031890883 - это то же самое, что и 232-2031890883 = 2263076413, а 262/2037795097 = 2263076412.94.
Казалось бы, круто. Миллиард таких вычислений остатка делается около 4 с четвертью секунд на моем ноутбуке трехлетней давности под виртуальной машиной, чего еще желать?
Оказывается, еще есть, чего. Если вычисляется остаток от деления на "большое" число, почти под обрез разрядной сетки, то ничего ни на что умножать не надо, а достаточно лишь несколько раз аккуратно вычесть:
unsigned mod(unsigned x) {
unsigned a;
a = x - 2037795097;
if (a < x) x = a;
a = x - 2037795097;
if (a < x) x = a;
return x;
}что выходит из-под пресса какmovl 4(%esp), %eax leal -2037795097(%eax), %edx cmpl %eax, %edx cmovbe %edx, %eax leal -2037795097(%eax), %edx cmpl %eax, %edx cmovbe %edx, %eax retНи единого разрыва потока команд, прошу заметить. Три секунды ровно.
До этой оптимизации, которая годится для нахождения остатка от деления на любое число, помещающееся в разрядной сетке не более 3 раз, гнутые товарищи не дотумкали.
no subject
Date: 2013-07-17 07:37 am (UTC)Ну и, под виртуальной машиной на однопоточном коде вроде не должно сказаться.
no subject
Date: 2013-07-17 03:36 pm (UTC)no subject
Date: 2013-07-17 07:59 am (UTC)unsigned mod(unsigned x, unsigned int d) {
if(d>x)
return x;
return x % d;
}
no subject
Date: 2013-07-17 03:11 pm (UTC)no subject
Date: 2013-07-17 05:26 pm (UTC)no subject
Date: 2013-07-17 06:05 pm (UTC)no subject
Date: 2013-07-17 06:53 pm (UTC)unsigned mod(unsigned x) {
if(2037795097>х)
return x;
return x % 2037795097;
}
а потом вот такое:
#define mod(х) ((2037795097>х)?х:х%2037795097)
no subject
Date: 2013-07-17 07:17 pm (UTC)no subject
Date: 2013-07-17 07:03 pm (UTC)% foreach i (VARMOD CONSTMOD SUBTRACT)
foreach? gcc -O5 -D$i -o mod mod.c
foreach? time ./mod > /dev/null
foreach? end
4.275u ...
1.589u ...
0.789u ...
Печатают все три варианта, естественно, одно и то же: 326015744
Текст программы:
#include <stdio.h> #if defined(CONSTMOD) unsigned mod(unsigned x) { return x % 2037795097; } #elif defined(SUBTRACT) unsigned mod(unsigned x) { unsigned a; a = x - 2037795097; if (a < x) x = a; a = x - 2037795097; if (a < x) x = a; return x; } #elif defined(VARMOD) unsigned y = 2037795097; unsigned mod(unsigned x) { return x % y; } #endif main() { unsigned i, x = 0; static unsigned base = 100000000; for (i = base; i < base+1000000000; ++i) { x += mod(i); } printf("%d\n", x); }no subject
Date: 2013-07-17 07:14 pm (UTC)unsigned i, x = 1; for (i = 0; i < 1000000000; ++i) { x += mod(x); } printf("%d\n", x);Добавляем
#elif defined(CHECK) unsigned mod(unsigned x) { return x < 2037795097 ? x : x % 2037795097; }Получаем, на foreach i ( VARMOD CONSTMOD SUBTRACT CHECK )
7.906u
3.908u
2.043u
3.612u
Архитектуру не проведёшь.
no subject
Date: 2013-07-17 08:27 am (UTC)no subject
Date: 2013-07-17 03:26 pm (UTC)no subject
Date: 2013-07-17 09:42 am (UTC)no subject
Date: 2013-07-17 03:29 pm (UTC)no subject
Date: 2013-07-17 04:58 pm (UTC)no subject
Date: 2013-07-17 06:02 pm (UTC)unsigned mod(a) unsigned a; { return a / 3; }с помощью
apout bin/cc -S -O2 mod.cполучаем
no subject
Date: 2013-07-17 06:40 pm (UTC)(Альцгеймер, правда, подсказывает, что это могло быть в run time библиотеке и/или эмуляторе отсутствующих команд…)
no subject
Date: 2013-07-17 06:46 pm (UTC)no subject
Date: 2013-07-17 04:56 pm (UTC)