Подтвердить что ты не робот

Как я могу прочность уменьшить деление на 2 ^ n + 1?

Мне нужно выполнить некоторые целые деления в горячем пути моего кода. Я уже определил с помощью профилирования и подсчета циклов, что целочисленные деления меня ценят. Я надеюсь, что что-то, что я могу сделать, чтобы прогнать дивизии во что-то более дешевое.

В этом пути я делясь на 2 ^ n + 1, где n - переменная. По сути, я хочу оптимизировать эту функцию, чтобы удалить оператор деления:

unsigned long compute(unsigned long a, unsigned int n)
{
    return a / ((1 << n) + 1);
}

Если бы я разделил на 2 ^ n, я бы просто заменил div на shift-right на n. Если бы я делялся на константу, я бы позволил силе компилятора уменьшить это конкретное деление, вероятно, превратив его в умножение и некоторые сдвиги.

Существует ли аналогичная оптимизация, применимая к 2 ^ n + 1?

Изменить: здесь может быть произвольное 64-битное целое число. n принимает только несколько значений между 10 и, скажем, 25. Я могу, конечно, прекомпретировать некоторые значения для каждого n, но не для a.

4b9b3361

Ответ 1

Поскольку вы можете только сдвинуть int столько мест, вы можете поместить все эти случаи в выбор одного из нескольких делений константой:

unsigned long compute(unsigned long a, unsigned int n)
{
    // assuming a 32-bit architecture (making this work for 64-bits 
    // is left as an exercise for the reader):
    switch (n) {
        case  0: return a / ((1 << 0) + 1);
        case  1: return a / ((1 << 1) + 1);
        case  2: return a / ((1 << 2) + 1);

            // cases 3 through 30...

        case 31: return a / ((1 << 31) + 1);
    }
}

Итак, теперь каждое разделение является константой, которую компиляторы обычно сводят к серии инструкций multiply/shift/add (в качестве упомянутого вопроса). См. Выполняет ли компилятор c/С++ оптимизацию постоянных делений по значению "сила-два" в сдвигах? для deatils.

Ответ 2

Вы можете заменить целочисленное деление на константу, умножением (по модулю словесности) на магическое число и сдвиг.

Магические числа могут быть предварительно рассчитаны для известных констант.

Так как n не может принимать много значений, например. 0..31 "легко" предварительно вычислить эти магические числа для всех n и сохранить их в таблице с 32 элементами.

Страница Javascript для расчета магических чисел

Хороший компилятор может вычислить магические числа и заменить целочисленное деление на умножение и сдвиг, если делитель является постоянным во время компиляции. В зависимости от того, как остальная часть кода структурирована вокруг критического кода производительности, вы можете использовать макросы или встроенные трюки для разворачивания для всех возможных значений n и позволить компилятору выполнять поиск магических чисел (аналогично ответу с помощью переключателя, но я бы поместил больше кода в константный регион, иначе это могло бы быть не выгодно, а разветвление может также стоить вам производительности.

Подробное описание вместе с кодом для расчета магических чисел можно оформить в книге "Хакерский восторг" Генри С. Уоррена-младшего (настоятельно рекомендуется иметь книгу!) стр. 180ff.

Ссылка на Google Книги для соответствующей главы:

Глава 10-9 Беззнаковое разделение по делителям >= 1