Saya mencoba skrip bash, tetapi butuh waktu terlalu lama untuk membuat file 1 MB sederhana. Saya pikir jawabannya terletak pada menggunakan /dev/random
atau /dev/urandom
, tetapi posting lain di sini hanya menunjukkan bagaimana cara menambahkan semua jenis data ke file menggunakan hal-hal ini, tetapi saya ingin menambahkan hanya angka.
Jadi, adakah perintah yang bisa saya gunakan untuk membuat file acak berukuran 1 GB yang hanya berisi angka antara 0 dan 9?
Sunting: Saya ingin hasilnya seperti ini
0 1 4 7 ..... 9
8 7 5 8 ..... 8
....
....
8 7 5 3 ..... 3
Kisarannya adalah 0 - 9 yang berarti hanya angka 0, 1, 2, 3, 4, 5, 6, 7, 8 dan 9. Juga saya membutuhkannya untuk dipisahkan dengan ruang dan 100 per baris, hingga n
jumlah baris. Ini adalah sesuatu yang saya tidak peduli, saya ingin ukuran akhir saya menjadi 1 GB.
Sunting: Saya menggunakan Ubuntu 16.04 LTS
yes 4 | tr '\n' ' ' | fold -w 200 | head -c1G
Jawaban:
Ini sebagian merupakan jawaban yang tidak jelas, karena judul pertanyaannya.
Ketika Anda mencari "cara tercepat untuk ..." , jawabannya hampir selalu merupakan alat khusus. "Jawaban" ini menunjukkan satu alat seperti itu, supaya Anda bisa bereksperimen.
Ini bukan jawaban yang serius, karena Anda tidak harus melihat ke dalam alat khusus untuk pekerjaan yang hanya Anda lakukan sekali, atau sangat jarang. Anda lihat, Anda akan menghabiskan lebih banyak waktu mencari alat dan belajar tentang itu, daripada benar-benar melakukan hal-hal. Kerang dan utilitas menyukai
bash
danawk
bukan yang tercepat, tetapi Anda biasanya dapat menulis satu baris untuk mencapai pekerjaan itu, menghabiskan hanya beberapa detik. Bahasa scripting yang lebih baik sepertiperl
juga dapat digunakan, meskipun kurva belajar untukperl
curam, dan saya ragu untuk merekomendasikannya untuk tujuan seperti itu, karena saya telah trauma dengan proyek perl yang mengerikan.python
di sisi lain sedikit cacat oleh I / O yang agak lambat; namun ini hanya masalah saat Anda memfilter atau menghasilkan gigabytes data.Bagaimanapun, program contoh C89 berikut (yang menggunakan POSIX.1 hanya untuk jam akurasi yang lebih tinggi jika tersedia) harus mencapai sekitar 100 MB / s tingkat generasi (diuji di Linux pada laptop dengan prosesor Intel i5-4200U, menyalurkan output to
/dev/null
), menggunakan generator nomor pseudo-acak yang cukup bagus. (Keluaran harus lulus semua tes BigCrunch, kecuali tes MatrixRank, karena kode menggunakan xorshift64 * dan metode pengecualian untuk menghindari bias angka.)desimal-digit.c:
Kita dapat membuatnya jauh lebih cepat, jika kita beralih ke buffer garis, dan
fwrite()
itu sekaligus bukannya menghasilkan setiap digit sekaligus. Perhatikan bahwa kami tetap menjaga aliran buffer sepenuhnya, untuk menghindari penulisan sebagian (non-power-of-two) jika output adalah perangkat blok.Catatan: kedua contoh diedit pada 2016-11-18 untuk memastikan distribusi angka yang seragam (nol dikecualikan; lihat misalnya di sini untuk perbandingan dan detail tentang berbagai generator angka pseudo-acak).
Kompilasi menggunakan misalnya
dan secara opsional menginstal seluruh sistem untuk
/usr/bin
menggunakanDibutuhkan jumlah digit per baris, dan jumlah baris. Karena
1000000000 / 100 / 2 = 5000000
(lima juta; total byte dibagi dengan kolom dibagi 2), Anda dapat menggunakanuntuk menghasilkan ukuran gigabyte
digits.txt
seperti yang diinginkan oleh OP.Perhatikan bahwa program itu sendiri ditulis lebih dengan keterbacaan daripada efisiensi dalam pikiran. Maksud saya di sini adalah bukan untuk menunjukkan efisiensi kode - saya akan menggunakan POSIX.1 dan I / O tingkat rendah, daripada antarmuka C generik - tetapi agar Anda dengan mudah melihat keseimbangan seperti apa yang ada dengan upaya yang dihabiskan dalam mengembangkan alat khusus dibandingkan kinerjanya, dibandingkan dengan skrip satu shell atau pendek atau awk.
Menggunakan perpustakaan C GNU, memanggil
fputc()
fungsi untuk setiap output karakter menimbulkan overhead yang sangat kecil (dari panggilan fungsi tidak langsung, atau kondisional -FILE
antarmuka sebenarnya cukup kompleks dan fleksibel, Anda lihat). Pada laptop Intel Core i5-4200U khusus ini, mengalihkan output ke/dev/null
, versi (fputc) pertama membutuhkan waktu sekitar 11 detik, sedangkan versi line-at-a-time hanya membutuhkan waktu 1,3 detik.Kebetulan saya sering menulis program dan generator seperti itu hanya karena saya suka bermain dengan dataset besar. Aku aneh seperti itu. Sebagai contoh, saya pernah menulis sebuah program untuk mencetak semua nilai floating-point IEEE-754 positif yang terbatas ke dalam file teks, dengan presisi yang cukup untuk menghasilkan nilai yang sama persis ketika diuraikan. File tersebut berukuran beberapa gigabytes (mungkin 4G atau lebih); tidak ada banyak positif positif yang terbatas
float
seperti yang diperkirakan orang. Saya menggunakan ini untuk membandingkan implementasi yang membaca dan mengurai data tersebut.Untuk kasus penggunaan normal, seperti yang dimiliki OP, skrip shell dan skrip dan satu-garis adalah pendekatan yang lebih baik. Lebih sedikit waktu yang dihabiskan untuk menyelesaikan tugas keseluruhan. (Kecuali jika mereka memerlukan file yang berbeda setiap hari atau lebih, atau ada banyak orang yang membutuhkan file yang berbeda, di mana - kasus yang jarang terjadi, alat khusus seperti di atas, dapat menjamin upaya yang dihabiskan.)
sumber
mmap()
adalah rute termudah ke kecepatan I / O terbaik - tapi patok sebelum membuat klaim!write()
, biasanya lebih cepat daripadammap()
.fwrite()
tidak jauh lebih lambat. Ya, saya telah membandingkannya (tidak hanya untuk contoh khusus ini);write()
dalam potongan besar (262144, 524288, atau 1048576 byte) cenderung mengungguli metode lain. Versi yangfputc()
diterapkan di pustaka GNU C (yang telah saya benchmark secara luas) lambat karena sejumlah alasan; khususnya, implementasi harus melakukan lompatan kondisional atau panggilan tidak langsung untuk setiap karakter yang ditambahkan; bahwa sedikit overhead yang terjadi begitu sering bertambah./dev/null
. Skriplet Stéphane Chazelas membutuhkan waktu sekitar 52 detik; cuplikan perl (termasukhead
pemfilteran) sekitar 58 detik;shuf
cuplikan Anda (dengan waktu yang tepat; Anda hanya mengukur waktu shuf, dengan asumsi tempel tidak akan memakan waktu lebih lama) membutuhkan waktu sekitar 69 detik. Program C ++ 11 James Hollis ' line-at-a-time membutuhkan waktu 14 detik. Program di atas membutuhkan waktu 10 detik.Ini:
(dengan asumsi
head
implementasi yang mendukung-c
) tampaknya cukup cepat di sistem saya.tr
menerjemahkan seluruh rentang byte (0 hingga 255, 0 hingga 0377 dalam oktal): 25 byte pertama sebagai 0, 25 byte berikutnya sebagai 1 ... kemudian 25 9 sisanya (250 hingga 255) menjadi "x" yang kemudian buang (dengantr -d x
) seperti yang kita inginkan distribusi seragam (dengan asumsi/dev/urandom
memiliki distribusi seragam itu sendiri) dan jadi tidak memberikan bias pada beberapa digit.Itu menghasilkan satu digit untuk 97% dari byte
/dev/urandom
.fold -w 1
menjadikannya satu digit per baris.paste -s
disebut dengan daftar pemisah yang terdiri dari 99 karakter spasi dan satu karakter baris baru, sehingga memiliki 100 digit spasi yang terpisah pada setiap baris.head -c1G
akan mendapatkan GiB pertama (2 30 ) dari itu. Perhatikan bahwa baris terakhir akan terpotong dan tidak direvisi. Anda dapat memotong ke 2 30 -1 dan menambahkan baris baru yang hilang dengan tangan, atau memotong ke 10 9 byte sebagai gantinya yang merupakan 50 juta dari 200 byte baris (head -n 50000000
juga akan membuatnya menjadi perintah standar / portable).Pengaturan waktu ini (diperoleh
zsh
pada sistem quad-core), memberikan indikasi di mana waktu CPU dihabiskan:Yang pertama
tr
adalah leher botol, sebagian besar waktu dihabiskan di kernel (saya kira untuk generasi nomor acak). Waktunya kira-kira sejalan dengan tingkat saya bisa mendapatkan byte dari/dev/uramdom
(sekitar 19MiB / s dan di sini kami menghasilkan 2 byte untuk setiap 0,97 byte / dev / urandom pada tingkat 32MiB / s).fold
tampaknya menghabiskan jumlah waktu CPU (15-an) yang tidak masuk akal hanya untuk memasukkan karakter baris baru setelah setiap byte tetapi itu tidak mempengaruhi waktu keseluruhan karena bekerja pada CPU yang berbeda dalam kasus saya (menambahkan-b
opsi membuatnya sangat sedikit lebih banyak efisien,dd cbs=1 conv=unblock
sepertinya alternatif yang lebih baik).Anda dapat menghapus
head -c1G
dan mencukur beberapa detik dengan menetapkan batas ukuran file (limit filesize 1024m
denganzsh
atauulimit -f "$((1024*1024))"
dengan sebagian besar shell lainnya (termasukzsh
)) sebagai gantinya dalam subkulit.Itu dapat ditingkatkan jika kita mengekstrak 2 digit untuk setiap byte, tetapi kita akan membutuhkan pendekatan yang berbeda untuk itu. Di atas sangat efisien karena
tr
hanya mencari setiap byte dalam array 256 byte. Itu tidak dapat melakukan itu untuk 2 byte pada satu waktu, dan menggunakan hal-hal sepertihexdump -e '1/1 "%02u"'
itu menghitung representasi teks dari byte menggunakan algoritma yang lebih kompleks akan lebih mahal daripada generasi nomor acak itu sendiri. Namun, jika seperti dalam kasus saya, Anda memiliki inti CPU yang memiliki waktu luang, mungkin masih dapat dimatikan beberapa detik:Dengan:
Saya mendapatkan (perhatikan bahwa di sini 1.000.000.000 byte dibandingkan dengan 1.073.741.824):
Lebih banyak waktu CPU secara keseluruhan, tetapi lebih baik didistribusikan di antara 4 core CPU saya, sehingga akhirnya memakan waktu lebih sedikit di dinding. Hambatannya sekarang
hexdump
.Jika kita menggunakan
dd
alih-alih berbasis garisfold
, kita sebenarnya dapat mengurangi jumlah pekerjaan yanghexdump
perlu dilakukan dan meningkatkan keseimbangan kerja antara CPU:(di sini mengasumsikan GNU
dd
untukiflag=fullblock
danstatus=none
) yang memberi:Kembali ke generasi nomor acak menjadi hambatan.
Sekarang, seperti yang ditunjukkan oleh @OleTange, jika Anda memiliki
openssl
utilitas, Anda bisa menggunakannya untuk mendapatkan yang lebih cepat (terutama pada prosesor yang memiliki instruksi AES) generator byte acak-acak.pada sistem saya menghabiskan 15 kali lebih banyak byte per detik daripada
/dev/urandom
. (Saya tidak bisa mengomentari bagaimana perbandingannya dalam hal sumber acak yang aman secara kriptografis jika itu berlaku untuk use case Anda).Sekarang berikan:
kembali
hexdump
menjadi hambatan.Karena saya masih memiliki CPU yang tersisa, saya dapat menjalankan 3 dari mereka
hexdump
secara paralel.(
<&3
diperlukan untuk shell selain darizsh
itu perintah dekat 'stdin on / dev / null saat dijalankan di latar belakang).Sekarang turun menjadi 6,2 detik dan CPU saya hampir sepenuhnya digunakan.
sumber
perl
varian yang secara signifikan lebih lambat. Saya tidak bisa mendapatkan 2 digit per byte dengan pendekatan tr | fold | paste.bc
(kemudian drop 0, 1, atau 2 digit paling signifikan).Jika Anda memiliki
shuf
(coreutils GNU terbaru tidak) Anda dapat melakukan ini:Pada VM saya, ini sekarang sedikit lebih lambat daripada jawaban Stéphane sekitar 3: 4 faktor.
sumber
shuf
di PC perusahaan saya tidak punya-r
,fmt
tidak punya-g
terlalupaste
/printf
trik - terima kasih. Jawaban Anda sekarang tampaknya lebih cepat.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 kewc -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.
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 iniwc
). 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()
bukanfwrite()
, 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.
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.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%fwrite
piping kewc -c
: real = 3.980s pengguna = 3.176s sys = 2.080sclang++-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.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.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.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.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).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 dari9
, 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 denganx - 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 danx/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 perubahanvector_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.)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 menjadimovdqa 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.
sumber
Inilah solusi yang saya harap mudah dimengerti:
od
menciptakan aliran seragam dari angka heksadesimal/dev/random
.tr
menghilangkan huruf, hanya menyimpan0-9
digitfold
memastikan ada 100 digit per barisawk
menyisipkan spasi di dalam garishead
memotong input ke 1 gigabytesumber
Anda dapat menggunakan
jot
perintah untuk ini:sumber
fmt
tidak memiliki opsi lebar tujuan. Bagaimanapun, itu akan tepat karena semua digit hanya mengambil satu kolom!fmt
versi saya adalahfmt (GNU coreutils) 8.25
(Ubuntu 16.04)536870912
Ini mirip dengan metode Stéphane Chazelas, namun saya membaca 64 bit sekaligus untuk meningkatkan kinerja. Distribusi masih seragam tetapi sekarang Anda mendapatkan 19 digit untuk setiap 8 byte, bukan hanya 8 dalam kasus terbaik seperti sebelumnya
Pada platform 32-bit, 9 digit akan dibaca setiap kali alih-alih 19.
sumber
perl
tidak dikompilasi dengan dukungan quad.next if $n >= 1000000000; $s = sprintf("%09u", $n);
untuk mendapatkan hanya 9 digit$n = unpack("Q")
jika quad tidak didukung.BEGIN{$/=\4; $,=" "} $n = unpack("L");
juga<16e18
dan bagi dengan 16, Anda mendapatkan 18 digit 86,7% untuk 1,95 dpB. Dengan 32bit,<4e9 /4
dapatkan 9 digit 93,1% untuk 2,10 dpB. Tetapi 5 byte (sebagai hex (H10))<1e12
memberikan 12 digit 90,9% untuk 2,18 dpB, atau membelah hex menjadi dua dan melakukan setiap setengahnya<1e6
memberikan 6 digit 95,4% untuk 2,29 dpB; ini mendekati batas log_10 (256) = 2.41.Saya agak setuju dengan Nominal Animal dalam menggunakan bahasa pemrograman yang dikompilasi jika Anda membutuhkan kecepatan. Namun, Anda tidak perlu menulis kode RNG Anda sendiri dalam C. C ++ 11 menawarkan Mersenne Twister yang sangat baik sebagai bagian dari perpustakaan standarnya.
Kode di atas cukup sederhana dan memakan waktu sekitar satu menit ketika saya mengirim output ke file. Kita bisa bergerak jauh lebih cepat dengan membuat string yang cukup besar untuk 100 digit dan meretasnya. Ini memungkinkan kita untuk memanggil semua saluran daripada setiap digit.
Kode ini membutuhkan mesin saya sekitar enam detik. Ingat itu adalah output standar, jadi pipa itu ke file.
Saya punya beberapa penafian. Pertama, saya menulis ini di PC Windows. Saya pikir semua perpustakaan ada di Linux, tetapi jika saya salah, pastikan untuk menunjukkannya.
Juga, ini sebenarnya menghasilkan setengah miliar digit ruang yang terpisah, yang secara teknis satu gigabyte tetapi mungkin tidak persis seperti yang Anda inginkan. Ini menghasilkan 5 juta baris, 100 digit per baris. Jika perbedaannya penting, Anda dapat menambah jumlah garis. Pada kotak Windows saya, file tersebut tampaknya sedikit lebih besar dari 10 ^ 9 byte, yang saya pikir ada hubungannya dengan karakter baris baru tambahan.
sumber
/dev/null
yang jauh lebih cepat daripada menulis ke file nyatawrite()
pemanggilan sistem besar adalah memcpy ke dalam pagecache, yang memblokir hanya jika kernel memutuskan untuk melakukan itu alih-alih mengalokasikan lebih banyak ruang buffer. Program ini hanya akan menghambat bottleneck pada disk I / O ketika memori sedang kencang, atau jika Anda telah menggunakan O_DIRECT untuk mem-bypass pagecache. Jika Anda beradawrite()
di potongan yang lebih kecil dari ukuran cache, semoga data Anda hanya masuk ke memori utama satu kali, dan buffer yang ditulis ulang di tempat tetap panas di L2 atau L3 cache.Itu tergantung pada definisi Anda tentang "acak". Jika maksud Anda adalah cryptographically random, Anda hanya perlu mendapatkan perpustakaan yang bagus dan menggigit peluru, tunggu sampai berjalan.
Jika Anda hanya perlu sesuatu yang terlihat cukup acak, berikut ini adalah cara mudah:
Mungkin butuh satu jam untuk berjalan di mesin yang lambat; cukup cepat dan cukup acak untuk sebagian besar keperluan.
sumber
/dev/urandom
cenderung lebih baik daripadagzip
, baik dalam kecepatan maupun keacakan.Get a file that is several Gb long
Anda memerlukan file ** minimal 8Gb` untuk mendapatkan file 1GBsumber
cat file | tr
ketika Anda bisatr <file
. IIRC, kamu bahkan bisa<file tr
. Saya pikir Anda baru saja berbicara tentang skrip shell ini terlihat kikuk dan lambat, sepertidu | awk
setelah setiap baris untuk memeriksa ukuran, dan membuka kembali file untuk menambahkan setiap baris alih-alih mengarahkan ulang di luar loop.cat /dev/urandom | busy-cmd
adalah salah satu kasus langka di mana itu bisa masuk akal karena dapat membagi generasi acak dan cmd sibuk antara prosesor.od
Misalnya bukan untuk tr tetapi membuat perbedaan untuk Sam misalnya.