Как использовать встроенные функции MSVC, чтобы получить эквивалент этого кода GCC?

Следующий код вызывает встроенные функции для clz/ctz в GCC, а в других системах имеет версии C. Очевидно, что версии C немного неоптимальны, если в системе есть встроенная инструкция clz/ctz, как в x86 и ARM.

#ifdef __GNUC__
#define clz(x) __builtin_clz(x)
#define ctz(x) __builtin_ctz(x)
#else
static uint32_t ALWAYS_INLINE popcnt( uint32_t x )
{
    x -= ((x >> 1) & 0x55555555);
    x = (((x >> 2) & 0x33333333) + (x & 0x33333333));
    x = (((x >> 4) + x) & 0x0f0f0f0f);
    x += (x >> 8);
    x += (x >> 16);
    return x & 0x0000003f;
}
static uint32_t ALWAYS_INLINE clz( uint32_t x )
{
    x |= (x >> 1);
    x |= (x >> 2);
    x |= (x >> 4);
    x |= (x >> 8);
    x |= (x >> 16);
    return 32 - popcnt(x);
}
static uint32_t ALWAYS_INLINE ctz( uint32_t x )
{
    return popcnt((x & -x) - 1);
}

#endif

Какие функции мне нужно вызвать, какие заголовки мне нужно включить и т. д., чтобы добавить здесь правильный ifdef для MSVC? Я уже просматривал эту страницу, но не совсем уверен, для чего нужна #pragma (требуется ли она?) и какие ограничения она накладывает на требования к версии MSVC для компиляции. Как человек, который на самом деле не использует MSVC, я также не знаю, есть ли у этих встроенных функций эквиваленты C для других архитектур, или мне также нужно #ifdef x86/x86_64 при их #определении.


person Dark Shikari    schedule 10.12.2008    source источник
comment
Страница, на которую вы ссылаетесь выше, относится к функции, которая является частью среды выполнения .NET. Вы пытаетесь создать свою программу для .NET или как собственный исполняемый файл Windows?   -  person Timo Geusch    schedule 10.12.2008
comment
Это родной исполняемый файл Windows — отчасти причина, по которой я спрашиваю, заключается в том, что мне было довольно трудно найти страницы документации Microsoft, которые действительно говорят о C в наши дни.   -  person Dark Shikari    schedule 10.12.2008
comment
Реализация Libcxx github.com/ llvm-зеркало/libcxx/blob/   -  person KindDragon    schedule 03.04.2017


Ответы (6)


Отталкиваясь от кода sh0dan, реализация должна быть исправлена ​​следующим образом:

#ifdef _MSC_VER
#include <intrin.h>

uint32_t __inline ctz( uint32_t value )
{
    DWORD trailing_zero = 0;

    if ( _BitScanForward( &trailing_zero, value ) )
    {
        return trailing_zero;
    }
    else
    {
        // This is undefined, I better choose 32 than 0
        return 32;
    }
}

uint32_t __inline clz( uint32_t value )
{
    DWORD leading_zero = 0;

    if ( _BitScanReverse( &leading_zero, value ) )
    {
       return 31 - leading_zero;
    }
    else
    {
         // Same remarks as above
         return 32;
    }
}
#endif

Как указано в коде, и ctz, и clz не определены, если значение равно 0. В нашей абстракции мы зафиксировали __builtin_clz(value) как (value?__builtin_clz(value):32), но это выбор.

person crazyjul    schedule 09.12.2013
comment
Почти 1-к-1 заменой __builtin_clz() в MSVC является __lzcnt(). Аппаратное обеспечение должно поддерживать SSE4. Подробнее. - person rustyx; 27.01.2016
comment
Мое оборудование поддерживает SSE4, но не BMI1, поэтому __lzcnt() компилируется, но не делает того, что я ожидал, а работает как BSR. - person GregC; 14.03.2017
comment
31 ^__builtin_clz равно _BitScanReverse - person KindDragon; 16.02.2018
comment
Обратите внимание, что GNU C __builtin_ctz и clz также имеют неопределенное поведение, когда входное значение равно 0 (gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html); это позволяет им встраиваться как одна инструкция bsf (или 31 ^ bsr, которая работает для диапазона определенных выходных данных). Если вам нужно обрабатывать, возможно, нулевые входные данные, вам понадобятся аналогичные оболочки для встроенных функций GNU C, поэтому уместно было бы иметь уровень переносимости вокруг BSF / 31 ^ BSR, а затем нулевую обработку поверх этого. .. И используя lzcnt #ifdef __BMI1__. - person Peter Cordes; 17.10.2020
comment
Связано: VS: неожиданное поведение оптимизации с встроенным _BitScanReverse64 - MSVC не раскрывает немодифицированное поведение инструкции asm, даже хотя у него есть API, который может сделать это. (Поэтому вам не нужно инициализировать выходной аргумент index, хотя это не повредит, компилятор знает, что это операнд только для вывода встроенной функции.) - person Peter Cordes; 17.10.2020

