64 bit tam sayıdaki paketlenmiş 8 bit tam sayıları paralel olarak 1, donanım SIMD'siz SWAR çıkarma


77

Ben 8 elementli paketlenmiş 8 bitlik tamsayılar dizisi olarak yorumladığım 64 bitlik bir tamsayı varsa. Ben 1başka bir elemanın sonucunu etkileyen bir eleman sonucu olmadan taşma işlerken her paketli tamsayı sabit çıkarmak gerekir .

Şu anda bu kodu var ve çalışıyor ama paralel olarak her paketlenmiş 8-bit tamsayı çıkarma yapar ve bellek erişimleri yapmaz bir çözüm gerekir. X86'da SIMD komutlarını psubb, paketlenmiş 8 bitlik tam sayıları paralel olarak çıkarabilirim, ancak kodladığım platform SIMD talimatlarını desteklemiyor. (Bu durumda RISC-V).

Bu yüzden bir bayt arasında taşıma yayılımını el ile iptal etmek için SWAR (bir kayıt içinde SIMD) yapmaya çalışıyorum uint64_t, buna eşdeğer bir şey yapıyor:

uint64_t sub(uint64_t arg) {
    uint8_t* packed = (uint8_t*) &arg;

    for (size_t i = 0; i < sizeof(uint64_t); ++i) {
        packed[i] -= 1;
    }

    return arg;
}

Bunu bitsel operatörlerle yapabileceğinizi düşünüyorum ama emin değilim. SIMD talimatlarını kullanmayan bir çözüm arıyorum. C veya C ++ 'da oldukça taşınabilir bir çözüm veya sadece arkasındaki teoriyi arıyorum, böylece kendi çözümümü uygulayabilirim.


5
8 bit mi olmalılar yoksa 7 bit mi olabilirler?
tadman

8-bit üzgün olmalılar :(
cam-beyaz

12
Bu tür tekniklere SWAR
harold


1
sıfır içeren bir baytın 0xff'ye sarılmasını mı bekliyorsunuz?
Alnitak

Yanıtlar:


75

Verimli SIMD talimatları olan bir CPU'nuz varsa, SSE / MMX paddb( _mm_add_epi8) de uygulanabilir. Peter Cordes'in cevabı ayrıca GNU C (gcc / clang) vektör sözdizimini ve katı örtüşen UB için güvenliği açıklar. Bu cevabı da incelemeyi şiddetle tavsiye ediyorum.

Bunu kendiniz yapmak uint64_ttamamen taşınabilirdir, ancak bir uint8_tdiziye erişirken bir hizalama sorunlarından ve katı takma UB'den kaçınmaya özen gösterir uint64_t*. Verilerinizle uint64_tzaten başlayarak bu bölümü soru dışında bıraktınız , ancak GNU C için bir may_aliastypedef sorunu çözüyor (bunun için Peter'ın cevabına bakın veya memcpy).

Aksi takdirde, verilerinizi ayrı ayrı baytlar olarak atayabilir / bildirebilir ve bunlara uint64_terişebilirsiniz uint8_t*. unsigned char*8 bitlik elemanlar için problemi ortadan kaldıracak şekilde herhangi bir şeyi takma adlara izin verilir. (Varsa uint8_t, muhtemelen bir olduğunu varsaymak güvenlidir unsigned char.)


Bunun önceki bir yanlış algoritmadan bir değişiklik olduğunu unutmayın (düzeltme geçmişine bakın).

Bu, keyfi çıkarma için döngü olmadan mümkündür ve 1her bayttaki gibi bilinen bir sabit için daha verimli hale gelir . Ana hile, yüksek biti ayarlayarak her bayttan taşınmayı önlemek, ardından çıkarma sonucunu düzeltmektir.

Burada verilen çıkarma tekniğini biraz optimize edeceğiz . Bunlar şunları tanımlar:

SWAR sub z = x - y
    z = ((x | H) - (y &~H)) ^ ((x ^~y) & H)

