2012-02-04 7 views
6

Ponieważ nie wydaje się, że jest to nieodłączne dla ADC i nie mogę używać wbudowanego assemblera dla architektury x64 z Visual C++, co powinienem zrobić, jeśli chcę napisać funkcję używając opcji add z przenosić, ale umieścić go w przestrzeni nazw C++?Visual C++ x64 add with carry

(Emulujący z operatorów porównania nie jest rozwiązaniem. To 256 megabit add to wydajność krytyczna.)

+0

Powiedz nam więcej o tym "Dodawanie 256 megabitów". Jest całkiem prawdopodobne, że wielokrotne dodawanie za pomocą SIMD będzie znacznie szybsze, nawet biorąc pod uwagę, że trzeba wykonać dodatkowy krok. –

+0

Zrobiłem już to badanie. Zobacz http://stackoverflow.com/questions/8866973/can-long-integer-routines-benefit-from-sse. – jnm2

+1

@ jnm2 - Wydaje się, że sposób x64 zapisuje oddzielny kod zespołu i wywołuje go z funkcji C++. Asembler jest już częścią pakietu. –

Odpowiedz

4

There is now an instrinsic dla ADC w MSVC: _addcarry_u64. Poniższy kod

#include <inttypes.h> 
#include <intrin.h> 
#include <stdio.h> 

typedef struct { 
    uint64_t x1; 
    uint64_t x2; 
    uint64_t x3; 
    uint64_t x4; 
} uint256; 

void add256(uint256 *x, uint256 *y) { 
    unsigned char c = 0; 
    c = _addcarry_u64(c, x->x1, y->x1, &x->x1); 
    c = _addcarry_u64(c, x->x2, y->x2, &x->x2); 
    c = _addcarry_u64(c, x->x3, y->x3, &x->x3); 
    _addcarry_u64(c, x->x4, y->x4, &x->x4); 
} 

