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 memcpy
dö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_t
alignof(uint64_t
memcpy
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_t
neredeyse kesin olduğunu unsigned char*
, bir bayt erişmek için güvenli uint64_t
aracı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, int
ancak değil float
veya uint8_t
baş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 d
veya q
kayı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