2013-07-10 6 views
8

Szukam kodu C dla podpisanego nasyconego 64-bitowego dodatku, który kompiluje się do wydajnego kodu X86-64 z optymalizatorem gcc. Kod przenośny byłby idealny, chociaż w razie potrzeby można by zastosować rozwiązanie asm.Podpisano nasycony dodatek 64-bitowy ints?

static const int64 kint64max = 0x7fffffffffffffffll; 
static const int64 kint64min = 0x8000000000000000ll; 

int64 signed_saturated_add(int64 x, int64 y) { 
    bool x_is_negative = (x & kint64min) != 0; 
    bool y_is_negative = (y & kint64min) != 0; 
    int64 sum = x+y; 
    bool sum_is_negative = (sum & kint64min) != 0; 
    if (x_is_negative != y_is_negative) return sum; // can't overflow 
    if (x_is_negative && !sum_is_negative) return kint64min; 
    if (!x_is_negative && sum_is_negative) return kint64max; 
    return sum; 
} 

Funkcja w formie pisemnej wytwarza dość długie wyjście zespołu z kilkoma odgałęzieniami. Wszelkie wskazówki dotyczące optymalizacji? Wygląda na to, że powinno być możliwe do wdrożenia za pomocą tylko ADD z kilkoma instrukcjami CMOV, ale jestem trochę zardzewiały z tego typu rzeczy.

+2

Twój sposób obliczania znaku twoich wartości jest zbyt skomplikowany, dlaczego nie użyć po prostu '(x <0)' np.? Aby być przenośnym użyj '[u] int64_t'. Wtedy masz 'INT64_MAX' i' INT64_MIN' za darmo i nie musisz używać do tego własnych stałych. –

+0

możliwy duplikat [Bitwise nasycony dodatek w C (HW)] (http://stackoverflow.com/questions/5277623/bitwise-saturated-addition-in-c-hw) – jxh

+0

gcc może zoptymalizować operacje na 128-bitowych liczbach. Spróbuj czegoś, co działa jak 'clamp ((int128_t) x + y, INT64_MIN, INT64_MAX))' i sprawdź, czy jest to dopuszczalne. – zch

Odpowiedz

1

ciągle szukam godnej przenośnego rozwiązania, ale to jest tak dobre, jak wpadliśmy do tej pory:

propozycje zmian?

int64 saturated_add(int64 x, int64 y) { 
#if __GNUC__ && __X86_64__ 
    asm("add %1, %0\n\t" 
     "jno 1f\n\t" 
     "cmovge %3, %0\n\t" 
     "cmovl %2, %0\n" 
     "1:" : "+r"(x) : "r"(y), "r"(kint64min), "r"(kint64max)); 
    return x; 
#else 
    return portable_saturated_add(x, y); 
#endif 
} 
+1

Zobacz moją odpowiedź na rozwiązanie, które generuje tylko jeden ruch warunkowy. Czy to jest lepsze, czy nie, trzeba by porównać. –

+0

Zastanawiam się, czy możesz zrobić coś takiego jak 'asm (" dodaj% [y],% [x] \ n \ t "" jno 1f \ n \ t "" xor %% rax, %% rax \ n \ t " "mov% [MAX],% [x] \ n \ t" "ustaw %% al \ n \ t" "dodaj %% rax,% [x] \ n \ t" "1:": [x] " + r "(x): [y]" r "(y), [MAX]" i "(INT64_MAX):" eax "," cc ");'. Na pierwszy rzut oka, to może wyglądać dłużej niż twój kod, ale pamiętaj, że twój kod musi wczytać wartości do% 2 i% 3 przed wywołaniem twojego asm, nawet jeśli nie zamierza ich użyć. Mój tylko ładunek na przepełnienie (przypuszczalnie mniej popularny przypadek). NB: Jest późno, a ja tego nie uruchomiłem. I jak mówi @JensGustedt, benchmark. –

9

Można to zoptymalizować, ale tutaj jest to przenośne rozwiązanie. Nie wywołuje niezdefiniowanego zachowania i sprawdza przed przepełnieniem, czy mogło wystąpić.

#include <stdint.h> 

int64_t sadd64(int64_t a, int64_t b) 
{ 
    if (a > 0) { 
     if (b > INT64_MAX - a) { 
      return INT64_MAX; 
     } 
    } else if (b < INT64_MIN - a) { 
      return INT64_MIN; 
    } 

    return a + b; 
} 
+2

Bardzo ładne rozwiązanie. – jxh

+1

Zgadzam się, że jest to przenośne, eleganckie i w 100% poprawne. Jedna potencjalna optymalizacja: zamiast 'return INT64_MAX', spróbuj' b = INT64_MAX - a'. Zamiast 'return INT64_MIN', spróbuj' b = INT64_MIN - a'. W moim kompilatorze (GCC 4.7.3) generuje to nieco ostrzejszy kod, zastępując dwie gałęzie warunkowe za pomocą ruchów warunkowych. (Z drugiej strony wprowadza więcej zależności danych, więc może być wolniejszy ...) – Nemo

+0

Zgadzam się, że jest to poprawne, "proste" rozwiązanie. @Nemo, istnieje możliwość, która powoduje tylko jeden ruch warunkowy, zobacz moją odpowiedź poniżej. Które z tych rozwiązań są bardziej wydajne, niż może wykazać analiza porównawcza. –

3

Jest to rozwiązanie, które trwa nadal w temacie podanym w jednym z komentarzy i zostało również zastosowane w rozwiązaniu ouah. tutaj wygenerowany kod powinien być bez skoków warunkowych

int64_t signed_saturated_add(int64_t x, int64_t y) { 
    // determine the lower or upper bound of the result 
    int64_t ret = (x < 0) ? INT64_MIN : INT64_MAX; 
    // this is always well defined: 
    // if x < 0 this adds a positive value to INT64_MIN 
    // if x > 0 this subtracts a positive value from INT64_MAX 
    int64_t comp = ret - x; 
    // the condition is equivalent to 
    // ((x < 0) && (y > comp)) || ((x >=0) && (y <= comp)) 
    if ((x < 0) == (y > comp)) ret = x + y; 
    return ret; 
} 

Pierwsze wygląda tak, jakby nie byłoby warunkowe przejście do zrobienia, ale ze względu na szczególne walory mój kompilator wysiada z dodatkiem: w 2 uzupełnień INT64_MIN jest INT64_MAX+1 . Jest wtedy tylko jeden ruch warunkowy dla przypisania sumy, na wypadek, gdyby cokolwiek było w porządku.

Wszystko to nie ma UB, ponieważ w abstrakcyjnej maszynie stanów suma jest wykonywana tylko wtedy, gdy nie ma przepełnienia.

+1

Dość (+1). Może użyć kilku komentarzy :-) – Nemo

+1

@Noo, tak, trochę zwięzłe, było za późno zeszłej nocy. Dodałem teraz komentarze wyjaśniające. –