ile Htanımlanır 0x8080808080808080U(yani her paketlenmiş tam sayının MSB'leri). Bir azalma için, yöyle 0x0101010101010101U.

yTüm MSB'lerinin açık olduğunu biliyoruz , bu yüzden maske adımlarından birini atlayabiliriz (yani bizim durumumuzla y & ~Haynıdır y). Hesaplama aşağıdaki gibi devam eder:

  1. Her bir bileşenin MSB'lerini 1 olarak ayarladık x, böylece bir ödünç MSB'nin ötesine geçerek bir sonraki bileşene geçemez. Buna ayarlanmış giriş deyin.
  2. 0x01010101010101Düzeltilmiş girdiden çıkararak her bileşenden 1 çıkarırız . Bu adım 1 sayesinde bileşenler arası ödünçlere neden olmaz.
  3. Şimdi sonucun MSB'sini düzeltmemiz gerekiyor. Sonucu düzeltmeyi tamamlamak için ayarlanan çıkışı orijinal girişin ters MSB'leriyle xor veya xor.

İşlem şu şekilde yazılabilir:

#define U64MASK 0x0101010101010101U
#define MSBON 0x8080808080808080U
uint64_t decEach(uint64_t i){
      return ((i | MSBON) - U64MASK) ^ ((i ^ MSBON) & MSBON);
}

Tercihen bu, derleyici tarafından satır içine alınır ( bunu zorlamak için derleyici yönergelerini kullanın) veya ifade başka bir işlevin parçası olarak satır içine yazılır.

testcases:

in:  0000000000000000
out: ffffffffffffffff

in:  f200000015000013
out: f1ffffff14ffff12

in:  0000000000000100
out: ffffffffffff00ff

in:  808080807f7f7f7f
out: 7f7f7f7f7e7e7e7e

in:  0101010101010101
out: 0000000000000000

Performans ayrıntıları

İşte işlevin tek bir çağrılması için x86_64 montajı. Daha iyi performans için sabitlerin olabildiğince uzun bir kayıtta yaşayabileceği ümidiyle vurgulanmalıdır. Sabitlerin bir kayıtta yaşadığı sıkı bir döngüde, asıl azalma beş talimat alır: veya optimizasyondan sonra + değil + ve + + + veya + ekleyin. Derleyicinin optimizasyonunu yenecek alternatifler görmüyorum.

uint64t[rax] decEach(rcx):
    movabs  rcx, -9187201950435737472
    mov     rdx, rdi
    or      rdx, rcx
    movabs  rax, -72340172838076673
    add     rax, rdx
    and     rdi, rcx
    xor     rdi, rcx
    xor     rax, rdi
    ret

Aşağıdaki snippet'in bazı IACA testleriyle:

// Repeat the SWAR dec in a loop as a microbenchmark
uint64_t perftest(uint64_t dummyArg){
    uint64_t dummyCounter = 0;
    uint64_t i = 0x74656a6d27080100U; // another dummy value.
    while(i ^ dummyArg) {
        IACA_START
        uint64_t naive = i - U64MASK;
        i = naive + ((i ^ naive ^ U64MASK) & U64MASK);
        dummyCounter++;
    }
    IACA_END
    return dummyCounter;
}


Skylake makinesinde, eksiltme, xor ve karşılaştır + atlama işleminin yineleme başına 5 döngüden biraz azında gerçekleştirilebileceğini gösterebiliriz:

Throughput Analysis Report
--------------------------
Block Throughput: 4.96 Cycles       Throughput Bottleneck: Backend
Loop Count:  26
Port Binding In Cycles Per Iteration:
--------------------------------------------------------------------------------------------------
|  Port  |   0   -  DV   |   1   |   2   -  D    |   3   -  D    |   4   |   5   |   6   |   7   |
--------------------------------------------------------------------------------------------------
| Cycles |  1.5     0.0  |  1.5  |  0.0     0.0  |  0.0     0.0  |  0.0  |  1.5  |  1.5  |  0.0  |
--------------------------------------------------------------------------------------------------

(Tabii ki, x86-64'te sadece movqbir XMM reg'ini yüklersiniz veya RMSC paddb-V gibi bir ISA için nasıl derlendiğine bakmak daha ilginç olabilir.)


4
Kodum SIMX talimatları (henüz) MMX için destek yalnız bırakmasın RISC-V makinelerde çalıştırmak için ihtiyacım var
cam-beyaz

2
@ cam-white Anladım - bu muhtemelen o zaman yapabileceğiniz en iyisidir. Ben de RISC için montaj akıl sağlığı için godbolt atlayacağım. Düzenleme: Godbolt'ta RISC-V desteği yok :(
nanofarad

7
Orada Godbolt üzerinde RISC V desteği gibi örneğin aslında bu (E: derleyici maskesi oluştururken aşırı yaratıcı alır gibi görünüyor ..)
Harold

4
Eşlik ("taşıma vektörü" olarak da bilinir) numarasının çeşitli durumlarda nasıl kullanılabileceğine dair daha fazla okuma: emulators.com/docs/LazyOverflowDetect_Final.pdf
jpa

4
Başka bir düzenleme yaptım; GNU C doğal vektörleri gerçekte sıkı örtüşme problemlerinden kaçınır ; bir vektörün verinin uint8_ttakma adı olmasına izin verilir uint8_t. Fonksiyonunuzun arayanları ( uint8_tveriyi a'ya alması gerekir uint64_t) katı takma konusunda endişelenmesi gereken kişilerdir! Yani muhtemelen OP sadece ilan etmeli / olarak diziler tahsis uint64_tçünkü char*ama tersi, ISO C takma şey ++ izin verilir.
Peter Cordes

16

RISC-V için muhtemelen GCC / clang kullanıyorsunuz.

Eğlenceli gerçek: GCC bu SWAR bithack hilelerinden bazılarını bilir (diğer cevaplarda gösterilmiştir) ve kod derlerken bunları sizin için kullanabilir donanım SIMD talimatları olmayan hedefler için GNU C yerel vektörleri . (Ama RISC-V için clang sadece naif bir şekilde skaler operasyonlara açacaktır, bu yüzden derleyiciler arasında iyi performans istiyorsanız bunu kendiniz yapmanız gerekir).

Yerel vektör sözdiziminin bir avantajı, bir makineyi hedeflerken , donanım SIMD'si olan , bithack'inizi veya bunun gibi korkunç bir şeyi otomatik olarak vektörlemek yerine bunu kullanmasıdır.

Yazmayı kolaylaştırır vector -= scalarİşlemleri ; Just Works sözdizimi, dolaylı olarak sizin için skaler splatting olarak yayın.


Ayrıca a'dan gelen bir uint64_t*yükün uint8_t array[]sıkı bir şekilde örtüşme UB olduğunu unutmayın , bu yüzden buna dikkat edin. (Ayrıca bkz. Glibc strlen'in hızlı bir şekilde çalışması için neden bu kadar karmaşık olması gerekir? Re: SWAR bithack'larını saf C'de sıkı bir şekilde yumuşatma yapmak). Bunun gibi bir şeyinuint64_t şeyin char*, ISO C / C ++ 'da nasıl çalıştığı gibi, diğer nesnelere erişmek için işaretçi atabileceğinizi .

uint8_t verilerini diğer cevaplarla kullanmak için uint64_t içine almak için bunları kullanın:

// GNU C: gcc/clang/ICC but not MSVC
typedef uint64_t  aliasing_u64 __attribute__((may_alias));  // still requires alignment
typedef uint64_t  aliasing_unaligned_u64 __attribute__((may_alias, aligned(1)));

Takma emniyetli yükler yapmanın diğer yolu , hizalama gereksinimini de ortadan kaldıran a'ya memcpydönüştürmektir . Ancak, etkili hizalanmamış yükler olmayan ISA'larda, gcc / clang , işaretçinin hizalandığını kanıtlayamadıklarında performans göstermez ve bu da performans için felaket olur.uint64_talignof(uint64_tmemcpy

TP: DR: en iyi bahis size gibi verileri bildirmek içinuint64_t array[...] ya da dinamik olarak tahsis uint64_t, tercihen veyaalignas(16) uint64_t array[]; en az 8 bayt veya 16 olmasını sağlar hizalama belirttiğiniz takdirde bu alignas.

Yana uint8_tneredeyse kesin olduğunu unsigned char*, bir bayt erişmek için güvenli uint64_taracılığı uint8_t*(ama tersi bir uint8_t dizisi için). Dolayısıyla, dar eleman türünün olduğu bu özel durum için unsigned char, sıkı kenar yumuşatma sorununu ortadan kaldırabilirsiniz, çünküchar özeldir.


GNU C yerel vektör sözdizimi örneği:

GNU C doğal vektörlerinin her zaman altta yatan türleriyle takma adlarına izin verilir (örn. int __attribute__((vector_size(16)))Güvenli bir şekilde takma ad olabilir, intancak değil floatveya uint8_tbaşka bir şey olabilir).

#include <stdint.h>
#include <stddef.h>

// assumes array is 16-byte aligned
void dec_mem_gnu(uint8_t *array) {
    typedef uint8_t v16u8 __attribute__ ((vector_size (16), may_alias));
    v16u8 *vecs = (v16u8*) array;
    vecs[0] -= 1;
    vecs[1] -= 1;   // can be done in a loop.
}

Herhangi bir HW SIMD'siz RISC-V için şunları kullanabilirsiniz: vector_size(8) için, verimli bir şekilde kullanabileceğiniz ayrıntı düzeyini ifade etmek için kullanabilirsiniz ve daha küçük vektörlerin iki katını yapabilirsiniz.

Fakat vector_size(8) hem GCC hem de clang ile x86 için çok aptalca derler: GCC, GP-tamsayı kayıtlarında SWAR bithack'lerini kullanır, clang, 16 baytlık bir XMM kaydını doldurmak için 2 baytlık öğelere açar ve ardından yeniden paketler. (MMX o kadar eskimiş ki, GCC / clang en azından x86-64 için bile kullanmıyor.)

Ama ile vector_size (16)( Godbolt ) beklediğimizden olsun movdqa/ paddb. (Tarafından oluşturulan bir all-ones vektör ile pcmpeqd same,same). İle-march=skylake hala yerine bir YMM iki ayrı XMM op olsun, bu yüzden ne yazık ki şimdiki düzenleyicileri ayrıca daha geniş vektörlere değil "otomatik vectorize" Vektör ops yapın: /

AArch64 için, kullanmak o kadar da kötü değil vector_size(8)( Godbolt ); ARM / AArch64 yerel olarak 8 veya 16 baytlık yığınlarla dveya qkayıtlarla çalışabilir .

Bu nedenle vector_size(16), x86, RISC-V, ARM / AArch64 ve POWER'da taşınabilir performans istiyorsanız , aslında derlemek istersiniz . Ancak, diğer bazı ISA'lar, MIPS MSA gibi 64 bit tamsayı kayıtlarında SIMD yapıyor.

vector_size(8)asm'a bakmayı kolaylaştırır (sadece bir kayıt veri değerinde): Godbolt derleyici gezgini

# GCC8.2 -O3 for RISC-V for vector_size(8) and only one vector

dec_mem_gnu(unsigned char*):
        lui     a4,%hi(.LC1)           # generate address for static constants.
        ld      a5,0(a0)                 # a5 = load from function arg
        ld      a3,%lo(.LC1)(a4)       # a3 = 0x7F7F7F7F7F7F7F7F
        lui     a2,%hi(.LC0)
        ld      a2,%lo(.LC0)(a2)       # a2 = 0x8080808080808080
                             # above here can be hoisted out of loops
        not     a4,a5                  # nx = ~x
        and     a5,a5,a3               # x &= 0x7f... clear high bit
        and     a4,a4,a2               # nx = (~x) & 0x80... inverse high bit isolated
        add     a5,a5,a3               # x += 0x7f...   (128-1)
        xor     a5,a4,a5               # x ^= nx  restore high bit or something.

        sd      a5,0(a0)               # store the result
        ret

Diğer döngüsel olmayan cevaplarla aynı temel fikir olduğunu düşünüyorum; Taşımayı önler ve sonucu düzeltir

Bu 5 ALU talimatı, bence en iyi cevaptan daha kötü. Ancak kritik yol gecikmesi sadece 3 döngü gibi görünüyor, her biri XOR'a giden 2 talimatdan oluşan iki zincir. @ Monica - ζ - 'nın cevabı 4 zamanlı bir dep zinciriyle derlenir (x86 için). 5 zamanlı döngü verimi, bir saf dahil olmak üzere tıkanır.sub kritik yolda ve döngü gecikmede darboğaz yapar.

Ancak, bu clang ile işe yaramaz. Hatta yüklediği sırayla ekleyip saklamaz, bu yüzden iyi bir yazılım boru hattı bile yapmaz!

# RISC-V clang (trunk) -O3
dec_mem_gnu(unsigned char*):
        lb      a6, 7(a0)
        lb      a7, 6(a0)
        lb      t0, 5(a0)
...
        addi    t1, a5, -1
        addi    t2, a1, -1
        addi    t3, a2, -1
...
        sb      a2, 7(a0)
        sb      a1, 6(a0)
        sb      a5, 5(a0)
...
        ret

13

Tek bir uint64_t daha fazla uğraşmaya başladığınızda, yazdığınız kod aslında vectorized olduğunu işaret ediyorum.

https://godbolt.org/z/J9DRzd


1
Orada neler olduğunu açıklayabilir veya referans verebilir misiniz? Oldukça ilginç görünüyor.
n314159

2
Bunu SIMD talimatları olmadan yapmaya çalışıyordum ama bu ilginç hiçbiri daha az buldum :)
cam-white

8
Öte yandan, bu SIMD kodu korkunç. Derleyici burada neler olduğunu tamamen yanlış anladı. E: o "hiçbir insan bu aptalca olurdu, çünkü bu açıkça bir derleyici tarafından yapıldığını" bir örnek
Harold

1
@PeterCordes: Daha çok __vector_loop(index, start, past, pad)bir uygulamanın for(index=start; index<past; index++)[herhangi bir uygulamanın kodu kullanarak, yalnızca bir makro tanımlayarak işleyebileceği anlamına geldiği gibi ] ele alabileceği bir yapı çizgileri boyunca düşünüyordum , ancak bir derleyiciyi işleri işlemeye davet etmek için daha gevşek anlambilimine sahip olacaktım yığın boyutunun padkatları değilse, başlangıcı aşağıya doğru ve sonuna kadar uzatan iki parçanın gücü . Her yığın içindeki yan etkiler emsalsiz olur breakve döngü içinde bir olay meydana gelirse, diğer temsilciler ...
Supercat

1
@PeterCordes: restrictyararlı olsa da (ve eğer Standart, "en azından potansiyel olarak dayalı" kavramını tanıdıysa ve daha sonra aptal ve işlenemez köşe kasaları olmadan doğrudan "dayalı" ve "en azından potansiyel olarak dayalı" olarak tanımlanmışsa daha yararlı olacaktır) önerim ayrıca bir derleyicinin döngüden istenenden daha fazla yürütme gerçekleştirmesine izin verecekti - vektörleştirmeyi büyük ölçüde basitleştirecek, ancak Standardın hiçbir hüküm vermemesi.
supercat

11

Çıkarmanın taşmadığından emin olabilir ve daha sonra yüksek biti düzeltebilirsiniz:

uint64_t sub(uint64_t arg) {
    uint64_t x1 = arg | 0x80808080808080;
    uint64_t x2 = ~arg & 0x80808080808080;
    // or uint64_t x2 = arg ^ x1; to save one instruction if you don't have an andnot instruction
    return (x1 - 0x101010101010101) ^ x2;
}

Bence bir bayt 256 olası değerleri için çalışır; 0x0, 0x7f, 0x80 ve 0xff (sayının ortasına kaydırılmış) gibi çeşitli girdiler için sabit yayılım sonuçlarına bakmak için Godbolt'a (RISC-V clang ile) godbolt.org/z/DGL9aq koydum . İyi görünüyor. Bence en iyi cevap aynı şeye benziyor, ama daha karmaşık bir şekilde açıklıyor.
Peter Cordes

Derleyiciler buradaki kayıtlarda sabitler oluşturmak için daha iyi bir iş yapabilirler. çınlama inşa talimatlar harcıyor splat(0x01)ve splat(0x80)bunun yerine bir kayma ile biri diğerinden alma. Godbolt.org/z/6y9v-u kaynağında bu şekilde yazmak bile , derleyiciyi daha iyi kod yapmak için el altında tutmaz; sadece sürekli yayılım yapar.
Peter Cordes

Acaba neden sadece sabiti bellekten yüklemiyor; Alpha (benzer bir mimari) için derleyiciler bunu yapar.
Falk Hüffner

RISC V GCC yapar bellekten yük sabitleri. Veri önbelleği özledikleri beklenmedikçe ve talimat verimi ile karşılaştırıldığında pahalı olmadığı sürece, clang'ın bazı ayarlara ihtiyacı var gibi görünüyor. (Bu denge Alpha'dan bu yana kesinlikle değişmiş olabilir ve RISC-V'nin muhtemelen farklı uygulamaları farklıdır. Derleyiciler, bir LUI / add ile başladıktan sonra değişebildikleri tekrarlayan bir kalıp olduğunu fark etseler de çok daha iyi yapabilirler. 20 + 12 = 32 bit anlık veri.AArch64'nin bit deseni hemen bunları AND / OR / XOR, akıllı kod çözme ve yoğunluk seçimi için anında bile kullanabilir)
Peter Cordes

