Nano Hash - криптовалюты, майнинг, программирование

Являются ли If Then быстрее, чем умножение и присваивание?

У меня есть быстрый вопрос, предположим, у меня есть следующий код, и он повторяется аналогичным образом, например, 10 раз.

if blah then
    number = number + 2^n
end if

Было бы быстрее оценить:

number = number + blah*2^n?

Что также вызывает вопрос, можете ли вы умножить логическое значение на целое число (хотя я не уверен, что тип, возвращаемый из 2 ^ n, является ли он целым числом или беззнаковым и т. д.)? (Я работаю на Аде, но давайте попробуем обобщить, может быть?)

РЕДАКТИРОВАТЬ: Извините, я должен уточнить, что я смотрю на 2 в степени n, и я поставил там c, потому что меня интересовало мое собственное обучение в будущем, если я когда-нибудь столкнусь с этой проблемой в c, и я думаю, что есть больше c программисты на этих досках, а затем Ада (я предполагаю, и вы знаете, что это значит), однако моя текущая проблема связана с языком Ада, но вопрос должен быть достаточно независимым от языка (я надеюсь).


  • Оператор вставки ^ означает XOR в C, так что имейте это в виду. 26.10.2010
  • Будь осторожен. Поскольку C не имеет встроенного логического типа, нет гарантии, что значение blah равно 1 или 0. Некоторые функции, возвращающие значение true или false, могут выбрать вместо значения true возвращаемое значение, отличное от 1. 26.10.2010
  • @Brian Спасибо, я не понимал, что логическое значение не всегда означает 0 и 1, это могло оказаться трудным уроком для усвоения. 26.10.2010
  • Вокруг StackOverflow тусуется немало программистов на языке Ada, и почти у всех у нас есть RSS-каналы (или что-то подобное), настроенные для отслеживания тега «Ada», так что не беспокойтесь о том, что программист на языке Ada не заметит тег Ada. вопрос :-) 26.10.2010
  • @Marc C Спасибо, я думаю, я сделал предположение, потому что не вижу почти столько вопросов об Аде (однако это может быть моей ошибкой, потому что я не особо ищу это), поэтому я просто предположил, что чем больше вопросов один тип был, привел больше таких программистов, мой плохой, спасибо, что просветили меня :) 26.10.2010
  • @Marc C - Это довольно круто. Я просто проверяю вручную. Однако он прав в том, что это действительно независимый от языка вопрос. Единственная загвоздка, которую добавляет Ада, заключается в том, что ее компиляторы имеют больше информации, позволяющей оптимизировать работу еще лучше. Так что то, что верно для C (не делайте микрооптимизацию подобным образом), еще более верно для Ada. 26.10.2010
  • @Brian: C имеет логический тип, _Bool, с псевдонимом bool в stdbool.h. Кроме того, хотя любое ненулевое значение считается истинным для условных выражений, результатом логического выражения (операторы сравнения, логические и/или/не операторы,...) всегда является 0 или 1. 26.10.2010
  • Кстати, надеюсь, вы понимаете, что blah*2^n — это blah<<n (битовый сдвиг) и одна из самых быстрых возможных операций. 26.10.2010
  • @R..: Вы правы, C99 действительно представил явный логический тип. Я все же был бы осторожен. На самом деле эта проблема уже сталкивалась со мной раньше, особенно при взаимодействии кода на разных языках программирования. 26.10.2010

Ответы:


1

если мы говорим о C и мля не в вашей власти, то просто сделайте это:

if(blah) number += (1<<n);

На самом деле в C нет логического значения, и оно не должно быть, false равно нулю, а true не равно нулю, поэтому вы не можете предполагать, что не ноль равно 1, что вам нужно для вашего решения, и вы не можете предполагать, что какой-либо конкретный установлен бит в blah, например:

number += (blah&1)<<n;

Не обязательно будет работать, потому что 0x2 или 0x4 или что-либо ненулевое с очищенным нулевым битом считается истинным. Обычно вы обнаружите, что 0xFFF...FFFF (минус один или все единицы) используются как истинные, но вы не можете полагаться на типичный.

Теперь, если вы полностью контролируете значение в blah и держите его строго равным 0 для false и 1 для true, тогда вы можете сделать то, о чем спрашивали:

number += blah<<n;

И избегайте возможности ветки, заполнения дополнительной строки кэша и т. д.

Однако вернемся к общему случаю, взяв это общее решение:

unsigned int fun ( int blah, unsigned int n, unsigned int number )
{
    if(blah) number += (1<<n);
    return(number);
}

И компиляция для двух самых популярных/используемых платформ:

    testl   %edi, %edi
    movl    %edx, %eax
    je  .L2
    movl    $1, %edx
    movl    %esi, %ecx
    sall    %cl, %edx
    addl    %edx, %eax
