Saya membuat kelas yang dipanggil QuickRandom
, dan tugasnya adalah menghasilkan angka acak dengan cepat. Ini sangat sederhana: ambil saja nilai lama, kalikan dengan double
, dan ambil bagian desimal.
Inilah QuickRandom
kelas saya secara keseluruhan:
public class QuickRandom {
private double prevNum;
private double magicNumber;
public QuickRandom(double seed1, double seed2) {
if (seed1 >= 1 || seed1 < 0) throw new IllegalArgumentException("Seed 1 must be >= 0 and < 1, not " + seed1);
prevNum = seed1;
if (seed2 <= 1 || seed2 > 10) throw new IllegalArgumentException("Seed 2 must be > 1 and <= 10, not " + seed2);
magicNumber = seed2;
}
public QuickRandom() {
this(Math.random(), Math.random() * 10);
}
public double random() {
return prevNum = (prevNum*magicNumber)%1;
}
}
Dan ini kode yang saya tulis untuk mengujinya:
public static void main(String[] args) {
QuickRandom qr = new QuickRandom();
/*for (int i = 0; i < 20; i ++) {
System.out.println(qr.random());
}*/
//Warm up
for (int i = 0; i < 10000000; i ++) {
Math.random();
qr.random();
System.nanoTime();
}
long oldTime;
oldTime = System.nanoTime();
for (int i = 0; i < 100000000; i ++) {
Math.random();
}
System.out.println(System.nanoTime() - oldTime);
oldTime = System.nanoTime();
for (int i = 0; i < 100000000; i ++) {
qr.random();
}
System.out.println(System.nanoTime() - oldTime);
}
Ini adalah algoritma yang sangat sederhana yang hanya mengalikan ganda sebelumnya dengan "angka ajaib" ganda. Saya melemparkannya bersama dengan cukup cepat, jadi saya mungkin bisa membuatnya lebih baik, tetapi anehnya, itu sepertinya berfungsi dengan baik.
Ini adalah contoh keluaran dari baris komentar dalam main
metode:
0.612201846732229
0.5823974655091941
0.31062451498865684
0.8324473610354004
0.5907187526770246
0.38650264675748947
0.5243464344127049
0.7812828761272188
0.12417247811074805
0.1322738256858378
0.20614642573072284
0.8797579436677381
0.022122999476108518
0.2017298328387873
0.8394849894162446
0.6548917685640614
0.971667953190428
0.8602096647696964
0.8438709031160894
0.694884972852229
Hm Cukup acak. Bahkan, itu akan bekerja untuk generator angka acak dalam game.
Berikut adalah contoh output dari bagian yang tidak dikomentari:
5456313909
1427223941
Wow! Ia melakukan hampir 4 kali lebih cepat daripada Math.random
.
Saya ingat pernah membaca di suatu tempat yang Math.random
digunakan System.nanoTime()
dan banyak modulus dan hal-hal divisi gila. Apakah itu benar-benar perlu? Algoritma saya bekerja jauh lebih cepat dan sepertinya cukup acak.
Saya punya dua pertanyaan:
- Apakah algoritma saya "cukup baik" (untuk, mengatakan, permainan, di mana benar-benar nomor acak yang tidak terlalu penting)?
- Mengapa
Math.random
melakukan begitu banyak ketika tampaknya hanya perkalian sederhana dan memotong desimal sudah cukup?
sumber
new QuickRandom(0,5)
ataunew QuickRandom(.5, 2)
. Keduanya akan menghasilkan 0 untuk nomor Anda berulang kali.Jawaban:
QuickRandom
Implementasi Anda belum benar-benar distribusi yang seragam. Frekuensi umumnya lebih tinggi pada nilai yang lebih rendah sementaraMath.random()
memiliki distribusi yang lebih seragam. Inilah SSCCE yang menunjukkan bahwa:Hasil rata-rata terlihat seperti ini:
Jika Anda mengulang tes, Anda akan melihat bahwa distribusi QR sangat bervariasi, tergantung pada benih awal, sementara distribusi MR stabil. Kadang-kadang mencapai distribusi seragam yang diinginkan, tetapi lebih sering tidak. Inilah salah satu contoh yang lebih ekstrem, bahkan di luar batas grafik:
sumber
QuickRandom
. Kadang-kadang, itu dekat dengan seragam, kadang-kadang jauh lebih buruk dari ini.Apa yang Anda gambarkan adalah jenis generator acak yang disebut generator kongruensial linier . Generator bekerja sebagai berikut:
Generator ini memiliki banyak sifat yang bagus, tetapi memiliki masalah signifikan sebagai sumber acak yang baik. Artikel Wikipedia yang tertaut di atas menjelaskan beberapa kekuatan dan kelemahan. Singkatnya, jika Anda membutuhkan nilai acak yang baik, ini mungkin bukan pendekatan yang sangat baik.
Semoga ini membantu!
sumber
Fungsi angka acak Anda buruk, karena memiliki status internal terlalu sedikit - angka yang dihasilkan oleh fungsi pada setiap langkah yang diberikan sepenuhnya tergantung pada angka sebelumnya. Misalnya, jika kita asumsikan
magicNumber
2 (dengan contoh), maka urutannya:sangat dicerminkan oleh urutan yang sama:
Dalam banyak kasus, ini akan menghasilkan korelasi yang nyata dalam permainan Anda - misalnya, jika Anda membuat panggilan berturut-turut ke fungsi Anda untuk menghasilkan koordinat X dan Y untuk objek, objek akan membentuk pola diagonal yang jelas.
Kecuali Anda memiliki alasan yang kuat untuk percaya bahwa penghasil angka acak memperlambat aplikasi Anda (dan ini SANGAT tidak mungkin), tidak ada alasan bagus untuk mencoba dan menulis sendiri.
sumber
Masalah sebenarnya dengan hal ini adalah bahwa histogram outputnya tergantung pada seed awal yang jauh ke banyak - sebagian besar waktu akan berakhir dengan output yang seragam dekat tetapi banyak waktu akan memiliki output yang jelas tidak seragam.
Terinspirasi oleh artikel ini tentang betapa buruknya
rand()
fungsi php , saya membuat beberapa gambar matriks acak menggunakanQuickRandom
danSystem.Random
. Proses ini menunjukkan bagaimana kadang-kadang benih dapat memiliki efek buruk (dalam hal ini mendukung angka yang lebih rendah) di mana sepertiSystem.Random
cukup seragam.QuickRandom
System.Random
Lebih buruk lagi
Jika kami menginisialisasi
QuickRandom
saatnew QuickRandom(0.01, 1.03)
kami mendapatkan gambar ini:Kode
sumber
magicNumber
perkalian menghasilkan angka yang mirip denganprevNum
, yang menunjukkan kurangnya keacakan. Jika kita menggunakan benihnew QuickRandom(0.01, 1.03)
maka kita mendapatkan ini i.imgur.com/Q1Yunbe.png !System.Drawing.Bitmap
.Satu masalah dengan generator nomor acak Anda adalah bahwa tidak ada 'keadaan tersembunyi' - jika saya tahu nomor acak apa yang Anda kembalikan pada panggilan terakhir, saya tahu setiap nomor acak tunggal yang akan Anda kirim sampai akhir waktu, karena hanya ada satu mungkin hasil selanjutnya, dan seterusnya dan seterusnya.
Hal lain yang perlu dipertimbangkan adalah 'periode' generator nomor acak Anda. Jelas dengan ukuran keadaan terbatas, sama dengan bagian mantissa dari suatu ganda, itu hanya akan dapat mengembalikan paling banyak 2 ^ 52 nilai sebelum perulangan. Tapi itu dalam kasus terbaik - dapatkah Anda membuktikan bahwa tidak ada loop periode 1, 2, 3, 4 ...? Jika ada, RNG Anda akan memiliki perilaku yang buruk dan merosot dalam kasus-kasus itu.
Selain itu, akankah pembangkitan angka acak Anda memiliki distribusi yang seragam untuk semua titik awal? Jika tidak, maka RNG Anda akan menjadi bias - atau lebih buruk, bias dengan cara yang berbeda tergantung pada benih awal.
Jika Anda bisa menjawab semua pertanyaan ini, luar biasa. Jika Anda tidak bisa, maka Anda tahu mengapa kebanyakan orang tidak menemukan kembali roda dan menggunakan generator nomor acak yang terbukti;)
(Omong-omong, pepatah yang baik adalah: Kode tercepat adalah kode yang tidak berjalan. Anda bisa membuat acak tercepat () di dunia, tetapi tidak baik jika tidak terlalu acak)
sumber
0 -> 0
. Tergantung pada benih, mungkin ada banyak lainnya. (Misalnya, dengan benih 3.0,0.5 -> 0.5
,0.25 -> 0.75 -> 0.25
,0.2 -> 0.6 -> 0.8 -> 0.4 -> 0.2
, dll)Satu tes umum yang selalu saya lakukan ketika mengembangkan PRNG adalah untuk:
Ini biarkan saya dengan cepat beralih pada ide-ide yang "cukup baik" PRNG untuk urutan sekitar 1 hingga 20 megabyte. Ini juga memberikan gambar top down yang lebih baik daripada hanya memeriksanya dengan mata, karena setiap PRNG "cukup baik" dengan setengah kata negara dapat dengan cepat melebihi kemampuan mata Anda untuk melihat titik siklus.
Jika saya benar-benar pilih-pilih, saya mungkin mengambil algoritma yang baik dan menjalankan tes DIEHARD / NIST pada mereka, untuk mendapatkan lebih banyak wawasan, dan kemudian kembali dan tweak lagi.
Keuntungan dari tes kompresi, yang bertentangan dengan analisis frekuensi adalah bahwa, pada dasarnya mudah untuk membangun distribusi yang baik: cukup output blok panjang 256 yang berisi semua karakter nilai 0 - 255, dan lakukan ini 100.000 kali. Tetapi urutan ini memiliki siklus panjang 256.
Distribusi miring, bahkan dengan margin kecil, harus diambil oleh algoritma kompresi, terutama jika Anda memberikannya cukup (katakanlah 1 megabyte) dari urutan untuk bekerja dengannya. Jika beberapa karakter, atau bigrams, atau n-gram lebih sering terjadi, suatu algoritma kompresi dapat menyandikan kemiringan distribusi ini ke kode-kode yang mendukung kejadian yang sering terjadi dengan kata-kata kode yang lebih pendek, dan Anda mendapatkan delta kompresi.
Karena sebagian besar algoritma kompresi cepat, dan mereka tidak memerlukan implementasi (seperti OS hanya berbaring di sekitar), tes kompresi adalah yang sangat berguna untuk dengan cepat lulus / gagal untuk PRNG yang mungkin Anda kembangkan.
Semoga berhasil dengan eksperimen Anda!
Oh, saya melakukan tes ini pada rng yang Anda miliki di atas, menggunakan mod kecil kode Anda berikut:
Hasilnya adalah:
Saya akan menganggap PRNG bagus jika file output tidak dapat dikompresi sama sekali. Sejujurnya, saya tidak berpikir PRNG Anda akan melakukannya dengan baik, hanya 16% pada ~ 20 Meg cukup mengesankan untuk konstruksi yang sederhana. Tapi saya masih menganggapnya gagal.
sumber
Generator acak tercepat yang dapat Anda terapkan adalah ini:
XD, bercanda terpisah, selain semua yang dikatakan di sini, saya ingin berkontribusi mengutip bahwa pengujian urutan acak "adalah tugas yang sulit" [1], dan ada beberapa tes yang memeriksa properti tertentu dari nomor pseudo-acak, Anda dapat menemukan banyak dari mereka di sini: http://www.random.org/analysis/#2005
Salah satu cara sederhana untuk mengevaluasi "kualitas" generator acak adalah uji Chi Square lama.
Mengutip [1]
Menggunakan teori ini dan kode berikut:
Saya mendapat hasil sebagai berikut:
Yang, untuk QuickRandom, jauh dari r (di luar
r ± 2 * sqrt(r)
)Yang mengatakan, QuickRandom bisa cepat tetapi (seperti yang dinyatakan dalam jawaban lain) tidak baik sebagai penghasil angka acak
[1] SEDGEWICK ROBERT, Algoritma di C , Addinson Wesley Publishing Company, 1990, halaman 516 hingga 518
sumber
int[]
diinisialisasi ke nol, jadi tidak perlu untuk bagian ini. Casting untuk mengapung tidak ada gunanya saat Anda bekerja dg ganda. Terakhir: memanggil metode nama random1 dan random2 cukup lucu.Saya mengumpulkan mock-up cepat dari algoritma Anda dalam JavaScript untuk mengevaluasi hasilnya. Ini menghasilkan 100.000 bilangan bulat acak dari 0 - 99 dan melacak contoh setiap bilangan bulat.
Hal pertama yang saya perhatikan adalah bahwa Anda lebih mungkin mendapatkan angka rendah daripada angka tinggi. Anda paling sering melihat ini ketika
seed1
sedang tinggi danseed2
rendah. Dalam beberapa contoh, saya hanya mendapatkan 3 angka.Paling-paling, algoritme Anda perlu disempurnakan.
sumber
Jika
Math.Random()
fungsi memanggil sistem operasi untuk mendapatkan waktu, maka Anda tidak dapat membandingkannya dengan fungsi Anda. Fungsi Anda adalah PRNG, sedangkan fungsi itu berjuang untuk angka acak nyata. Apel dan jeruk.PRNG Anda mungkin cepat, tetapi tidak memiliki informasi status yang cukup untuk mencapai periode yang lama sebelum diulang (dan logikanya tidak cukup canggih bahkan untuk mencapai periode yang mungkin dengan informasi negara sebanyak itu).
Periode adalah panjang urutan sebelum PRNG Anda mulai terulang. Ini terjadi segera setelah mesin PRNG membuat transisi keadaan ke keadaan yang identik dengan beberapa keadaan masa lalu. Dari sana, itu akan mengulangi transisi yang dimulai di negara itu. Masalah lain dengan PRNG adalah jumlah sekuens unik yang rendah, serta merosotnya konvergensi pada sekuens tertentu yang berulang. Bisa juga ada pola yang tidak diinginkan. Sebagai contoh, misalkan PRNG terlihat cukup acak ketika angka-angka dicetak dalam desimal, tetapi pemeriksaan nilai-nilai dalam biner menunjukkan bahwa bit 4 hanya beralih antara 0 dan 1 pada setiap panggilan. Ups!
Lihatlah Mersenne Twister dan algoritma lainnya. Ada beberapa cara untuk mencapai keseimbangan antara panjang periode dan siklus CPU. Salah satu pendekatan dasar (digunakan dalam Mersenne Twister) adalah untuk berputar-putar dalam vektor negara. Dengan kata lain, ketika angka sedang dihasilkan, itu tidak didasarkan pada seluruh negara, hanya pada beberapa kata dari subjek array negara untuk operasi bit sedikit. Tetapi pada setiap langkah, algoritme juga bergerak dalam array, mengacak konten sedikit demi sedikit.
sumber
/dev/random
adalah sumber keacakan nyata yang diperoleh dari driver perangkat, dan bukan PRNG. Itu blok ketika bit tidak cukup tersedia. Perangkat saudara/dev/urandom
juga tidak memblokir, tetapi masih bukan PRNG karena diperbarui dengan bit acak ketika mereka tersedia.nanoTime()
+ counter / hash digunakan untuk seed defaultjava.util.Random
oracle / OpenJDK. Itu hanya untuk benih maka itu adalah LCG standar. Akibatnya, generator OP mengambil 2 angka acak untuk seed, yang ok - jadi tidak ada bedanyajava.util.Random
.System.currentTimeMillis()
adalah unggulan default di JDK1.4-Ada banyak, banyak generator angka acak pseudo di luar sana. Misalnya milik Knuth ranah , twister Mersenne , atau cari generator LFSR. "Algoritma seminarial" monumental Knuth menganalisa area tersebut, dan mengusulkan beberapa generator kongruensi linear (mudah diterapkan, cepat).
Tapi saya sarankan Anda tetap berpegang pada
java.util.Random
atauMath.random
, mereka cepat dan setidaknya OK untuk penggunaan sesekali (yaitu, game dan semacamnya). Jika Anda hanya paranoid pada distribusi (beberapa program Monte Carlo, atau algoritma genetika), periksa implementasinya (sumber tersedia di suatu tempat), dan seed mereka dengan beberapa nomor yang benar-benar acak, baik dari sistem operasi Anda atau dari random.org . Jika ini diperlukan untuk beberapa aplikasi di mana keamanan sangat penting, Anda harus menggali sendiri. Dan seperti dalam kasus itu Anda tidak harus percaya apa kotak berwarna dengan semburan bit hilang di sini, saya akan diam sekarang.sumber
Sangat tidak mungkin bahwa kinerja pembuatan angka acak akan menjadi masalah untuk setiap kasus penggunaan yang Anda buat kecuali mengakses satu
Random
instance dari beberapa utas (karenaRandom
memang demikiansynchronized
).Namun, jika itu benar - benar terjadi dan Anda membutuhkan banyak angka acak dengan cepat, solusi Anda terlalu tidak dapat diandalkan. Terkadang memberikan hasil yang baik, terkadang memberikan hasil yang mengerikan (berdasarkan pengaturan awal).
Jika Anda ingin angka yang sama dengan yang diberikan
Random
kelas, hanya lebih cepat, Anda bisa menghilangkan sinkronisasi di sana:Saya hanya mengambil
java.util.Random
kode dan menghapus sinkronisasi yang menghasilkan dua kali kinerja dibandingkan dengan yang asli pada Oracle HotSpot JVM 7u9 saya. Masih lebih lambat dari AndaQuickRandom
, tetapi memberikan hasil yang jauh lebih konsisten. Tepatnya, untuk nilai yang samaseed
dan aplikasi berulir tunggal, ini memberikan angka pseudo-acak yang sama denganRandom
kelas aslinya .Kode ini didasarkan pada arus
java.util.Random
di OpenJDK 7u yang dilisensikan di bawah GNU GPL v2 .EDIT 10 bulan kemudian:
Saya baru saja menemukan bahwa Anda bahkan tidak perlu menggunakan kode saya di atas untuk mendapatkan
Random
contoh yang tidak disinkronkan . Ada satu di JDK juga!Lihatlah
ThreadLocalRandom
kelas Java 7 . Kode di dalamnya hampir identik dengan kode saya di atas. Kelas ini hanyaRandom
versi yang diisolasi secara lokal yang cocok untuk menghasilkan angka acak dengan cepat. Satu-satunya downside yang dapat saya pikirkan adalah bahwa Anda tidak dapat mengaturseed
secara manual.Contoh penggunaan:
sumber
:)
Itu menarik, terima kasih!'Acak' lebih dari sekedar tentang mendapatkan angka .... apa yang Anda miliki adalah pseudo-acak
Jika pseudo-random cukup baik untuk keperluan Anda, maka tentu saja, itu jauh lebih cepat (dan XOR + Bitshift akan lebih cepat daripada yang Anda miliki)
Rolf
Edit:
Oke, setelah terlalu tergesa-gesa dalam jawaban ini, izinkan saya menjawab alasan sebenarnya mengapa kode Anda lebih cepat:
Dari JavaDoc untuk Math.Random ()
Ini mungkin mengapa kode Anda lebih cepat.
sumber
Math.random()
lebih lambat. Entah harus menyinkronkan atau membuat yang baruRandom
setiap kali, dan tidak ada yang sangat menarik performancewise. Jika saya peduli dengan kinerja, saya akan membuat sendirinew Random
dan hanya menggunakannya. : Pjava.util.Random tidak jauh berbeda, LCG dasar yang dijelaskan oleh Knuth. Namun memiliki 2 keunggulan / perbedaan utama:
Di bawah ini adalah rutin utama yang menghasilkan bilangan bulat 'acak' di java.util.Random.
Jika Anda menghapus AtomicLong dan sate yang dirahasiakan (yaitu menggunakan semua bit dari
long
), Anda akan mendapatkan lebih banyak kinerja daripada multiplikasi ganda / modulo.Catatan terakhir:
Math.random
tidak boleh digunakan untuk apa pun kecuali tes sederhana, itu rawan pertengkaran dan jika Anda bahkan memiliki beberapa utas menyebutnya bersamaan kinerja menurun. Satu fitur historis yang sedikit diketahui dari itu adalah pengenalan CAS di java - untuk mengalahkan tolok ukur yang terkenal (pertama oleh IBM melalui intrinsik dan kemudian Sun membuat "CAS dari Jawa")sumber
Ini adalah fungsi acak yang saya gunakan untuk game saya. Ini cukup cepat, dan memiliki distribusi yang cukup (cukup).
sumber
volatile
, kompiler bebas untuk menghilangkan (atau memperkenalkan) variabel lokal sesuai keinginan.