RISC-V için GCC'nin doğal vektör SWAR'sını gösteren bir cevap eklendi
Peter Cordes

7

İstediğiniz bu olup olmadığından emin değilim ancak 8 çıkarma işlemini birbirine paralel olarak yapıyor:

#include <cstdint>

constexpr uint64_t mask = 0x0101010101010101;

uint64_t sub(uint64_t arg) {
    uint64_t mask_cp = mask;
    for(auto i = 0; i < 8 && mask_cp; ++i) {
        uint64_t new_mask = (arg & mask_cp) ^ mask_cp;
        arg = arg ^ mask_cp;
        mask_cp = new_mask << 1;
    }
    return arg;
}

Açıklama: Bit maskesi, 8 bitlik sayıların her birinde 1 ile başlar. Argümanımızla xor. Bu yerde bir 1 olsaydı, biz 1 çıkardı ve durdurmak zorunda. Bu, new_mask'ta karşılık gelen bit'i 0 olarak ayarlayarak yapılır. Eğer 0 olsaydı, onu 1 olarak ayarladık ve taşıma işlemini yapmak zorunda kaldık, böylece bit 1 kalır ve maskeyi sola kaydırırız. Yeni maskenin neslinin amaçlandığı gibi çalışıp çalışmadığını kontrol etsen iyi olur, sanırım, ama ikinci bir görüş kötü olmaz.