int main() { 
    //uint64_t x1, x2, x3, x4; 
    //uint64_t y1, y2, y3, y4; 
    uint256 x, y; 
    x.x1 = x.x2 = x.x3 = -1; x.x4 = 0; 
    y.x1 = 2; y.x2 = y.x3 = y.x4 = 0; 

    printf(" %016" PRIx64 "%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "\n", x.x4, x.x3, x.x2, x.x1); 
    printf("+"); 
    printf("%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "\n", y.x4, y.x3, y.x2, y.x1); 
    add256(&x, &y); 
    printf("="); 
    printf("%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "\n", x.x4, x.x3, x.x2, x.x1); 
} 

produkuje następujące montażowej z Visual Studio Express 2013

mov rdx, QWORD PTR x$[rsp] 
mov r8, QWORD PTR x$[rsp+8] 
mov r9, QWORD PTR x$[rsp+16] 
mov rax, QWORD PTR x$[rsp+24] 
add rdx, QWORD PTR y$[rsp] 
adc r8, QWORD PTR y$[rsp+8] 
adc r9, QWORD PTR y$[rsp+16] 
adc rax, QWORD PTR y$[rsp+24] 

który ma jedną add i trzy adc zgodnie z oczekiwaniami.

Edit:

Wydaje się pewne niejasności, co _addcarry_u64 robi. Jeśli spojrzysz na dokumentację Microsoftu dotyczącą tego, z którym łączyłem się na początku tej odpowiedzi, pokazuje ona, że ​​nie wymaga żadnego specjalnego sprzętu. To wytwarza adc i będzie działać na wszystkich procesorach x86-64 (i _addcarry_u32 będzie działać na jeszcze starszych procesorach). Działa dobrze na systemie Ivy Bridge, na którym go przetestowałem.

Jednak _addcarryx_u64 wymaga adx (jak pokazano w dokumentacji MSFT) i rzeczywiście nie działa na moim systemie Ivy Bridge.

+1

Ta odpowiedź wymaga zastrzeżenia, ta instrukcja może być używana tylko w procesorach Core 4. generacji (Haswell i nowsze). Kolejne 5 do 10 lat i numer telefonu wsparcia, zanim będziesz mógł ślepo polegać na tym, że będzie dostępny. –

+0

@HansPassant Nie mogę tego potwierdzić. Czy masz na to referencję? – jnm2

+0

https://software.intel.com/en-us/node/523867 –

7

VS2010 posiada wbudowane wsparcie dla kompilacji i kod napisany w montażu i tłumaczone przez MASM łącząca (ml64.exe) . Trzeba tylko przeskoczyć kilka kółek, aby go włączyć:

  • Kliknij prawym przyciskiem myszy projekt w oknie Solution Explorer, Build Customizations, zaznacz "masm".
  • Projekt + Dodaj nowy element, wybrać szablon C++ plik, ale nazwę go something.asm
  • Upewnij się, że masz cel platformy x64 dla projektu. Build + Configuration Manager, wybierz "x64" w kombinacji "Aktywna platforma rozwiązań". Jeśli go brakuje, wybierz <New> i wybierz x64 z pierwszego zestawu. Jeśli go brakuje, będziesz musiał ponownie uruchomić instalację i dodać obsługę 64-bitowych kompilatorów.

Napisz kod zespołu przy użyciu składni MASM, nr referencyjny is here. Szybki przewodnik samouczka is here.

Szkielet kodu montażowej wygląda następująco:

.CODE 
PUBLIC Foo 
Foo PROC 
    ret     ; TODO: make useful 
Foo ENDP 
END 

i zadzwonił z kodu C++ tak:

extern "C" void Foo(); 

int main(int argc, char* argv[]) 
{ 
    Foo(); 
    return 0; 
} 

Pełne wsparcie debugowania jest dostępny, będziesz zazwyczaj chcą przynajmniej użyj okna Debug + Windows + Registers.

+0

Idealnym rozwiązaniem w tym przypadku będą funkcje inline (montaż inline). Użycie asemblera i łączenie w plikach obiektowych nie spowoduje tego, a 64-bitowy kod w MSVC nie zezwoli na wbudowane złożenie. Oznacza to, że OP musi napisać wiele innych funkcji (z których kompilator prawdopodobnie wykonuje już dobrą robotę) również w zestawie, aby uniknąć wywołań funkcji. –

1

Zaimplementowałem 256-bitową liczbę całkowitą, używając tablicy unsigned long long i używanego zestawu x64 do implementacji polecenia dodaj z przeniesieniem. Oto C++ rozmówcy:

#include "stdafx.h" 

extern "C" void add256(unsigned long long *a, unsigned long long * b, unsigned long long *c); 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    unsigned long long a[4] = {0x8000000000000001, 2, 3, 4}; 
    unsigned long long b[4] = {0x8000000000000005, 6, 7, 8}; 
    unsigned long long c[4] = {0, 0, 0, 0}; 
    add256(a, b, c); // c[] == {6, 9, 10, 12}; 
    return 0; 
} 

add256 jest realizowany w zespole:

; void add256(unsigned long long *a, unsigned long long * b, unsigned long long *c) 

.CODE 
PUBLIC add256 
add256 PROC 

    mov     qword ptr [rsp+18h],r8  
    mov     qword ptr [rsp+10h],rdx  
    mov     qword ptr [rsp+8],rcx  
    push    rdi  

    ; c[0] = a[0] + b[0]; 

    mov     rax,qword ptr 16[rsp] 
    mov     rax,qword ptr [rax]  
    mov     rcx,qword ptr 24[rsp] 
    add     rax,qword ptr [rcx]  
    mov     rcx,qword ptr 32[rsp] 
    mov     qword ptr [rcx],rax  

    ; c[1] = a[1] + b[1] + CARRY; 

    mov     rax,qword ptr 16[rsp] 
    mov     rax,qword ptr [rax+8]  
    mov     rcx,qword ptr 24[rsp] 
    adc     rax,qword ptr [rcx+8]  
    mov     rcx,qword ptr 32[rsp] 
    mov     qword ptr [rcx+8],rax  

    ; c[2] = a[2] + b[2] + CARRY; 

    mov     rax,qword ptr 16[rsp] 
    mov     rax,qword ptr [rax+10h]  
    mov     rcx,qword ptr 24[rsp] 
    adc     rax,qword ptr [rcx+10h]  
    mov     rcx,qword ptr 32[rsp] 
    mov     qword ptr [rcx+10h],rax  

    ; c[3] = a[3] + b[3] + CARRY; 

    mov     rax,qword ptr 16[rsp] 
    mov     rax,qword ptr [rax+18h]  
    mov     rcx,qword ptr 24[rsp] 
    adc     rax,qword ptr [rcx+18h]  
    mov     rcx,qword ptr 32[rsp] 
    mov     qword ptr [rcx+18h],rax  

    ; } 

    pop     rdi  
    ret  

    add256    endp 

    end       

wiem, co wskazuje, że nie chcesz emulowanym dodać roztworem carry i chciał rozwiązanie najlepsze osiągnięcia, ale , jeszcze, można rozważyć następujące C++ tylko rozwiązanie, które ma ładny sposób symulujący 256 bitowych liczb:

#include "stdafx.h" 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    unsigned long long a[4] = {0x8000000000000001, 2, 3, 4}; 
    unsigned long long b[4] = {0x8000000000000005, 6, 7, 8}; 
    unsigned long long c[4] = {0, 0, 0, 0}; 
    c[0] = a[0] + b[0]; // 6 
    c[1] = a[1] + b[1] + (c[0] < a[0]); // 9 
    c[2] = a[2] + b[2] + (c[1] < a[1]); // 10 
    c[3] = a[3] + b[3] + (c[2] < a[2]); // 12 
    return 0; 
} 
+0

Przepraszamy za spóźnienie, ale rozwiązanie C++ nie jest poprawne. Jako uproszczenie rozważmy a = 01 i b = 11 z carry = 1, a następnie c = 01 z carry = 1, ale c knivil