Встраивать ассемблер для управления 64-битными регистрами в переносимом C ++ - PullRequest
2 голосов
/ 22 октября 2011

У меня есть простой (но критичный к производительности) алгоритм на C (встроенный в C ++) для управления буфером данных ... алгоритм «естественно» использует 64-битные значения регистра с прямым порядком байтов - и я хотел бы оптимизироватьэто использует ассемблер для получения прямого доступа к флагу переноса и BSWAP и, следовательно, избегает необходимости манипулировать 64-битными значениями по одному байту за раз.

Я хочу, чтобы решение было переносимым между ОС / компиляторами -минимально поддерживающая GNU g ++ и Visual C ++ - и между Linux и Windows соответственно.Очевидно, что для обеих платформ я предполагаю процессор, который поддерживает набор команд x86-64.

Я нашел этот документ о встроенном ассемблере для MSVC / Windows и нескольких фрагментахчерез Google с подробным описанием несовместимого синтаксиса для g ++.Я принимаю, что мне может потребоваться реализовать эту функцию отдельно на каждом диалекте.Я не смог найти достаточно подробную документацию по синтаксису / средствам, чтобы заняться этой разработкой.

Мне нужна четкая документация, детализирующая доступные мне средства - как с наборами инструментов MS, так и с GNU.Хотя я написал несколько 32-разрядных ассемблеров много лет назад, я не уверен, что выиграл бы от кратких документов, доступных на уровне ассемблера.

Еще одно осложнение заключается в том, что я хотел быскомпилировать для Windows с использованием Visual C ++ Express Edition 2010 ... Я признаю, что это 32-битный компилятор - но, мне было интересно, возможно ли встраивать 64-битную сборку в ее исполняемые файлы?Меня интересует только 64-битная производительность в разделе, который я планирую написать вручную.

Может кто-нибудь предложить какие-нибудь указатели (прошу прощения за каламбур ...)?

Ответы [ 5 ]

3 голосов
/ 22 октября 2011

К сожалению, MSVC ++ не поддерживает встроенную сборку в 64-битном коде и также не поддерживает __emit. В MSVC ++ вы должны либо реализовать фрагменты кода в отдельных файлах .asm и скомпилировать и связать их с остальным кодом, либо прибегнуть к грязным хакерским атакам, как показано ниже (реализовано для 32-битного кода в качестве подтверждения концепции):

#include <windows.h>
#include <stdio.h>

unsigned char BswapData[] =
{
  0x0F, 0xC9, // bswap ecx
  0x89, 0xC8, // mov   eax, ecx
  0xC3        // ret
};

unsigned long (__fastcall *Bswap)(unsigned long) =
  (unsigned long (__fastcall *)(unsigned long))BswapData;

int main(void)
{
  DWORD dummy;
  VirtualProtect(BswapData, sizeof(BswapData), PAGE_EXECUTE_READWRITE, &dummy);
  printf("0x%lX\n", Bswap(0x10203040));
  return 0;
}

Выход: 0x40302010

Я думаю, что вы должны быть в состоянии сделать то же самое не только с gcc, но и с Linux, с двумя незначительными отличиями (VirtualProtect () - одно, соглашения о вызовах - другое).

РЕДАКТИРОВАТЬ : Вот как можно выполнить BSWAP для 64-битных значений в 64-битном режиме в Windows (не проверено):

unsigned char BswapData64[] =
{
  0x48, 0x0F, 0xC9, // bswap rcx
  0x48, 0x89, 0xC8, // mov   rax, rcx
  0xC3              // ret
};

unsigned long long (*Bswap64)(unsigned long long) =
  (unsigned long long (*)(unsigned long long))BswapData64;

А остальное тривиально.

3 голосов
/ 22 октября 2011

Чтобы дать вам представление о препятствиях на вашем пути, вот простая встроенная функция ассемблера на двух диалектах.Во-первых, версия Borland C ++ Builder (я думаю, что она компилируется и под MSVC ++):

int BNASM_AddScalar (DWORD* result, DWORD x)
  {
  int carry = 0 ;
  __asm
    {
    mov     ebx,result
    xor     eax,eax
    mov     ecx,x
    add     [ebx],ecx
    adc     carry,eax    // Return the carry flag
    }
  return carry ;
  }

Теперь версия g ++:

int BNASM_AddScalar (DWORD* result, DWORD x)
  {
  int carry = 0 ;
  asm volatile (
"    addl    %%ecx,(%%edx)\n"
"    adcl    $0,%%eax\n"    // Return the carry flag
: "+a"(carry)         // Output (and input): carry in eax
: "d"(result), "c"(x) // Input: result in edx and x in ecx
) ;
  return carry ;
  }

Как видите, различия существенны,И нет никакого пути к ним.Они взяты из большой целочисленной арифметической библиотеки, которую я написал для 32-битной среды.

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

1 голос
/ 28 февраля 2013

Существует много функций, доступных для замены порядка байтов, например, из сокетов BSD:

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

64 бита менее переносимы:

unsigned __int64 _byteswap_uint64(unsigned __int64); // Visual C++
int64_t __builtin_bswap64 (int64_t x). // GCC

Не прибегайте к сборке каждый разчто-то не выражается в стандарте C ++.

0 голосов
/ 22 октября 2011

Встроенный ассемблер не является одной из ваших возможностей: компиляторы Win64 Visual C не поддерживают __asm, вам нужно использовать отдельные [m | y | n] asm-скомпилированные файлы.

0 голосов
/ 22 октября 2011

По определению операторы asm в C или C ++ не являются переносимыми, в частности потому, что они связаны с конкретным набором команд. В частности, не ожидайте, что ваш код будет работать на ARM, если ваши операторы ассемблера предназначены для x86.

Кроме того, даже на одной аппаратной платформе, такой как 64-разрядные x86-64 (то есть современные ПК), разные системы (например, Linux против Windows) имеют разный синтаксис ассемблера и разные соглашения о вызовах. Таким образом, у вас должно быть несколько вариантов вашего кода.

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

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

...