Not: Döngüde mask_cpboş olmama denetiminin programı yavaşlatabileceğinden emin değilim . Onsuz, kod yine de doğru olurdu (0 maskesi sadece hiçbir şey yapmaz) ve derleyicinin döngü açma işlemini yapması çok daha kolay olurdu.


forparalel koşmayacaksınız, kafanız mı karıştı for_each?
LTPCGO

3
@LTPCGO Hayır, bunu döngü için paralelleştirmek niyetim değil, aslında algoritmayı bozacaktı. Ancak bu kod, 64 bit tamsayıdaki farklı 8 bit tamsayılarda paralel olarak çalışır, yani 8 çıkarma da aynı anda yapılır, ancak 8 adıma kadar gerekir.
n314159

Sorduğum şeyin biraz mantıksız olabileceğini fark ettim ama bu ihtiyacım olana oldukça yakındı :)
cam-white

4
int subtractone(int x) 
{
    int f = 1; 

    // Flip all the set bits until we find a 1 at position y
    while (!(x & f)) { 
        x = x^f; 
        f <<= 1; 
    } 

    return x^f; // return answer but remember to flip the 1 at y
} 

Yukarıdakileri kullanarak bitsel işlemlerle yapabilirsiniz ve bu işleve 8 kez göndermek için tam sayınızı 8 bit parçalara bölmeniz yeterlidir. Aşağıdaki bölüm 64 bitlik bir sayının sekiz 8 bitlik değere bölünmesi bölümünden alınmıştır. benimle yukarıdaki işlevi ekleyerek