.L2:

В приведенном выше примере используется условная ветвь.

Приведенный ниже использует условное выполнение, без ветвления, без сброса конвейера, является детерминированным.

  cmp   r0,#0
  movne r3,#1
  addne r2,r2,r3,asl r1
  mov   r0,r2
  bx    lr

Можно было бы сохранить инструкцию mov r0,r2, переставив аргументы в вызове функции, но это академично, вы бы не записали вызов функции на этом обычно.

РЕДАКТИРОВАТЬ:

Как было предложено:

unsigned int fun ( int blah, unsigned int n, unsigned int number )
{
    number += ((blah!=0)&1)<<n;
    return(number);
}
  subs  r0, r0, #0
  movne r0, #1
  add   r0, r2, r0, asl r1
  bx    lr

Конечно, дешевле, и код выглядит хорошо, но я бы не стал делать предположений, что результат blah!=0, который равен нулю или тому, что компилятор определил как true, всегда имеет установленный lsbit. Компилятору не обязательно устанавливать этот бит для генерации рабочего кода. Возможно, стандарты диктуют конкретное значение true. путем перестановки параметров функции число if(blah) +=... также приведет к трем одиночным тактовым инструкциям и не будет иметь предположений.

РЕДАКТИРОВАТЬ2:

Глядя на то, что я понимаю под стандартом C99:

Операторы == (равно) и != (не равно) аналогичны операторам отношения, за исключением их более низкого приоритета. Каждый из операторов возвращает 1, если указанное отношение истинно, и 0, если оно ложно.

Это объясняет, почему приведенное выше редактирование работает и почему вы получаете movne r0,#1, а не какое-то другое случайное число.

Плакат задавал вопрос относительно C, но также отметил, что ADA является текущим языком, с независимой от языка точки зрения вы не должны предполагать «функции», такие как функция C выше, и использовать if (бла) число = число + (1 ‹‹н). Но это было задано с помощью тега C, поэтому в целом (независимо от процессора) самый быстрый результат для C, я думаю, это number += (blah!=0)‹‹n; Так что комментарий Стивена Райта был правильным, и он должен получить за это признание.

Предположение плаката также в основном правильное, если вы можете получить бла в форме 0 или 1, то использование его в математике будет быстрее в том смысле, что нет ответвления. Хитрость заключается в том, чтобы получить его в этой форме, не будучи дороже, чем ветка.

