Возможна ли "пользовательская встроенная" функция для x64 вместо встроенной сборки? - PullRequest
6 голосов
/ 04 апреля 2011

В настоящее время я экспериментирую с созданием высокооптимизированных, многократно используемых функций для моей библиотеки. Например, я пишу функцию «является степенью 2» следующим образом:

template<class IntType>  
inline bool is_power_of_two( const IntType x )
{
    return (x != 0) && ((x & (x - 1)) == 0);
}

Это переносимая, не требующая обслуживания реализация в виде встроенного шаблона C ++. Этот код компилируется VC ++ 2008 в следующий код с ветвями:

is_power_of_two PROC
    test    rcx, rcx
    je  SHORT $LN3@is_power_o
    lea rax, QWORD PTR [rcx-1]
    test    rax, rcx
    jne SHORT $LN3@is_power_o
    mov al, 1
    ret 0
$LN3@is_power_o:
    xor al, al
    ret 0
is_power_of_two ENDP

Я также нашел реализацию здесь: "Битовый тиддлер" , который будет закодирован в сборке для x64 следующим образом:

is_power_of_two_fast PROC
    test rcx, rcx
    je  SHORT NotAPowerOfTwo
    lea rax, [rcx-1]
    and rax, rcx
    neg rax
    sbb rax, rax
    inc rax
    ret
NotAPowerOfTwo:
    xor rax, rax
    ret
is_power_of_two_fast ENDP

Я протестировал обе подпрограммы, написанные отдельно от C ++ в модуле сборки (файл .asm), а вторая работает примерно на 20% быстрее!

И все же издержки вызова функции значительны: если я сравним реализацию второй сборки "is_power_of_two_fast" с inline'd-версией функции шаблона, последняя быстрее, несмотря на ветви!

К сожалению, новые соглашения для x64 указывают, что встроенная сборка недопустима. Вместо этого следует использовать «внутренние функции».

Теперь вопрос: могу ли я реализовать более быструю версию "is_power_of_two_fast" как собственную встроенную функцию или нечто подобное, чтобы ее можно было использовать inline? Или, в качестве альтернативы, можно ли каким-то образом заставить компилятор создать версию функции с низкой ветвью?

Ответы [ 4 ]

2 голосов
/ 17 апреля 2012

Даже VC 2005 способен генерировать код с инструкцией sbb.

для кода C

bool __declspec(noinline) IsPowOf2(unsigned int a)
{
    return (a>=1)&((a&(a-1))<1);
}

компилируется в следующее

00401000  lea         eax,[ecx-1] 
00401003  and         eax,ecx 
00401005  cmp         eax,1 
00401008  sbb         eax,eax 
0040100A  neg         eax  
0040100C  cmp         ecx,1 
0040100F  sbb         ecx,ecx 
00401011  add         ecx,1 
00401014  and         eax,ecx 
00401016  ret          
2 голосов
/ 04 апреля 2011

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

Одна из причин удаления встроенной сборки для x86-64 заключается в том, чтовставка ассемблера в середину функции нарушает работу оптимизатора и часто приводит к менее оптимизированному коду вокруг кода ассемблера.Там легко может быть чистый убыток!

Единственное реальное использование встроенных функций - это "интересные" специальные инструкции, которые компилятор не может генерировать из конструкций C или C ++, таких как BSF или BSR.Почти все остальное будет работать лучше с использованием встроенных функций, как ваш шаблон выше.

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

Доверяйте своему компилятору (tm)!

1 голос
/ 19 октября 2011

Встроенные функции VC10 x64 не очень помогут в этом простом случае. Динамическое ветвление у вас есть благодаря оператору &&, который является оператором раннего завершения. Во многих случаях (ваш случай - прекрасный пример) лучше избегать ветвления, вычисляя результат для всех ветвей, а затем применять маску для выбора хорошей. Код cpp с маскированием будет выглядеть так:

template<typename T_Type>
inline bool isPowerOfTwo(T_Type const& x)
{
    // static type checking for the example
    static_assert( std::is_integral<T_Type>::value && std::is_unsigned<T_Type>::value, "limited to unsigned types for the example" );
    typedef std::make_signed<T_Type>::type s_Type;

    // same as yours but with no branching
    return bool(  ((s_Type( s_Type(x != 0) << (s_Type(sizeof(T_Type)<<3u)-1) )) >> (s_Type(s_Type(sizeof(T_Type)<<3u)-1)))  & ((x & (x - 1)) == 0)  );
}

В приведенном выше коде я не проверяю, является ли число отрицательным или нет для подписанных типов. Снова простая маска сделает свое дело, выполнив арифметический сдвиг вправо (numBit-1) раз, чтобы получить значение (~ 0) для отрицательных чисел и 0 для положительных чисел

0 голосов
/ 04 апреля 2011

Единственный путь вперед - немного отступить назад и начать смотреть на большую картинку.Либо прекратите реализацию микрооптимизированного API, либо переходите к выполнению более крупных вызовов API, оптимизированных для MASM64, YASM, NASM и т. Д.

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

...