uint64_t v= _64bitVariable;
uint8_t i=0,parts[8]={0};
do parts[i++] = subtractone(v&0xFF); while (v>>=8);

Birinin buna nasıl rastladığına bakılmaksızın geçerli C veya C ++ geçerlidir


5
Bu OP'nin sorusu olan çalışmayı paralelleştirmiyor.
nickelpro

Evet @ nickelpro haklı, bu her çıkarmayı birbiri ardına yapar, 8 bitlik tam sayıların hepsini aynı anda çıkarmak isterim. Teşekkürler cevap tho teşekkür ediyorum
cam-white

2
@nickelpro cevabı başlattığımda, sorunun paralel kısmını belirten düzenleme yapılmadı ve bu yüzden gönderildikten sonraya kadar fark etmedim, en azından cevap verdiği için diğerleri için yararlı olması durumunda ayrılacak kısımları bitsel işlemler yapmak ve bunun for_each(std::execution::par_unseq,...yerine
whiles

2
Benim hatam, soruyu gönderdim, sonra paralel olması gerektiğini söylemediğimi fark ettim
cam-white

2

Kod ile gelmeye çalışmayacaksınız, ancak 1'lik bir azalma için 8 1'lik grup tarafından azaltılabilir ve daha sonra sonuçların LSB'lerinin "ters çevrildiğinden" emin olabilirsiniz. Değiştirilmemiş herhangi bir LSB, bitişik 8 bitten bir taşıma gerçekleştiğini gösterir. Herhangi bir dal olmadan, bununla başa çıkmak için bir dizi AND veya OR / XOR çalışmak mümkün olmalıdır.


Bu işe yarayabilir, ancak bir taşınmanın 8 bitlik bir gruptan diğerine kadar yayıldığı durumu düşünün. İyi cevaplardaki (MSB'yi veya ilk olarak bir şeyi) taşıma işleminin yayılmamasını sağlamak için strateji muhtemelen en azından bu kadar etkili olacaktır. Yenilecek mevcut hedef (yani iyi döngüsüz dalsız cevaplar), kritik yolu sadece 3 döngü yapan ve iki 64 bit sabit kullanan, talimat düzeyinde paralelliğe sahip 5 RISC-V asm ALU talimatıdır.
Peter Cordes

0

Her bayta tamamen tek başına odaklanın, sonra bulunduğu yere geri koyun.

uint64_t sub(uint64_t arg) {
   uint64_t res = 0;

   for (int i = 0; i < 64; i+=8) 
     res += ((arg >> i) - 1 & 0xFFU) << i;

    return res;
   }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.