Если у MSVC есть встроенный компилятор для этого, он будет здесь:

Внутренние компоненты компилятора в MSDN

В противном случае вам придется написать его, используя __asm

person Ana Betts    schedule 10.12.2008

Я нашел его на корейском веб-сайте https://torbjorn.tistory.com/317 В компиляторе msvc, вы можете использовать __lzcnt(unsigned int) для замены __builtin_clz(unsigned int) в компиляторе gcc.

person Jebearssica    schedule 17.10.2020
comment
Обратите внимание, что для инструкции lzcnt требуется BMI1. На старых процессорах он выполняется как bsr, давая 31-lzcnt (и оставляя регистр назначения без изменений для ввода = 0). GCC расширит __builtin_clz до lznct только в том случае, если вы скомпилируете с -march=haswell или подобными параметрами. - person Peter Cordes; 17.10.2020

  1. Эквивалентной функцией для int __builtin_ctz (unsigned int x) в MSVC является unsigned int _tzcnt_u32 (unsigned int a) для 32-битного целого числа, которая возвращает количество замыкающих нулей. Для 64-разрядных используйте unsigned __int64 _tzcnt_u64 (unsigned __int64 a) 1.

  2. Эквивалентной функцией для int __builtin_clz (unsigned int x) в MSVC является unsigned int _lzcnt_u32 (unsigned int a) для 32-битного целого числа, которая возвращает количество ведущих нулей. Для 64-разрядных используйте unsigned __int64 _lzcnt_u64 (unsigned __int64 a) 2

Заголовок С++: immintrin.h

