Eğer çok yüksek kalitede rastlantısallığa ihtiyacınız yoksa ve tek biçime yakın dağılım yeterli ise , özellikle SSE2 veya AVX2 ile x86 gibi verimli SIMD tamsayı vektörleri içeren modern bir CPU'da çok hızlı gidebilirsiniz .
Bu, @ NominalAnimal'in cevabı gibi, çünkü ikimiz de aynı fikre sahiptik , ancak x86 için elle vektörelendik. (Ve daha kötü kalitede rastgele sayılarla, ancak muhtemelen birçok kullanım durumu için hala yeterince iyi.) Bu, 2.5GHz Intel Haswell'deki ~ 13GB / sn ASCII çıkışında @ Nominal kodundan yaklaşık 15 veya 30 kat daha hızlı çalışır AVX2 ile CPU. Bu hala teorik maksimum ana hafıza bant genişliğinden daha az (çift kanallı DDR3-1600 yaklaşık 25.6 GB / sn'dir), ancak / dev / null'a yazarken zamanlama yapıyordum, bu yüzden aslında sadece önbellekte sıcak kalan bir tamponu yeniden yazıyordum. Skylake bu kodu Haswell'den önemli ölçüde daha hızlı çalıştırmalıdır (bu cevabın sonuna bakınız).
Bunu bir diske veya boruya sokmak için gerçekten G / Ç üzerinde bir darboğaz bulunduğunu varsayarsak, hızlı bir uygulama CPU'nuzun rölantiden daha yüksek saatlere sahip olması gerekmediği anlamına gelir. Sonuç üretmek için çok daha az toplam enerji kullanır. (Pil ömrü / ısı / küresel ısınma.)
Bu o kadar hızlı ki muhtemelen diske yazmak istemiyorsunuz. İhtiyaç duyduğunuzda tekrar oluşturun (aynı veriyi tekrar istiyorsanız, aynı tohumdan). Tüm işlemcileri kullanabilen çok iş parçacıklı bir prosese beslemek isteseniz bile, bunu veriyi yönlendirmek için çalıştırmak, L3 önbelleğinde (ve bunu yazılan çekirdekte L2 önbelleğinde) sıcak tutacak ve çok kullanacak az işlemci zamanı. (Ancak, boruların yazıya çok fazla ek yük eklediğine dikkat edin /dev/null
. Skylake i7-6700k'de, wc -c
ya da sadece girişini okuyan + okuyan başka bir programa ya da boruya yazmak/dev/null
, yazmaktan 8 kat daha yavaştır ve sadece% 70’ini kullanır. CPU: Ama hala 3.9GHz işlemcide 4.0GB / sn.
Yeniden üretme, hızlı bir PCIe bağlantılı SSD'den bile yeniden okumaktan daha hızlıdır, ancak IDK daha güç verimli ise (vektör-tam sayı çarpanı oldukça meşgul tutulur ve muhtemelen diğer AVX2 ile birlikte güçle açtır) 256b vektör ALU'lar). OTOH, diskten okuma işleminin ne kadar zaman harcadığını bilmiyorum, bu girdiyi işleyen tüm çekirdekleri maksimize eden bir şey. 128k parçalarda yeniden oluşturulacak bir bağlam anahtarının dosya sistemi / pagecache kodunu çalıştırmak ve diskten veri okumak için sayfa tahsis etmekle rekabet edebileceğini tahmin ediyorum. Tabii ki, eğer sayfa önbelleğinde sıcaksa, basitçe not edin. OTOH, biz zaten memcpy kadar hızlı yazıyoruz! (ana hafıza bant genişliğini okuma ve yazma arasında bölmek zorundadır). (Ayrıca şunu da not edin.rep movsb
( Andy Glew'in bunu P6'da (Pentium Pro) uygulamasından beri RFO'dan kaçınan mikro kodda optimize edilmiş memcpy ve memset ).
Şimdiye kadar bu sadece bir konsept kanıtı ve yeni hat kullanımı sadece yaklaşık olarak doğru. 2 gücünün arabelleğinin uçlarında yanlış. Daha fazla gelişme zamanı ile. Ayrıca, tam olarak doğru olan yeni satırları eklemek için daha etkili bir yol bulabileceğime inanıyorum (en az bu kadar düşük gider) (yalnızca boşluklar çıkarmakla karşılaştırıldığında). Bunun% 10 ila% 20 gibi bir şey olduğunu düşünüyorum. Ben sadece cilalı bir sürümüne sahip olmakla değil, bu koşuyu ne kadar hızlı yapabileceğimizi bilmekle ilgileniyorum, bu yüzden bu bölümü okuyucu için bir alıştırma olarak bırakacağım.
DDG3-1600MHz RAM ile 2.5GHz max turbo bir Haswell i5, 100GiB üreten zamanlanmış ama küçültülmüş. (Win10'daki cygwin64'te gcc5.4 ile zaman aşımına uğradı -O3 -march=native
, -funroll-loops
çünkü bu ödünç alınan dizüstü bilgisayarda iyi zamanlama çalışmaları yapmak için yeterince zor zamanlar geçirdim.
aksi belirtilmedikçe / dev / null yazar.
- James Hollis'in: (test edilmedi)
- Nominal'in fwrite versiyonu: ~ 2.21s
- bu (SSE2): ~ 0.142s (ölçeklenmemiş zamanlar = gerçek = 14.232s, kullanıcı = 13.999s, sys = 0.187s).
- bu (AVX-128): ~ 0.140s
- bu (AVX2): ~ 0.073s (ölçeklenmemiş: real = 0m7.291s, kullanıcı = 0m7.125s, sys = 0m0.155s).
- bu (AVX2) cygwin boru
wc -c
hattına 128kiB tampon boyutuyla: 2.38GHz'de CPU ile 0.32s (maksimum çift çekirdekli turbo). (ölçeklenmemiş zamanlar: gerçek = 32.466s kullanıcı = 11.468s sys = 41.092s, hem de bu hem de wc
). Yine de verinin sadece yarısı kopyalandı, çünkü benim aptal programım yazmanın tüm arabellekleri yaptığını varsayıyor, bu durum böyle olmasa da ve cygwin write () bir çağrıya sadece 64k yazıyor.
Yani SSE2 ile bu, @Nominal Animal'in skaler kodundan yaklaşık 15 kat daha hızlıdır. AVX2 ile, yaklaşık 30 kat daha hızlı. Nominal'in sadece write()
bunun yerine kullandığı bir versiyonunu denemedim fwrite()
, ama muhtemelen büyük tamponlar için stdio çoğunlukla yoldan çıkıyor. Verileri kopyalıyorsa, bu çok yavaşlama anlamına gelir.
Core2Duo E6600 (Merom 2.4GHz, 32kiB özel L1, 4MiB paylaşımlı L2 önbellek), 64-bit Linux 4.2'de (Ubuntu 15.10) DDR2-533MHz'de 1GB veri üretme zamanı . Hala write () için bir 128kiB arabellek boyutu kullanmak, bu boyutu araştıramadı.
aksi belirtilmedikçe / dev / null yazar.
- (SSE2) bunu yeni satır işleme ve rastgele baytların her bir vektöründen 4 basamak hanesiyle gösterir : 0.183s (18.3'lerde 100GiB yaparken zamanlanmış, ancak 1GiB çalışması için benzer sonuçlar). 1.85 devir başına talimat.
- (SSE2) bu, borulara
wc -c
: 0.593s (ölçeklenmemiş: gerçek = 59.266s kullanıcı = 20.148s sys = 1m6.548s, wc'nin CPU zamanı dahil). Aynı sayıda write () sistemi cygwin'de olduğu gibi çağrıda bulunur, ancak Linux bütün 128k'lik bir yazıyı () bir yöneltme ile işlediğinden aslında tüm verileri birleştirir.
- NominalAnimal'in
fwrite()
sürümü (gcc5.2 -O3 -march=native
), aşağıdakilerle çalışır ./decdig 100 $((1024*1024*1024/200)) > /dev/null
: 3.19s +/-% 0.1, döngü başına 1.40 komut. -Funroll-Döngüler belki küçük bir fark yarattı. clang-3.8 -O3 -march=native
: 3.42s +/-% 0.1
- Nominal
fwrite
borular wc -c
: real = 3.980s kullanıcı = 3.176s sys = 2.080s
- James Hollis'in her seferinde bir hat sürümü (
clang++-3.8 -O3 -march=native
): 22885s +/-% 0.07, her bir döngü için 0.84 talimat. (g ++ 5.2, biraz yavaştı: 22.98s). Bir seferde sadece bir satır yazmak muhtemelen ciddi şekilde yaralandı.
- Stéphane Chazelas's
tr < /dev/urandom | ...
: gerçek = 41.430s kullanıcı = 26.832s sys = 40.120s. tr
CPU çekirdeğinin tamamını çoğu zaman kendisine çekiyordu, çekirdeğinde neredeyse tüm zamanını rasgele baytlar üretip bunları bir boruya kopyalayarak geçiriyordu. Bu çift çekirdekli makinedeki diğer çekirdek, boru hattının geri kalanını çalıştırıyordu.
time LC_ALL=C head -c512M </dev/urandom >/dev/null
: yani sadece boru kullanmadan o kadar rasgele olduğunu okumak: real = 35.018s user = 0.036s sys = 34.940s.
- Lĩu Vĩnh Phúc'in perl programı (Ubuntu15.10'daki v5.20.2 perl)
LANG=en_CA.UTF-8
: real = 4m32.634s kullanıcısı = 4m3.288s sys = 0m29.364.
LC_ALL=C LANG=C
: real = 4m18.637s kullanıcı = 3m50.324s sys = 0m29.356s. Hala çok yavaş.
- (SSE2) bunu , yeni satır kullanımı olmadan ve her bir rasgele bayt vektöründen 3 veya 4 basamak hanesi (hemen hemen tamamen aynı hızda:
dig3 = v%10
adım bu HW'de bile eşitlikle ilgilidir): 0.166s (döngü başına 1.82 talimat) . Bu, temel olarak, mükemmel verimli yeni hat kullanımıyla yaklaşabileceğimiz şeyler için alt sınırdır.
- (SSE2) Bunun yeni sürümü, yeni satır kullanımı olmadan, ancak uint16_t öğesi başına yalnızca bir rakam elde ederek
v%10
, 0.222 saniye +/-% 0.4, döngü başına 2.12 komut kullanarak . (Gcc5.2 ile derlenmiştir., Unroll loop'lar -march=native -O3 -funroll-loops
bu donanımdaki bu kod için yardımcı olur. Bunu özellikle büyük programlar için, kör kullanmayın).
- (SSE2) Bunun eski sürümü, bir dosyaya yazıyor (3 hızlı manyetik sabit sürücünün RAID10f2'sinde, yazma için çok iyi optimize edilmemiş): ~ 4 saniye. Write () bloklarından önce çok daha kirli verilere izin vermek için çekirdek G / Ç arabellek ayarlarını değiştirerek daha hızlı gidebilir. "Sistem" süresi hala ~ 1.0 saniyedir, "kullanıcı" saatinden çok daha yüksektir. Yavaş DDR2-533 RAM içeren bu eski sistemde, çekirdeğin verileri sayfa önbelleğine alması ve XFS işlevlerini çalıştırması, çekirdeğimin sıcak kaldığı bir arabellekte yerinde yeniden yazmasını sağlamak için benim döngümden daha uzun sürüyor önbelleği.
Nasıl yapılır
Hızlı bir PRNG açıkçası esastır. xorshift128 + vektörleştirilebilir, böylece bir SIMD vektörünün öğelerinde paralel olarak iki veya dört 64 bit jeneratörünüz olur. Her adım tam bir rastgele bayt vektörü üretir. ( Intel intrinics ile birlikte 256b AVX2 uygulaması ). Nominal'in xorshift * seçiminden seçtim, çünkü 64-bit vektör tamsayı çarpımı yalnızca SSE2 / AVX2'de genişletilmiş hassasiyetli tekniklerle mümkün .
Rastgele baytlık bir vektör verildiğinde, her 16 bitlik öğeyi birden çok ondalık basamağa bölebiliriz. Her biri ASCII basamak + ASCII alanı olan 16 bitlik öğelerin çoklu vektörlerini üretiyoruz . Bunu doğrudan çıktı tamponumuza saklıyoruz.
Orijinal versiyonum sadece x / 6554
bir vektörün her uint16_t elemanından rastgele bir rakam elde etmek için kullanılır . Her zaman 0 ile 9 arasında, dahil. Önyargılı 9
, çünkü (2^16 -1 ) / 6554
sadece 9.99923. (6554 = tavan ((2 ^ 16-1) / 10), ki bu bölümün daima <10 olmasını sağlar.)
x/6554
bir "sihirli" sabit ( sabit nokta karşılıklı ) ve yüksek yarı sonucun sağa kayması ile çarpılarak hesaplanabilir . Bu, sabit bir bölünme için en iyi durumdur; bazı bölenler daha fazla işlem yapar ve imzalı bölüm daha fazla iş gerektirir. x % 10
benzer önyargıya sahiptir ve hesaplanması ucuz değildir. (gcc'nin asm çıktısı eşdeğerdir x - 10*(x/10)
, yani modüler bir çarpma tersini kullanarak bölmenin üstüne ekstra çarpma ve çıkarma.) Ayrıca, xorshift128 + 'ın en düşük ucu, yüksek bitlerden entropi almak için ayırmanın daha iyi olması anlamına gelir ( düşük hızdan entropi almak için modülodan daha hızlı ve kaliteli).
Bununla birlikte, @ Nominal'in digit()
işlevi gibi düşük ondalık basamaklara bakarak her uint16_t içindeki entropinin daha fazlasını kullanabiliriz . Maksimum performans için, düşük 3 ondalık basamağı almaya karar verdim ve x/6554
bir PMULLW ve PSUBW (ve muhtemelen bazı MOVDQA) 'ı kurtarmaya karar verdim, buna karşılık 4 düşük ondalık basamağı almada daha yüksek kalite seçeneği var. x / 6554, düşük 3 ondalık basamaktan hafifçe etkilenir; bu nedenle, aynı öğeden gelen basamaklar arasında bazı korelasyonlar vardır (ASCII çıkışında vektör genişliğine bağlı olarak 8 veya 16 basamak ayırma).
Bence gcc art arda 10'a bölünen daha uzun bir zincirden ziyade 100'e ve 1000'e bölünüyor, bu yüzden muhtemelen her PRNG çıktısından 4 sonuç üreten döngü taşımayan bağımlılık zincirinin uzunluğunu önemli ölçüde kısaltmıyor. port0 (vektör çarpma ve kayma), modüler çarpma inversiyonları ve xorshift + içindeki kaymalar nedeniyle tıkanıklıktır, bu nedenle bir vektör çarpma işleminin kaydedilmesi kesinlikle yararlıdır.
xorshift + o kadar hızlıdır ki, her 16'dan (yani% 20 verimlilik) yalnızca ~ 3.3 bit rasgelelık kullanmak bile, onu birden fazla ondalık basamağa bölmekten çok daha yavaş değildir. Sadece homojen dağılıma yaklaşıyoruz, çünkü kalite çok kötü olmadığı sürece bu cevap hıza odaklanıyor.
Değişken sayıda öğeyi tutan her türlü koşullu davranış çok daha fazla iş gerektirecektir. (Ama yine de SIMD sol paketleme teknikleri kullanılarak daha verimli bir şekilde yapılabilir . Ancak, bu küçük eleman boyutları için daha az verimli olur; dev karıştırma maskesi arama tabloları uygun değildir ve 32'den küçük olan AVX2 şerit geçişi karıştırması yoktur. 128b PSHUFB sürümü BMI2 PEXT / PDEP ile anında daha büyük öğeler içeren AVX2 için yapabileceğiniz gibi bir maske oluşturabilir , ancak 64 bitlik bir tamsayı yalnızca 8 bayt tutabildiği için zor olabilir. Bu cevabın üzerinde, yüksek element sayıları için işe yarayabilecek bazı kodlar vardır.)
RNG'nin gecikmesi bir tıkanıklıksa, iki jeneratör vektörünü paralel olarak çalıştırarak hangisini kullandığımızı değiştirerek daha da hızlı gidebiliriz. Derleyici, kayıtlardaki her şeyi kontrolsüz bir döngüde kolayca tutabilir ve bu iki bağımlılık zincirinin paralel çalışmasını sağlar.
Şu anki sürümde PRNG'nin çıktısını keserek, PRNG gecikme yerine 0 numaralı çıkış noktasında geriledik, bu yüzden buna gerek yok.
Kod: AVX2 sürümü
Godbolt derleyici explorer hakkında daha fazla yorum ile tam sürüm .
Çok düzenli değil, üzgünüm uyumak zorundayım ve bunu yayınlamak istiyorum.
SSE2 sürümü almak, için s/_mm256/_mm
, s/256/128/
, s/v16u/v8u/
ve değişim vector_size(32)
de 4 * 16 4 * 8'den satır artışını değiştirmek 16'ya. (Dediğim gibi, kod dağınık ve iki sürümü derlemek için iyi ayarlanmamış. Başlangıçta bir AVX2 sürümü hazırlamayı planlamıyordum, ancak daha sonra gerçekten erişimime sahip olduğum bir Haswell CPU üzerinde test etmek istedim.)
#include <immintrin.h>
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
//#include <string.h>
// This would work equally fast 128b or 256b at a time (AVX2):
// https://stackoverflow.com/questions/24001930/avx-sse-version-of-xorshift128
struct rngstate256 {
__m256i state0;
__m256i state1;
};
static inline __m256i xorshift128plus_avx2(struct rngstate256 *sp)
{
__m256i s1 = sp->state0;
const __m256i s0 = sp->state1;
sp->state0 = s0;
s1 = _mm256_xor_si256(s1, _mm256_slli_epi64(s1, 23));
__m256i state1new = _mm256_xor_si256(_mm256_xor_si256(_mm256_xor_si256(s1, s0),
_mm256_srli_epi64(s1, 18)),
_mm256_srli_epi64(s0, 5));
sp->state1 = state1new;
return _mm256_add_epi64(state1new, s0);
}
// GNU C native vectors let us get the compiler to do stuff like %10 each element
typedef unsigned short v16u __attribute__((vector_size(32)));
__m256i* vec_store_digit_and_space(__m256i vec, __m256i *restrict p)
{
v16u v = (v16u)vec;
v16u ten = (v16u)_mm256_set1_epi16(10);
v16u divisor = (v16u)_mm256_set1_epi16(6554); // ceil((2^16-1) / 10.0)
v16u div6554 = v / divisor; // Basically the entropy from the upper two decimal digits: 0..65.
// Probably some correlation with the modulo-based values, especially dig3, but we do this instead of
// dig4 for more ILP and fewer instructions total.
v16u dig1 = v % ten;
v /= ten;
v16u dig2 = v % ten;
v /= ten;
v16u dig3 = v % ten;
// dig4 would overlap much of the randomness that div6554 gets
const v16u ascii_digitspace = (v16u)_mm256_set1_epi16( (' '<<8) | '0');
v16u *vecbuf = (v16u*)p;
vecbuf[0] = div6554 | ascii_digitspace;
vecbuf[1] = dig1 | ascii_digitspace;
vecbuf[2] = dig2 | ascii_digitspace;
vecbuf[3] = dig3 | ascii_digitspace;
return p + 4; // always a constant number of full vectors
}
void random_decimal_fill_buffer(char *restrict buf, size_t len, struct rngstate256 *restrict rngstate)
{
buf = __builtin_assume_aligned(buf, 32);
// copy to a local so clang can keep state in register, even in the non-inline version
// restrict works for gcc, but apparently clang still thinks that *buf might alias *rngstate
struct rngstate256 rng_local = *rngstate;
__m256i *restrict p = (__m256i*restrict)buf;
__m256i *restrict endbuf = (__m256i*)(buf+len);
static unsigned newline_pos = 0;
do {
__m256i rvec = xorshift128plus_avx2(&rng_local);
p = vec_store_digit_and_space(rvec, p); // stores multiple ASCII vectors from the entropy in rvec
#if 1
// this is buggy at the end or start of a power-of-2 buffer:
// usually there's a too-short line, sometimes a too-long line
const unsigned ncols = 100;
newline_pos += 4*16;
if (newline_pos >= ncols) {
newline_pos -= ncols;
char *cur_pos = (char*)p;
*(cur_pos - newline_pos*2 - 1) = '\n';
}
#endif
// Turning every 100th space into a newline.
// 1) With an overlapping 1B store to a location selected by a counter. A down-counter would be more efficient
// 2) Or by using a different constant for ascii_digitspace to put a newline in one element
// lcm(200, 16) is 400 bytes, so unrolling the loop enough to produce two full lines makes a pattern of full vectors repeat
// lcm(200, 32) is 800 bytes
// a power-of-2 buffer size doesn't hold a whole number of lines :/
// I'm pretty sure this can be solved with low overhead, like maybe 10% at worst.
} while(p <= endbuf-3);
*rngstate = rng_local;
}
#define BUFFER_SIZE (128 * 1024)
const static size_t bufsz = BUFFER_SIZE;
__attribute__((aligned(64))) static char static_buf[BUFFER_SIZE];
int main(int argc, char *argv[])
{
// TODO: choose a seed properly. (Doesn't affect the speed)
struct rngstate256 xorshift_state = {
_mm256_set_epi64x(123, 456, 0x123, 0x456),
_mm256_set_epi64x(789, 101112, 0x789, 0x101112)
};
for (int i=0; i < 1024ULL*1024*1024 / bufsz * 100; i++) {
random_decimal_fill_buffer(static_buf, bufsz, &xorshift_state);
size_t written = write(1, static_buf, bufsz);
(void)written;
//fprintf(stderr, "wrote %#lx of %#lx\n", written, bufsz);
}
}
Gcc, clang veya ICC ile derleyin (ya da C99'un GNU C lehçesini ve Intel'in özünü anlayan herhangi bir derleyici). GNU C vektör uzantıları, derleyicinin bölüm / modülo için sihirli sayıları modüler çarpma tersini kullanarak üretmesini sağlamak için son derece kullanışlıdır ve zaman zaman __attribute__
kullanışlıdır.
Bu taşınabilir bir şekilde yazılabilir, ancak daha fazla kod alacaktır.
Performans notları:
Yeni çizgiler eklemek için üst üste binen mağazanın, nereye yerleştirileceğine karar vermek için önemli bir ek yükü vardır (şube yanlış tahminleri ve Çekirdek2 üzerindeki ön darboğazlar), ancak mağazanın performans üzerinde hiçbir etkisi yoktur. Sadece derleyicinin deposundaki mağaza talimatının yorumlanması (tüm dalların aynı bırakılması), Core2'deki performansı tamamen değişmeden bıraktı ve tekrarlanan işlemler aynı zamanda% 1'den daha az +/- verdi. Bu yüzden ben mağaza tampon / önbellek sadece iyi idare ettiği sonucuna varıyorum.
Yine de, ascii_digitspace
herhangi bir sayaç / dallanmanın ortadan kalkması için yeterince açılırsa, yeni bir çizgiye sahip bir elemanlı bir tür döner pencere kullanmak daha da hızlı olabilir.
/ Dev / null öğesinin yazılması temel olarak no-op'tur, bu nedenle tampon L2 önbelleğinde sıcak kalır (Haswell'de çekirdek başına 256kiB). 128b vektörlerden 256b vektörlere kadar mükemmel hız bekleniyor: Ekstra talimat yok ve her şey (mağazalar dahil) genişliğin iki katı ile oluyor. Yeni satır ekleme dalı, yine de iki kez sık alınır. Maalesef Haswell cygwin kurulumuma o kısımda zaman ayıramadım #ifdef
.
2.5GHz * 32B / 13.7GB / s = Haswell'de AVX2 mağazası başına 5.84 döngü. Bu oldukça iyi, ama daha hızlı olabilirdi. Belki cygwin sistem çağrısında düşündüğümden daha fazla yük var. Derleyicinin asm çıktısındakileri yorumlamayı denemedim (bu hiçbir şeyin optimize edilmemesini sağlayacaktı.)
L1 önbellek, saat başına bir 32B deposunu koruyabilir ve L2 çok düşük bant genişliği değildir (yine de daha yüksek gecikme süresi).
Birkaç versiyondan önce IACA'ya baktığımda (yeni hatlar için dallanma olmadan, ancak RNG vektörü başına sadece bir ASCII vektör elde ettim), 4 veya 5 saat başına bir 32B vektör mağazası gibi bir şey öngörüyordu.
Agner Fog'un rehberlerini ve SO x86 etiketi wiki'deki bağlantılarını eklediğim diğer optimizasyon kaynaklarını göz önünde bulundurarak, her RNG sonucundan daha fazla veri çıkarmaktan daha fazla hız almayı umuyordum .)
Muhtemelen , vektör tamsayı çarpımı ve kaymanın Haswell'e kıyasla iki kat daha fazla (p0 / p1) çalıştırılabildiği Skylake'de (sadece p0) çok daha hızlı olacaktır. xorshift ve rakam çıkartma işlemlerinde çok fazla kayma ve çarpma kullanılır. ( Güncelleme: Skylake 3.02 IPC'de çalışıyor, bize 32-byte AVX2 mağazası başına 3.77 devir veriyor , 1GB iterasyon başına /dev/null
0.030 s'de, 3.7GHz'de i7-6700k'de Linux 4.15'e yazıyor.
İyi çalışması için 64 bit mod gerektirmez . SSE2 sürümü, derlendiğinde çok hızlıdır -m32
, çünkü çok fazla vektör kaydına ihtiyaç duymaz ve tüm 64-bit matematik, genel amaçlı kayıtlara değil, vektörlerde yapılır.
Core2'de 32-bit modunda aslında biraz daha hızlı, çünkü karşılaştır / dal makro-füzyonu sadece 32-bit modunda çalışıyor, bu nedenle sıra dışı çekirdek için daha az sayıda uop var (18.3s (Saatte 1.85 Talimatlar) vs 16.9s (2.0 IPC)). REX öneklerine sahip olmamaktan daha küçük kod boyutu Core2'nin kod çözücülerini de yardımcı olur.
Ayrıca, bazı reg-reg vektör hareketleri yüklerle değiştirilir, çünkü tüm sabitler artık regs vektörlerinde sabitlenmez. L1 önbellekten gelen yük verimi bir darboğaz olmadığından, bu gerçekten yardımcı olur. (örneğin, sabit bir vektör ile çarparak set1(10)
: movdqa xmm0, xmm10
/ pmullw xmm0, xmm1
dönüşür movdqa xmm0, [constant]
/ ' pmullw xmm0, xmm1
.) reg-reg MOVDQA yana ALU portu gerektirir, bu gerçek iş yapılan ile rekabet, ancak MOVDQA yük yalnızca ön uç kod çözme bant genişliği için rekabet eder. (Pek çok talimatın içinde 4 baytlık bir adres olması REX öneklerinin kaydedilmesinden elde edilen kazancı ortadan kaldırır.
ALU MOVDQA cihazlarını kurtarmanın, gerçek kazanımların geldiği yer olması durumunda şaşırmam, çünkü ön uç ortalama 2.0 IPC'ye oldukça iyi uyuyor olmalıydı.
Tüm bu farklılıklar, geri döngü tamponu değilse, her şeyin kodu çözülmüş-uop önbellekten çalışması gereken Haswell'de kaybolur. ALU + branş makro füzyonu, Nehalem'den bu yana her iki modda da çalışır.