Jika Anda tidak memerlukan keacakan kualitas sangat tinggi, dan distribusi hampir seragam cukup baik, Anda dapat berjalan sangat cepat, terutama pada CPU modern dengan vektor integer SIMD efisien seperti x86 dengan SSE2 atau AVX2.
Ini seperti jawaban @ NominalAnimal karena kami berdua memiliki ide yang sama, tetapi secara manual di-vektor-kan untuk x86. (Dan dengan angka acak kualitas yang lebih buruk, tetapi mungkin masih cukup baik untuk banyak kasus penggunaan). Ini berjalan sekitar 15 atau 30 kali lebih cepat dari kode @ Nominal, pada ~ 13GB / s output ASCII pada 2.5GHz Intel Haswell CPU dengan AVX2. Itu masih kurang dari bandwidth maksimum memori teoretis max (dual channel DDR3-1600 adalah sekitar 25.6GB / s), tapi saya sedang menulis waktu untuk / dev / null jadi itu sebenarnya hanya menulis ulang buffer yang tetap panas di cache. Skylake harus menjalankan kode yang sama ini secara signifikan lebih cepat daripada Haswell (lihat bagian bawah jawaban ini).
Dengan asumsi Anda benar-benar bottleneck pada I / O ke disk atau pipa ini di suatu tempat, implementasi yang cepat berarti CPU Anda bahkan tidak perlu clock lebih tinggi daripada idle. Ia menggunakan energi total yang jauh lebih sedikit untuk menghasilkan hasilnya. (Usia baterai / panas / pemanasan global.)
Ini sangat cepat sehingga Anda mungkin tidak ingin menulisnya ke disk. Cukup hasilkan kembali sesuai kebutuhan (dari seed yang sama jika Anda menginginkan data yang sama lagi). Bahkan jika Anda ingin memasukkannya ke proses multi-utas yang dapat menggunakan semua CPU, menjalankan ini untuk menyalurkan data ke sana akan membuatnya panas di L3 cache (dan L2 cache pada inti yang menulisnya), dan gunakan sangat waktu CPU sedikit. (Tetapi perhatikan bahwa perpipaan menambahkan banyak overhead vs tulisan /dev/null
. Pada Skylake i7-6700k, perpipaan ke wc -c
atau program lain yang hanya membaca + membuang inputnya, ini sekitar 8x lebih lambat daripada menulis/dev/null
, dan hanya menggunakan 70% dari CPU, tetapi itu masih 4,0GB / s pada CPU 3,9GHz.
Menghasilkannya kembali lebih cepat daripada membacanya kembali bahkan dari SSD yang terhubung PCIe cepat, tetapi IDK jika lebih hemat daya (pengganda integer vektor tetap sangat sibuk, dan mungkin sangat haus daya, bersama dengan AVX2 lainnya 256b vektor ALU). OTOH, saya tidak tahu berapa banyak waktu CPU membaca dari disk akan mengambil dari sesuatu yang memaksimalkan semua core yang memproses input ini. Saya kira bahwa konteks-switch untuk menghasilkan kembali dalam potongan 128k mungkin kompetitif dengan menjalankan filesystem / kode pagecache dan mengalokasikan halaman untuk membaca data dari disk. Tentu saja, jika sudah panas di pagecache, itu pada dasarnya memcpy. OTOH, kami sudah menulis secepat memcpy! (yang harus membagi bandwidth memori utama antara membaca dan menulis). (Juga perhatikan bahwa menulis ke memori bahwa 'rep movsb
(dioptimalkan memcpy dan memset dalam mikrokode, yang menghindari RFO, sejak implementasi Andy Glew di P6 (Pentium Pro) )).
Sejauh ini ini hanya bukti konsep, dan penanganan baris baru hanya kira-kira benar. Ada yang salah di sekitar ujung buffer power-of-2. Dengan lebih banyak waktu pengembangan. Saya yakin saya bisa menemukan cara yang lebih efisien untuk memasukkan baris baru yang juga tepat benar, dengan overhead setidaknya serendah ini (dibandingkan dengan hanya menghasilkan spasi). Saya pikir ini sekitar 10 hingga 20%. Saya hanya tertarik mengetahui seberapa cepat kami dapat menjalankan ini, tidak benar-benar memiliki versi yang dipoles, jadi saya akan meninggalkan bagian itu sebagai latihan untuk pembaca, dengan komentar yang menggambarkan beberapa ide.
Pada Haswell i5 pada 2.5GHz max turbo, dengan DDR3-1600MHz RAM , waktunya menghasilkan 100GiB tetapi diperkecil. (Waktunya pada cygwin64 pada Win10 dengan gcc5.4 -O3 -march=native
, dihilangkan -funroll-loops
karena saya memiliki waktu yang cukup sulit untuk menjalankan pengaturan waktu yang layak pada laptop yang dipinjam ini. Seharusnya baru saja mem-boot Linux pada USB).
menulis ke / dev / null kecuali ditentukan lain.
- James Hollis's: (tidak diuji)
- Versi fwrite Nominal: ~ 2.21s
- this (SSE2): ~ 0.142s (waktu yang tidak dihitung = real = 14.232s, pengguna = 13.999s, sys = 0.187s).
- ini (AVX-128): ~ 0,140s
- ini (AVX2): ~ 0,073s (unscaled: real = 0m7.291s, pengguna = 0m7.125s, sys = 0m0.155s).
- ini (AVX2) cygwin piping ke
wc -c
, dengan 128kiB ukuran buffer: 0,32s dengan CPU pada 2,38GHz (max turbo dual-core). (waktu tidak dihitung: nyata = 32,466 pengguna = 11,468 detik sys = 41,092 detik, termasuk keduanya dan ini wc
). Namun, hanya setengah data yang benar-benar disalin, karena program konyol saya menganggap bahwa tulis melakukan buffer penuh, meskipun itu tidak terjadi dan cygwin menulis () hanya melakukan 64k per panggilan ke pipa.
Jadi dengan SSE2 ini sekitar 15 kali lebih cepat dari kode skalar @Nominal Animal. Dengan AVX2, sekitar 30 kali lebih cepat. Saya tidak mencoba versi kode Nominal yang hanya menggunakan write()
bukan fwrite()
, tetapi mungkin untuk buffer besar stdio sebagian besar tetap menyingkir. Jika menyalin data, itu akan menyebabkan banyak pelambatan.
Kali untuk menghasilkan 1GB data pada Core2Duo E6600 (Merom 2.4GHz, 32kiB private L1, 4MiB berbagi cache L2), DDR2-533MHz di 64-bit Linux 4.2 (Ubuntu 15.10). Masih menggunakan ukuran buffer 128kiB untuk write (), belum menjelajahi dimensi itu.
menulis ke / dev / null kecuali ditentukan lain.
- (SSE2) ini dengan penanganan baris baru dan 4 vektor digit dari masing-masing vektor byte acak: 0,183s (waktunya melakukan 100GiB dalam 18,3s, tetapi hasil serupa untuk 1GiB berjalan). 1,85 instruksi per siklus.
- (SSE2) ini, menyalurkan ke
wc -c
: 0,593s (unscaled: real = 59.266s pengguna = 20.148s sys = 1m6.548s, termasuk waktu CPU wc). Jumlah yang sama dari sistem write () memanggil seperti dengan cygwin, tetapi sebenarnya mem-pip semua data karena Linux menangani semua 128k dari write () ke sebuah pipa.
- NominalAnimal ini
fwrite()
versi (gcc5.2 -O3 -march=native
), dijalankan dengan ./decdig 100 $((1024*1024*1024/200)) > /dev/null
: 3.19s +/- 0,1%, dengan 1,40 instruksi per siklus. -funroll-loop mungkin membuat perbedaan kecil. clang-3.8 -O3 -march=native
: 3.42s +/- 0,1%
- Nominal
fwrite
piping ke wc -c
: real = 3.980s pengguna = 3.176s sys = 2.080s
- Versi baris-at-a-waktu James Hollis (
clang++-3.8 -O3 -march=native
): 22,855 +/- 0,07%, dengan 0,84 instruksi per siklus. (g ++ 5.2 sedikit lebih lambat: 22.98s). Menulis hanya satu baris pada satu waktu mungkin sangat menyakitkan.
- Stéphane Chazelas's
tr < /dev/urandom | ...
: real = 41,430s pengguna = 26,832s sys = 40,120s. tr
mendapatkan semua inti CPU untuk dirinya sendiri sebagian besar waktu, menghabiskan hampir seluruh waktunya di driver kernel menghasilkan byte acak dan menyalinnya ke sebuah pipa. Core lain pada mesin dual core ini menjalankan sisa pipa.
time LC_ALL=C head -c512M </dev/urandom >/dev/null
: yaitu hanya membaca bahwa banyak keacakan tanpa piping: real = 35.018s pengguna = 0,036s sys = 34.940s.
- Program perl Lưu Vĩnh Phúc (perl v5.20.2 dari Ubuntu15.10)
LANG=en_CA.UTF-8
:: real = 4m32.634s pengguna = 4m3.288s sys = 0m29.364.
LC_ALL=C LANG=C
: real = 4m18.637s pengguna = 3m50.324s sys = 0m29.356s. Masih sangat lambat.
- (SSE2) ini tanpa penanganan baris baru , dan 3 atau 4 vektor digit dari masing-masing vektor byte acak (kecepatan hampir persis sama:
dig3 = v%10
langkahnya adalah tentang impas pada HW ini): 0,166s (1,82 instruksi per siklus) . Ini pada dasarnya adalah batas bawah untuk apa yang bisa kita lakukan dengan penanganan baris baru yang sangat efisien.
- (SSE2) Versi lama ini tanpa penanganan baris baru, tetapi hanya mendapatkan satu digit per elemen uint16_t menggunakan
v%10
, 0,222 detik +/- 0,4%, 2,12 instruksi per siklus. (Dikompilasi dengan gcc5.2 -march=native -O3 -funroll-loops
,. Buka gulungan tidak terjadi untuk membantu kode ini pada perangkat keras ini. Jangan menggunakannya secara membabi buta, terutama untuk program besar).
- (SSE2) Versi lama ini, menulis ke file (pada RAID10f2 dari 3 hard drive magnetik cepat, tidak terlalu dioptimalkan untuk menulis): ~ 4 detik. Bisa lebih cepat dengan mengubah pengaturan buffer I / O kernel untuk memungkinkan lebih banyak data kotor sebelum blok tulis (). Waktu "Sistem" masih ~ 1,0 detik, jauh lebih tinggi dari waktu "pengguna". Pada sistem lama ini dengan RAM DDR2-533 yang lambat, dibutuhkan ~ 4x lebih lama untuk memcpy data ke dalam pagecache dan menjalankan fungsi-fungsi XFS dibandingkan dengan loop saya untuk terus menulis ulang di tempat dalam buffer yang tetap panas di cache.
Bagaimana itu dilakukan
PRNG yang cepat jelas penting. xorshift128 + dapat di-vektor-kan, sehingga Anda memiliki dua atau empat generator 64-bit secara paralel, dalam elemen-elemen vektor SIMD. Setiap langkah menghasilkan vektor penuh byte acak. ( Implementasi 256b AVX2 di sini dengan Intel intrinsik ). Saya mengambilnya dari Nominal's pilihan xorshift *, karena multiplikasi vektor integer 64-bit hanya mungkin di SSE2 / AVX2 dengan teknik presisi yang diperluas .
Diberikan vektor byte acak, kita dapat memotong setiap elemen 16-bit menjadi beberapa angka desimal. Kami menghasilkan beberapa vektor elemen 16-bit yang masing-masing satu digit ASCII + ruang ASCII . Kami menyimpannya langsung ke buffer output kami.
Versi asli saya hanya digunakan x / 6554
untuk mendapatkan satu digit acak dari setiap elemen uint16_t dari suatu vektor. Itu selalu antara 0 dan 9, inklusif. Itu bias jauh dari 9
, karena (2^16 -1 ) / 6554
hanya 9,99923. (6554 = ceil ((2 ^ 16-1) / 10), yang memastikan bahwa hasil bagi selalu <10.)
x/6554
dapat dihitung dengan satu kalikan dengan konstanta "ajaib" ( titik tetap timbal balik ) dan pergeseran kanan dari hasil setengah tinggi. Ini adalah kasus terbaik untuk pembagian oleh konstanta; beberapa pembagi mengambil lebih banyak operasi, dan divisi yang ditandatangani membutuhkan kerja ekstra. x % 10
memiliki bias yang sama dan tidak semurah untuk menghitung. (Keluaran asm gcc setara dengan x - 10*(x/10)
, yaitu penggandaan ekstra dan kurangi di atas divisi menggunakan invers multiplikatif modular.) Juga, bit xorshift128 + terendah tidak berkualitas tinggi , jadi membagi untuk mengambil entropi dari bit tinggi lebih baik ( untuk kualitas dan kecepatan) daripada modulo untuk mengambil entropi dari bit rendah.
Namun, kita dapat menggunakan lebih banyak entropi di setiap uint16_t dengan melihat angka desimal rendah, seperti digit()
fungsi @ Nominal . Untuk kinerja maksimum, saya memutuskan untuk mengambil 3 angka desimal rendah dan x/6554
, untuk menghemat satu PMULLW dan PSUBW (dan mungkin beberapa MOVDQA) vs. pilihan kualitas yang lebih tinggi dengan mengambil 4 angka desimal rendah. x / 6554 sedikit dipengaruhi oleh rendahnya 3 digit desimal, sehingga ada beberapa korelasi antara digit dari elemen yang sama (8 atau 16 digit pemisahan dalam output ASCII, tergantung pada lebar vektor).
Saya pikir gcc membaginya dengan 100 dan 1000, daripada rantai yang lebih panjang yang secara berturut-turut membaginya dengan 10, jadi mungkin tidak secara signifikan memperpendek panjang rantai ketergantungan yang tidak digerakkan-loop yang menghasilkan 4 hasil dari setiap output PRNG. port0 (vektor multiply dan shift) adalah hambatan karena inversi modular multiplikatif, dan pergeseran dalam xorshift +, jadi pasti berguna untuk menyimpan vektor-multiply.
xorshift + sangat cepat sehingga bahkan hanya menggunakan ~ 3,3 bit keacakan dari setiap 16 (yaitu efisiensi 20%) tidak jauh lebih lambat daripada memotongnya menjadi beberapa angka desimal. Kami hanya memperkirakan distribusi seragam, karena jawaban ini difokuskan pada kecepatan selama kualitasnya tidak terlalu buruk.
Setiap jenis perilaku kondisional yang membuat sejumlah variabel elemen akan membutuhkan lebih banyak pekerjaan. (Tapi mungkin masih bisa dilakukan agak efisien menggunakan teknik pengemasan kiri SIMD . Namun, yang menjadi kurang efisien untuk ukuran elemen kecil; tabel pencarian topeng-acak tidak memungkinkan, dan tidak ada AVX2 lane-crossing shuffle dengan yang lebih kecil dari 32- elemen bit. Sebuah versi 128H PSHUFB mungkin masih dapat menghasilkan topeng dengan cepat dengan BMI2 PEXT / PDEP, seperti yang Anda bisa untuk AVX2 dengan elemen yang lebih besar , tetapi rumit karena bilangan bulat 64-bit hanya menampung 8 byte. Link godbolt pada jawaban itu ada beberapa kode yang mungkin berfungsi untuk jumlah elemen yang lebih tinggi.)
Jika latensi RNG adalah hambatan, kita bisa lebih cepat lagi dengan menjalankan dua vektor generator secara paralel, bergantian mana yang kita gunakan. Compiler masih dapat dengan mudah menyimpan semuanya dalam register dalam satu loop yang tidak terbuka, dan itu memungkinkan dua rantai dependensi berjalan secara paralel.
Dalam versi saat ini, memotong output dari PRNG, kita sebenarnya bottleneck pada throughput port 0, bukan latensi PRNG, jadi tidak perlu untuk itu.
Kode: versi AVX2
Versi lengkap dengan lebih banyak komentar di explorer compiler Godbolt .
Tidak terlalu rapi, maaf saya harus tidur dan ingin memposting ini.
Untuk mendapatkan versi SSE2, s/_mm256/_mm
, s/256/128/
, s/v16u/v8u/
, dan perubahan vector_size(32)
ke 16. Juga mengubah selisih baris dari 4 * 16-4 * 8. (Seperti yang saya katakan, kode berantakan, dan tidak diatur dengan baik untuk mengkompilasi dua versi. Awalnya tidak berencana membuat versi AVX2, tapi kemudian saya benar-benar ingin menguji pada Haswell CPU yang saya punya akses.)
#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);
}
}
Kompilasi dengan gcc, dentang, atau ICC (atau mudah-mudahan kompiler lain yang memahami dialek GNU C C99, dan intrinsik Intel). Ekstensi vektor GNU C sangat mudah untuk mendapatkan kompiler untuk menghasilkan angka ajaib untuk divisi / modulo menggunakan inversi multiplikatif modular, dan sesekali __attribute__
berguna.
Ini bisa ditulis dengan mudah, tetapi akan membutuhkan lebih banyak kode.
Catatan kinerja:
Toko tumpang tindih untuk menyisipkan baris baru memiliki overhead yang signifikan untuk memutuskan di mana menempatkannya (salah duga cabang, dan kemacetan frontend pada Core2), tetapi toko itu sendiri tidak memiliki dampak pada kinerja. Mengomentari hanya itu instruksi toko di asm kompiler (meninggalkan semua percabangan yang sama) meninggalkan kinerja pada Core2 sama sekali tidak berubah, dengan berjalan berulang memberikan waktu yang sama untuk +/- kurang dari 1%. Jadi saya menyimpulkan bahwa buffer toko / cache menanganinya dengan baik.
Namun, menggunakan beberapa jenis jendela putar ascii_digitspace
dengan satu elemen yang memiliki baris baru mungkin lebih cepat, jika kita cukup membuka gulungan sehingga penghitung / percabangan hilang.
Menulis ke / dev / null pada dasarnya adalah no-op, jadi buffer mungkin tetap panas di L2 cache (256kiB per core pada Haswell). Diharapkan speedup sempurna dari 128b vektor ke 256b vektor: tidak ada instruksi tambahan, dan semuanya (termasuk toko) terjadi dengan lebar dua kali lipat. Cabang penyisipan baris baru diambil dua kali lebih sering. Sayangnya saya tidak sempat mengatur Haswell cygwin dengan bagian itu #ifdef
diedit.
2.5GHz * 32B / 13.7GB / s = 5.84 siklus per AVX2-store di Haswell. Itu cukup bagus, tetapi bisa lebih cepat. Mungkin ada beberapa overhead dalam panggilan sistem cygwin daripada yang saya kira. Saya tidak mencoba mengomentari mereka dalam output asm kompiler (yang akan memastikan bahwa tidak ada yang dioptimalkan.)
Cache L1 dapat mempertahankan satu toko 32B per jam, dan L2 tidak bandwidth yang jauh lebih rendah (latensi lebih tinggi, meskipun).
Ketika saya melihat IACA beberapa versi yang lalu (tanpa bercabang untuk baris baru, tetapi hanya mendapatkan satu vektor ASCII per vektor RNG), itu memprediksi sesuatu seperti satu toko vektor 32B per 4 atau 5 jam.
Saya berharap untuk mendapatkan lebih banyak percepatan dari mengekstraksi lebih banyak data dari setiap hasil RNG, berdasarkan pada melihat asm sendiri, mempertimbangkan panduan Agner Fog dan sumber daya pengoptimalan lainnya yang telah saya tambahkan tautan untuk dalam wiki tag SO x86 .)
Kemungkinan akan lebih cepat secara signifikan pada Skylake , di mana vektor integer dan pergeseran dapat berjalan pada port dua kali lebih banyak (p0 / p1) dibandingkan dengan Haswell (hanya p0). xorshift dan ekstraksi digit keduanya menggunakan banyak pergeseran dan penggandaan. ( Pembaruan: Skylake menjalankannya pada IPC 3.02, memberi kami 3,77 siklus per toko AVX2 32-byte , dihitung pada 0,030 detik per iterasi 1GB, menulis /dev/null
di Linux 4,15 pada i7-6700k pada 3,9GHz.
Tidak memerlukan mode 64-bit untuk bekerja dengan baik . Versi SSE2 sama cepatnya ketika dikompilasi -m32
, karena ia tidak membutuhkan register vektor yang sangat banyak, dan semua matematika 64-bit dilakukan dalam vektor, bukan register untuk keperluan umum.
Ini sebenarnya sedikit lebih cepat dalam mode 32-bit pada Core2, karena membandingkan / cabang makro-fusi hanya bekerja dalam mode 32-bit, jadi ada lebih sedikit uops untuk core out-of-order (18,3s (1,85 Instructions Per Clock) vs .16.9s (2.0 IPC)). Ukuran kode yang lebih kecil karena tidak memiliki awalan REX juga membantu decoder Core2.
Juga, beberapa gerakan vektor reg-reg diganti dengan beban, karena tidak semua konstanta memperbaiki dalam vektor regs lagi. Karena memuat throughput dari cache L1 bukan hambatan, ini sebenarnya membantu. (mis. mengalikan dengan vektor konstan set1(10)
: movdqa xmm0, xmm10
/ pmullw xmm0, xmm1
berubah menjadi movdqa xmm0, [constant]
/ pmullw xmm0, xmm1
.) Karena reg-reg MOVDQA membutuhkan port ALU, itu bersaing dengan pekerjaan nyata yang sedang dilakukan, tetapi beban MOVDQA hanya bersaing untuk bandwidth decode front-end. (Memiliki alamat 4-byte di dalam banyak instruksi membatalkan banyak keuntungan dari menyimpan awalan REX.
Saya tidak akan terkejut jika menyimpan ALU MOVDQA uops adalah tempat keuntungan sebenarnya berasal, karena frontend harus mengikuti rata-rata 2,0 IPC dengan cukup baik.
Semua perbedaan ini menghilang di Haswell, di mana semuanya harus dijalankan dari cache yang di-decode, jika bukan buffer loopback. Fusi makro cabang ALU + bekerja di kedua mode sejak Nehalem.