На всех процессорах операция деления происходит относительно медленно, и с этим ничего нельзя поделать.
Но если делитель — константа, то деление можно заменить умножением на какую-нибудь другую константу (обратное число, вычисляемое во время компиляции).
Тогда код будет работать немного быстрее (и потреблять меньше энергии).
Многие компиляторы (gcc, MSVC) делают такую оптимизацию, но оказывается, что многие разработчики не знают, как рассчитывается коэффициент, и это нетривиально.
Далее мы объясним, как рассчитывается коэффициент.
Идея и аргументы в пользу действительных чисел
Например, если в коде двойной х = а/2,5 , то это можно заменить на х = а * (1,0/2,5) или х = а * 0,4 .Код будет работать быстрее, но иногда менее точно.
Для целых чисел такой трюк не пройдет, потому что.
множитель будет меньше единицы (то есть ноль).
Вы можете использовать числа с фиксированной точкой (мантисса Н ): int x = a/b = a * B > > N, где B = (1 << N) / b .
Проблема в том, что иногда рассчитанное таким образом x будет отличаться на 1 от правильного ответа, что неприемлемо для целых чисел.
Алгоритм
Для простоты мы будем работать только с неотрицательными числами.Математическая формулировка: Для известного целого б найдите такую пару целых чисел М, Б так что для всего целого а : 0 ≤ а < (1 << Н ) выполненный Икс = [a/b] = [a * B > > M] , где [ ] — целая часть, а > > М это разделить а в 2 степени М (и << multiply).
Давайте запишем это 1,0/б в двоичной форме (иногда бесконечно длинное число), первое М бит которого мы обозначаем как целое число С , остальные биты (хвостовой остаток) как Д , те 1,0/б = С > > М + Д, Д < (1 > > М) .
Давайте посмотрим, что произойдет, если Б=С И а * Д ≤ 1 (те Н ≤ М ): x = [a / b] = [a * (C > > M + D)]= [a * C > > M + a*D] ≥ [a * C > > M] + [a*D] = [ а * С > > М] Оказывается, если Б это первые М бит числа 1/б , а оставшиеся биты отбросить (заменить их нулями), тогда мы будем иногда получать правильные значения, а иногда на 1 меньше Икс .
Это происходит потому, что его отбрасывают объявление , который хоть и меньше единицы, но все же может изменить всю деталь.
Давайте попробуем увеличить Б за единицу (тех Б=С+1 ), Затем: [a*(C + 1) > > M] = [a*C > > M + a * (1 > > M)] ≥ [a * C > > M + a*D] = [a * (C > > M + D)] = [a/b] = x Теперь оказывается, что иногда мы получаем правильный ответ Икс , а иногда и еще 1. Может показаться трудным точно рассчитать Икс Через умножение это невозможно, потому что при одном приближении иногда вы получите числа на 1 меньше необходимого, а при другом приближении они будут на 1 больше.
Но это неправда.
После всего а являются целыми числами, а максимальная дробная часть а/б точно равно (б-1)/б , и в расчетах Икс оно увеличивается на а * ((1 > > М) – Д) .
Да, это значит, если а * ((1> > М) – Д) < 1 / b тогда неравенство превращается в равенство!!! Пусть число л такой, что (1 << L) ≥ b , то для равенства достаточно (1 > > M) ≤ (1 > > (L + N)) или М ≥ Л + Н .
Пример на Java
В примере делитель равен 127 ( л = 7).Результат умножения на JVM должен укладываться в 32 бита, выбираем Н = (32 – 7) / 2 = 12. Пример:
На моем Intel Core-i5 2500, JVM 1.7.0u5 с JIT и опцией -XX:+AggressiveOpts тест деления работает. в 2 раза медленнее чем при умножении.final static int N = 12; private static int divideBy127(int a) { final int b = 127; final int L = 7; final int B = ((1 << (N + L)) / b) + 1; return (a * B) >>> (N + L); } public static void main(String[] args) { final int size = 20000; final int times = 10001; final int[] data = new int[size]; for (int i = 0; i < size; ++i) data[i] = (i * i) & ((1 << N) - 1); // fill by any random data for (int iteration = 0; iteration < 10; ++iteration) { long time0 = System.currentTimeMillis(); // Test 1: Using div int v0 = 0; for (int j = 0; j < times; ++j) for (int i = 0; i < size; ++i) v0 ^= data[i] / 127; // keep result long time1 = System.currentTimeMillis(); // Test 2: Using mul int v1 = 0; for (int j = 0; j < times; ++j) for (int i = 0; i < size; ++i) v1 ^= divideBy127(data[i]); // keep result long time2 = System.currentTimeMillis(); System.out.println((time1 - time0) + " vs " + (time2 - time1) + " " + (v0 - v1)); } }
Добавление
Остальные темы, не затронутые в этой статье:- дивиденд может быть отрицательным;
- как избежать переполнения при умножении;
- результат умножения целых чисел на x86 занимает 64 бита и при определенных условиях можно убрать правый сдвиг;
- если известно, что числа делятся на целое число, то можно использовать другой алгоритм;
- в некоторых случаях умножение можно заменить сдвигом влево и сложением;
- особые случаи для некоторых делителей с еще большей скоростью выполнения.
Литература
«Деление на инвариантные целые числа с использованием умножения», Торбьорн Гранлунд, Питер Л.Монтгомери
— формальное доказательство и некоторые другие алгоритмы по теме.Теги: #Алгоритмы #компиляторы #оптимизация программ #Чулан
-
Надувной Модуль Мкс
19 Oct, 24 -
Бета-Версия Google Chrome
19 Oct, 24 -
Поиск По Умолчанию На Ipad?
19 Oct, 24 -
Канделябры Против Леденца
19 Oct, 24