Битовые операторы, а не против использования xor в ветвлении

Спросив этот ТАК вопрос, Я получил очень интересный комментарий от @ AndonM.Coleman, который мне пришлось проверить.

Поскольку ваш дизассемблированный код написан для x86, стоит указать, что XOR установит / сбросит нулевой флаг, а NOT — нет (иногда полезно, если вы хотите выполнить побитовую операцию, не затрагивая условия перехода, которые зависят от флагов от предыдущих операций) , Теперь, учитывая, что вы не пишете ассемблер напрямую, у вас действительно нет значимого доступа к этому флагу, поэтому я сомневаюсь, что это причина для предпочтения одного над другим.

Его комментарий меня заинтересовал, если следующий код будет производить те же инструкции по сборке

#include <iostream>

int main()
{
unsigned int val = 0;

std::cout << "Enter a numeric value: ";
std::cin >> val;

if ( (val ^ ~0U) == 0)
{
std::cout << "Value inverted is zero" << std::endl;
} else
{
std::cout << "Value inverted is not zero" << std::endl;
}

if ( (~val) == 0)
{
std::cout << "Value inverted is zero" << std::endl;
} else
{
std::cout << "Value inverted is not zero" << std::endl;
}

return 0;
}

Для следующих двух операций

if ( (val ^ ~0U) == 0 )

а также

if ( (~val) == 0 )

не оптимизирован Сборка в Visual Studio 2010 дает следующую разборку:

    if ( (val ^ ~0U) == 0)
00AD1501  mov         eax,dword ptr [val]
00AD1504  xor         eax,0FFFFFFFFh
00AD1507  jne         main+86h (0AD1536h)if ( (~val) == 0)
00AD1561  mov         eax,dword ptr [val]
00AD1564  not         eax
00AD1566  test        eax,eax
00AD1568  jne         main+0E7h (0AD1597h)

Мой вопрос касается оптимизации. Лучше написать

if ( (val ^ ~0U) == 0)

или же

if ( (~val) == 0)

0

Решение

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

Если компилятор настроен на оптимизацию по размеру (наименьший байт-код), то иногда он будет использовать XOR в, казалось бы, странных местах. Например, схема кодирования переменной длины, которую использует X86, может установить регистр в 0 от XORв меньшем количестве байт кода, чем требуется при использовании MOV инструкция.

Рассмотрим код, который использует XOR:

if ( (val ^ ~0U) == 0 )  /* 3-bytes to negate and test (x86) */

    XOR eax,0FFFFFFFFh требуется 3 байта А ТАКЖЕ устанавливает / очищает нулевой флаг (ZF)

Теперь рассмотрим код, который использует NOT:

if ( (~val) == 0)        /* 4-bytes to negate and test (x86) */

    NOT eax закодирован в 2-байтовую инструкцию, но не влияет на флаги процессора.

    TEST eax,eax добавляет дополнительные 2 байта, и необходимо установить / очистить Zero Flag (ZF)

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


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

Если позже вы обнаружите, что эта ветвь действительно является узким местом, и нет более высокоуровневого пути решения проблемы, тогда вы могли бы выполнить некоторую низкоуровневую настройку. Тем не менее, это такая тривиальная вещь, на которой стоит сосредоточиться в наши дни, если только вы не нацелены на что-то вроде встроенного процессора с низким энергопотреблением или устройства с ограниченной памятью. Единственные места, где я когда-либо выживал достаточную производительность путем ручной настройки, чтобы сделать ее достойной, были в алгоритмах, которые выиграли от параллелизма данных, и где компилятор не был достаточно умен, чтобы эффективно использовать специализированные наборы команд, такие как MMX / SSE.

3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]