27.10.2010
  • А как насчет number += ((blah!=0)&1)<<n; ? 27.10.2010
  • результатом blah!=0 является либо 0 для false, либо true, что не является детерминированным. 27.10.2010
  • Читая ответ на аналогичный вопрос SO, стандарт может указывать, что промежуточное сравнение действительно возвращает 1 или 0, если это правда, и компилятор везде соответствует этому стандарту, тогда просто сделайте number += (blah!=0)‹‹n; Я все еще жду выхода хорошего стандарта и компилятора, который действительно соответствует любому стандарту, поэтому я предпочел бы перестраховаться. (бла!=0)‹‹n; должно привести к трем инструкциям на ARM, так что не быстрее, чем if(blah) number += 1‹‹n; для этой архитектуры. для x86 это должно быть немного быстрее. 28.10.2010
  • спасибо, не забудьте поставить Саймону +1 за его вклад. и заплатите вперед (помогите кому-нибудь еще выйти из stackoverflow) 02.11.2010

  • 2

    На такой вопрос нет общего ответа, это во многом зависит от вашего компилятора и процессора. Современные процессоры имеют инструкции условного перемещения, так что все возможно.

    Единственные способы узнать здесь - проверить созданный ассемблер (обычно -S в качестве опции компилятора) и измерить.

    26.10.2010

    3

    В Аде...

    Первоначальная формулировка:

    if Blah then
      Number := Number + (2 ** N);
    end if;
    

    Альтернативная общая формулировка, предполагающая, что Blah имеет тип Boolean, а Number и N — подходящие типы:

    Number := Number + (Boolean'pos(Blah) * (2 ** N));
    

    (Для N и Number определенных пользователем целочисленных типов или типов с плавающей запятой могут потребоваться подходящие определения и преобразования типов, ключевым моментом здесь является конструкция Boolean'pos(), которая, как гарантирует Ада, даст вам 0 или 1 для предопределенного логического типа.)

    Что касается того, быстрее это или нет, я согласен с @Cthutu:

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

    26.10.2010
  • Хорошо с точки зрения поста, я не подумал об этом. Если бы blah было предсказуемым значением, я мог бы понять часть компилятора, которую говорят и вы, и chutu, но, поскольку это дискретное значение, поступающее от части оборудования, я не уверен, как компилятор может оптимизировать это дальше, не могли бы вы (или Кхуту) разум расширяется? 26.10.2010
  • Действительно ли Ада гарантирует 0 и 1 для логического типа? Единственный комментарий по этому поводу в LRM заключается в том, что False ‹ True. Однако я ожидаю, что это произойдет с большинством (всеми?) компиляторами. И, конечно же, параноик может определить свое собственное представление для производного логического типа, который гарантирует 0 и 1 в качестве значений :) 26.10.2010
  • Да, для предопределенного логического значения это гарантировано. Это связано с определением атрибута 'Pos, который возвращает номер позиции перечисления, т. е. Boolean'Pos(False) равен 0, Boolean'Pos(True) равен 1. Даже если базовый представления были 0, и что-то отличное от 0, свойство 'Pos все еще сохранялось бы (чтобы получить фактическое представление, вам нужно было бы использовать экземпляр Unchecked_Conversion, чтобы получить его.) 26.10.2010
  • Если компилятор генерирует логическое значение, он определенно будет иметь соответствующее поведение 'Pos. Однако, если вы сгенерируете логическое значение, используя какую-либо форму непроверенного преобразования (например, импорт из C), оно может быть недействительным, и большинство ставок не работает. Например, с компилятором GCC Ada 42 может казаться ложным в некоторых контекстах (поскольку его младший бит не запятнан), истинным в других или приводить к Constraint_Error. Как всегда, если вы импортируете из чужого контекста, проверьте интерфейс. 26.10.2010
  • & Саймон: Спасибо за вклад. Теперь, перечитав LRM, это ясно. Я перепутал Pos с внутренним представлением. Полезная новая информация. 28.10.2010

  • 4

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

    number += (blah ? 2^n : 0);
    

    Если по какой-то причине этот небольшой расчет является узким местом вашего приложения после профилирования, побеспокойтесь о низкоуровневой оптимизации.

    26.10.2010
  • Когда я делаю обзоры кода для встраиваемых систем, я обычно смотрю на написанный код с точки зрения небольших изменений, может ли он быть немного быстрее, я не планирую масштабное переписывание или недели времени на настройку мелких вещей, но только, надеюсь, очевидные мелочи, но, возможно, об этом позаботится компилятор. Хотя я не думаю, что это может оптимизировать его, поскольку данные в логическом значении в этом случае являются дискретными, поэтому они неизвестны до времени выполнения. 26.10.2010
  • Я бы действительно рекомендовал придерживаться логической проверки, если ваша логика срабатывает, когда условие истинно, а не использовать целое число/число с плавающей запятой и умножать его. Это будет более очевидно для вас, когда вы вернетесь к своему коду через 6 месяцев. 26.10.2010
  • Будьте очень устали от настройки для оптимизации. Вы можете усложнить чтение своего кода и, что еще хуже, сделать его медленнее. Интуиция не всегда ваш лучший друг, когда дело доходит до оптимизации. 26.10.2010
  • основываясь на комментарии, связанном с функцией, которая запускает этот код, я был бы удивлен, если бы не смог легко прочитать код, но я определенно понимаю вашу точку зрения. Я полагаю, что быстрый способ увидеть, быстрее это или медленнее (даже с настройкой компилятора), состоял бы в том, чтобы запустить аналогичную функцию несколько раз, выполнив массу измерений времени, и это должно сказать мне в нашей конкретной системе, является ли это быстрее или медленнее. 26.10.2010
  • Я пытался объяснить, что вам не следует беспокоиться об оптимизации на этом уровне, и я описывал общий подход, а не что-то конкретное для примера кода. Часто профилируйте свой код и используйте его как руководство к тому, на чем следует сосредоточить усилия по оптимизации, если это понадобится вашему приложению. 26.10.2010

  • 5

    В C относительно blah*2^n: есть ли у вас основания полагать, что blah принимает значения 0 и 1? Язык только обещает, что 0 ‹-> FALSE и (все остальное) ‹-> TRUE. C позволяет вам умножать "логическое" временное значение на другое число, но результат не определен, за исключением случаев, когда результат=0 ‹=> логическое значение было ложным или число было равно нулю.

    В Аде относительно blah*2^n: язык не определяет оператор умножения для типа Boolean. Таким образом, blah не может быть логическим значением и не может быть умножено.

    26.10.2010
  • Я разговаривал с коллегой, и он упомянул, что логические значения нельзя умножать на числа. Это имело смысл, но я не был уверен, было ли это ограничение только для ada или большинство языков не позволяли. 26.10.2010
  • Эрик: Этот ответ вводит в заблуждение. Результатом логического оператора/оператора сравнения в C является всегда 0 или 1. Это четко определено стандартом. Вы можете использовать другие ненулевые значения с условными операторами, но это сильно отличается от вашего предположения, что true принимает случайные ненулевые значения. 26.10.2010
  • @R ..: Хм ... Теперь я пытаюсь вспомнить, в какой среде я был удивлен, обнаружив истинную (явно) реализованную как -1. Кажется, я помню каламбур, что и !true==false, и ~true==false. 27.10.2010

  • 6

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

    26.10.2010
  • Интересно, я думал обо всей этой ветке. Я забыл о конвейерной обработке (позор мне, мы довольно много говорили об этом в школе, мне лучше знать) 26.10.2010

  • 7

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

    У разных процессоров разные потребности, и они могут быть безумно сложными. Например, в этом случае то, что быстрее, во многом зависит от настройки конвейера вашего ЦП, того, что находится в кеше в данный момент, и как работает его модуль прогнозирования ветвлений. Часть работы вашего компилятора состоит в том, чтобы быть экспертом в этих вещах, и он справится с этой задачей лучше, чем все, кроме самых лучших программистов на ассемблере. Конечно лучше, чем вы (или я).

    Таким образом, вы просто беспокоитесь о том, чтобы написать хороший код, и пусть компилятор заботится о том, чтобы сделать из него эффективный машинный код.

    26.10.2010

    8

    Для указанной проблемы действительно существуют простые выражения в C, которые могут создавать эффективный код.

    nth степень 2 может быть вычислена с помощью оператора << как 1 << n при условии, что n меньше количества битов значения в int.

    Если blah является логическим значением, а именно int со значением 0 или 1, фрагмент кода можно записать так:

    number += blah << n;
    

    Если blah является любым скалярным типом, значение истинности которого можно проверить как if (blah), выражение будет несколько более сложным:

    number += !!blah << n;
    

    что эквивалентно number += (blah != 0) << n;

    Тест по-прежнему присутствует, но для современных архитектур в сгенерированном коде не будет скачков, что можно проверить онлайн с помощью Обозреватель компиляторов Godbolt.

    01.08.2017
  • Рад, что вы решили дать этот ответ. Я сам почти дал тот же ответ некоторое время назад, но это был старый вопрос. Каким-то образом он продолжает работать, поэтому, вероятно, должен быть оптимальный ответ. 02.08.2017

  • 9

    В любом случае вы не сможете избежать ответвления (внутри), так что даже не пытайтесь!

    In

    number = number + blah*2^n
    

    полное выражение всегда должно быть оценено, если только компилятор не достаточно умен, чтобы остановиться, когда blah равен 0. Если это так, вы получите ветвь, если blah равно 0. Если это не так, вы всегда получите дорогое умножение. В случае, если blah ложно, вы также получите ненужное добавление и присвоение.

    В операторе «if then» оператор будет выполнять сложение и присваивание только в том случае, если blah истинно.

    Короче говоря, ответ на ваш вопрос в данном случае «да».

    26.10.2010
  • Почему все упускают из виду, что это умножение не дорогое, а практически бесплатное? (Подсказка: это степень числа 2.) 26.10.2010
  • Просто потому, что это степень двойки, не делает это быстрее, чем вообще не делать этого. 26.10.2010
  • вы можете избежать ветки, это зависит от архитектуры. вы не можете избежать некоторого условного выполнения, это верно, если только blah не является просто общим логическим значением, но конкретно 1 или 0. 27.10.2010

  • 10

    Этот код показывает, что они работают одинаково, но умножение обычно немного быстрее.

    @Test
    public void manual_time_trial()
    {
        Date beforeIfElse = new Date();
        if_else_test();
        Date afterIfElse = new Date();
        long ifElseDifference = afterIfElse.getTime() - beforeIfElse.getTime();
        System.out.println("If-Else Diff: " + ifElseDifference);
    
        Date beforeMultiplication = new Date();
        multiplication_test();
        Date afterMultiplication = new Date();
        long multiplicationDifference = afterMultiplication.getTime() - beforeMultiplication.getTime();
        System.out.println("Mult Diff   : " + multiplicationDifference);
    
    }
    
    private static long loopFor = 100000000000L;
    private static short x = 200;
    private static short y = 195;
    private static int z;
    
    private static void if_else_test()
    {
        short diff = (short) (y - x);
        for(long i = 0; i < loopFor; i++)
        {
            if (diff < 0)
            {
                z = -diff;
            }
            else
            {
                z = diff;
            }
        }
    }
    
    private static void multiplication_test()
    {
        for(long i = 0; i < loopFor; i++)
        {
            short diff = (short) (y - x);
            z = diff * diff;
        }
    }
    
    01.08.2017
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

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

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..