Перемещает бит быстрее, чем умножение и деление на Java?.СЕТЬ?

Перемещение битов влево и вправо, по-видимому, быстрее, чем операции умножения и деления на большинстве, возможно даже на всех, процессорах, если вы используете мощность 2. Однако это может уменьшить ясность кода для некоторых читателей и некоторые алгоритмы, Является ли бит-сдвиг действительно необходимым для производительности, или я могу ожидать, что компилятор или виртуальная машина заметят случай и оптимизируют его (в частности, когда сила-2 является литералом)? Меня в основном интересуют поведение Java и .NET, но также приветствуем понимание других реализаций языка.

Ответ 1

Большинство компиляторов сегодня будут делать больше, чем конвертировать умножить или разделить на две операции смены. При оптимизации многие компиляторы могут оптимизировать умножение или деление с постоянной времени компиляции, даже если это не сила 2. Часто умножение или деление можно разложить на ряд сдвигов и добавлений, и если эта серия операций будет быстрее чем умножить или разделить, компилятор будет использовать его.

Для деления на константу компилятор может часто преобразовывать операцию в умножение на "магическое число", за которым следует сдвиг. Это может быть основной заставкой с тактовым циклом, поскольку умножение часто происходит намного быстрее, чем операция деления.

Книга Генри Уоррена, Hacker Delight, содержит множество информации по этой теме, которая также хорошо освещена на веб-сайте компаньона:

См. также обсуждение (со ссылкой или двумя) в:

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

Ответ 2

Почти любая окружающая среда, достойная его соли, оптимизирует это для вас. И если это не так, у вас есть большая рыба, чтобы жарить. Серьезно, не тратьте больше внимания на это. Вы узнаете, когда у вас проблемы с производительностью. И после запуска профилировщика вы узнаете, что вызывает его, и должно быть достаточно ясно, как его исправить.

Вы никогда не услышите, как кто-либо скажет: "Моя заявка была слишком медленной, затем я начал случайным образом заменять x * 2 на x << 1, и все было исправлено!" Проблемы с производительностью, как правило, решаются путем поиска способа сделать работу на порядок меньше, а не найти способ сделать одну и ту же работу на 1% быстрее.

Ответ 3

В этих случаях люди ошибаются.

99%, когда они пытаются догадаться о современных (и всех будущих) компиляторах.
99,9%, когда они пытаются вторгаться в современные (и все будущие) JIT в одно и то же время.
99,999%, когда они пытаются второй угадать современные (и все будущие) оптимизации ЦП.

Программа таким образом, чтобы точно описать то, что вы хотите выполнить, а не как это сделать. Будущие версии JIT, VM, компилятора и процессора могут быть независимо улучшены и оптимизированы. Если вы укажете что-то настолько маленькое и конкретное, вы потеряете выгоду от всех будущих оптимизаций.

Ответ 4

Вы можете почти наверняка зависеть от оптимизации умножения буквальной мощности в два на операцию сдвига. Это одна из первых оптимизаций, которые будут изучать студенты компиляции.:)

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

Ответ 5

Обратите внимание, что сдвиг вниз и деление (в Java, конечно) дают разные результаты для отрицательных, нечетных чисел.

int a = -7;
System.out.println("Shift: "+(a >> 1));
System.out.println("Div:   "+(a / 2));

Печать

Shift: -4
Div:   -3

Так как Java не имеет никаких неподписанных номеров, для компилятора Java это не реально оптимизировать.

Ответ 6

На компьютерах, которые я тестировал, целые деления от 4 до 10 раз медленнее, чем другие операции.

Когда компиляторы могут заменить деления на кратные 2 и заставить вас не видеть разницы, деления, не кратные 2, значительно медленнее.

Например, у меня есть (графическая) программа с множеством многих делений на 255. Фактически, мои вычисления:

r = (((top.R - bottom.R) * alpha + (bottom.R * 255)) * 0x8081) >> 23;

Я могу гарантировать, что это намного быстрее, чем предыдущие вычисления:

r = ((top.R - bottom.R) * alpha + (bottom.R * 255)) / 255;

так что нет, компиляторы не могут делать все трюки оптимизации.

Ответ 7

Я бы спросил: "Что ты делаешь, что это имеет значение?". Сначала создайте свой код для удобства чтения и обслуживания. Вероятность того, что стандартное умножение битов смещения стилей приведет к разнице в производительности, ЧРЕЗМЕРНО мало.

Ответ 8

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

На современном оборудовании (Pentium Pro и выше) конвейерная обработка делает это совершенно неуместным и отклоняется от проторенного пути, как правило, означает, что вы теряете гораздо больше оптимизаций, чем можете получить.

Микрооптимизация - это не только трата вашего времени, но и чрезвычайно трудно получить право.

Ответ 9

Если компилятор (константа времени компиляции) или JIT (константа времени выполнения) знает, что делитель или мультипликатор является степенью двух и целочисленной арифметики, он преобразует ее в сдвиг для вас.

Ответ 10

Я ошеломлен, так как я просто написал этот код и понял, что переход на один на самом деле медленнее, чем умножение на 2!

(EDIT: изменил код, чтобы перестать переполняться после предложения Майкла Майерса, но результаты одинаковы! Что здесь не так?)

import java.util.Date;

public class Test {
    public static void main(String[] args) {
        Date before = new Date();
        for (int j = 1; j < 50000000; j++) {
            int a = 1 ;
            for (int i = 0; i< 10; i++){
                a *=2;
            }
        }
        Date after = new Date();
        System.out.println("Multiplying " + (after.getTime()-before.getTime()) + " milliseconds");
        before = new Date();
        for (int j = 1; j < 50000000; j++) {
            int a = 1 ;
            for (int i = 0; i< 10; i++){
                a = a << 1;
            }
        }
        after = new Date();
        System.out.println("Shifting " + (after.getTime()-before.getTime()) + " milliseconds");
    }
}

Результаты:

Умножение 639 миллисекунд
Смена 718 миллисекунд

Ответ 12

Большинство компиляторов в случае необходимости будут умножать и делить на сдвиги бит. Это одна из самых простых оптимизаций. Итак, вы должны сделать то, что более легко читаемо и подходит для данной задачи.

Ответ 13

Это анализ теста, сделанного Саввасом Далкицисом. Хотя этот тест проверяет скорость умножения по смещению битов, используемые значения не совпадают, проверьте код ниже на С#, который отображает значения)

for (int i = 0, j = 1, k = 1; i < 10; i++)
{
  j = j * 2;
  k <<= 2;
  Console.WriteLine("{0} {1}", j, k);
}

Эквивалентный код примера Savvas Dalkitsis со значениями, отображаемыми на С#, будет

    public static void Main(String[] args)
    {
        for (int i = 0, j = 1, k = 1; i < 10; i++)
        {
            j = j * 2; k <<= 2;
            Console.WriteLine("{0} {1}", j, k);
        }
        Console.WriteLine("-----------------------------------------------");


        DateTime before = DateTime.Now;
        for (int j = 1; j < 500000000; j++)
        {
            int a = 1;
            for (int i = 0; i < 10; i++) a *= 2;
        }
        DateTime after = DateTime.Now;

        Console.WriteLine("Multiplying " + (after - before).ToString() + " milliseconds");

        before = DateTime.Now;
        for (int j = 1; j < 500000000; j++)
        {
            int a = 1;
            for (int i = 0; i < 10; i++) a = a << 1;
        }
        after = DateTime.Now;
        Console.WriteLine("Shifting " + (after - before).ToString() + " milliseconds");
        Console.ReadKey();
    }