person Cipher    schedule 03.05.2021
comment
Не все компьютеры имеют ИМТ1, поэтому lzcnt может расшифровываться как bsr и давать 31-clz вместо ожидаемого clz. Существует встроенная функция MSVC для bsr, в частности _BitScanReverse. Они эквивалентны только в том случае, если вы бы/могли бы скомпилировать gcc -mbmi (или, конечно, gcc -march=haswell или что-то, что включает BMI1). См. раздел Подразумевает ли поддержка x64 поддержку BMI1? для проблем с совместимостью старого оборудования (и младших процессоров Pentium/Celeron текущего поколения, спасибо Intel) с этими встроенными функциями. Это полезный ответ, но только с редактированием, чтобы упомянуть об этом. - person Peter Cordes; 03.05.2021
comment
Все процессоры могут не поддерживать набор инструкций BMI1. Однако на основе обнаружения доступности инструкций BMI1 можно использовать lzcnt. В противном случае можно использовать bsr. - person Cipher; 03.05.2021
comment
Да, точно. Вы должны проверить свой процессор и вручную использовать clz = 31-bsr(x);, чтобы точно эмулировать __builtin_clz на процессорах без BMI1. Но ваш ответ не говорит об этом. Это ошибочно подразумевает, что _lzcnt_u32 даст вам результаты, идентичные __builtin_clz в целом. Но в отличие от GCC, MSVC позволяет вам использовать встроенные функции без необходимости компилировать с каким-либо эквивалентом -march=haswell, чтобы гарантировать, что вы будете запускать двоичный файл только на процессорах, которые поддерживают некоторые расширения набора инструкций. - person Peter Cordes; 04.05.2021
comment
(Кстати, для точной эмуляции _lzcnt_u32 без BMI1 вам понадобится lzcnt = x==0 ? 32 : 31-bsr(x);. __builtin_clz имеет неопределенное поведение при запуске с вводом 0, что позволяет компилировать его только в bsr(x) ^ 31, но lzcnt имеет четко определенное поведение для ввода == 0 .) - person Peter Cordes; 04.05.2021
comment
Я не понял, что вручную использовать clz = 31-bsr(x) и почему вы хотите получить начальный/конечный нулевой счет для ввода == 0? - person Cipher; 04.05.2021
comment
BSR не обрабатывает input==0, но _lzcnt_u32 четко определено для возврата 32 в тот случай. Если вы хотите эмулировать lzcnt с точки зрения BSR, вам нужно ввести особый случай == 0. Что касается вручную, я имею в виду, что если вы не можете предположить, что ваш код всегда будет работать на машине с lzcnt, вы должны использовать _BitScanReverse(&tmp, x); return 31 - tmp; чтобы получить тот же результат (для всех ненулевых x). felixcloutier.com/x86/lzcnt объясняет, чем он отличается от BSR. - person Peter Cordes; 04.05.2021
comment
Хорошо ... Итак, вы имеете в виду, что нет способа определить, поддерживается ли BMI1 или нет, и нам нужно всегда предполагать, прежде чем использовать lzcnt, это проблема? Согласно документации GCC, результат __builtin_clz/ctz также не определен для ввода == 0. Итак, вы хотели использовать встроенные функции clz/ctz для подсчета общего количества битов, предоставляя 0, это правильно? - person Cipher; 04.05.2021
comment
Ну, вы можете написать 2 версии своей функции, которые используют clz, и выполнять диспетчеризацию во время выполнения (например, с помощью указателя функции, который вы установили один раз на основе cpuid). Но, как и в большинстве остальных BMI1/BMI2, обычно не стоит на самом деле отправлять, просто используйте одну версию, которая действительно работает везде, используя BSR. Если input==0 возможен для вашего варианта использования, то да, вам нужен какой-то обходной путь. Как bsr(x|1), так что всегда есть что найти, вместо того, чтобы на самом деле разветвляться. Итак, если x был равен 0 или 1, вы получаете bsr(x|1) == 0. - person Peter Cordes; 04.05.2021
comment
Поехали... спасибо! - person Cipher; 04.05.2021

Протестировано на Linux и Windows (x86):

#ifdef WIN32
    #include <intrin.h>
    static uint32_t __inline __builtin_clz(uint32_t x) {
        unsigned long r = 0;
        _BitScanReverse(&r, x);
        return (31-r);
    }
#endif

uint32_t clz64(const uint64_t x)
{
    uint32_t u32 = (x >> 32);
    uint32_t result = u32 ? __builtin_clz(u32) : 32;
    if (result == 32) {
        u32 = x & 0xFFFFFFFFUL;
        result += (u32 ? __builtin_clz(u32) : 32);
    }
    return result;
}
person Tanguy    schedule 13.09.2014
comment
Вы тестировали производительность вашего clz64? Я не удивлюсь, что все это разветвление делает его медленнее, чем реализация OP. - person plamenko; 24.09.2016
comment
Используйте __builtin_clzll как обычный человек, если вам нужна поддержка 64-битных целых чисел в GNU C. Написание таким образом, вероятно, остановит GCC от использования одного 64-битного bsr или lzcnt в 64-битных сборках. (Но тогда у вас также будет доступна встроенная 64-разрядная версия MSVC.) - person Peter Cordes; 17.10.2020

Есть две встроенные функции "_BitScanForward" и "_BitScanReverse", которые подходят для той же цели для MSVC. Включают . Функции:

#ifdef _MSC_VER
#include <intrin.h>

static uint32_t __inline ctz( uint32_t x )
{
   int r = 0;
   _BitScanReverse(&r, x);
   return r;
}

static uint32_t __inline clz( uint32_t x )
{
   int r = 0;
   _BitScanForward(&r, x);
   return r;
}
#endif

Существуют эквивалентные 64-битные версии "_BitScanForward64" и "_BitScanReverse64".

Подробнее здесь:

Внутренние компоненты x86 в MSDN

person sh0dan    schedule 29.03.2011
comment
ctz и clz вызывают неправильные функции (они должны использовать _BitScanForward и BitScanReverse соответственно, а не BitScanReverse/BitScanForward), а clz неверен, поскольку возвращает смещение установленного бита вместо количества начальных нулей. - person Vitali; 16.12.2011