Berapa banyak yang dibaca dari ThreadLocal
variabel lebih lambat daripada dari bidang biasa?
Lebih konkretnya apakah pembuatan objek sederhana lebih cepat atau lebih lambat daripada akses ke ThreadLocal
variabel?
Saya berasumsi bahwa ini cukup cepat sehingga memiliki ThreadLocal<MessageDigest>
instance jauh lebih cepat daripada membuat instance MessageDigest
setiap saat. Tetapi apakah itu juga berlaku untuk byte [10] atau byte [1000] misalnya?
Edit: Pertanyaan apa yang sebenarnya terjadi saat panggilan ThreadLocal
get? Jika itu hanya sebuah bidang, seperti bidang lainnya, maka jawabannya adalah "selalu tercepat", bukan?
Thread
s berisi hashmap (tidak tersinkronisasi) di mana kuncinya adalahThreadLocal
objek saat iniJawaban:
Menjalankan tolok ukur yang tidak dipublikasikan,
ThreadLocal.get
membutuhkan sekitar 35 siklus per iterasi di mesin saya. Tidak banyak. Dalam implementasi Sun, peta hash probing linier kustom diThread
petaThreadLocal
s ke nilai. Karena hanya dapat diakses oleh satu utas, itu bisa sangat cepat.Alokasi objek kecil mengambil jumlah siklus yang sama, meskipun karena kehabisan cache Anda mungkin mendapatkan angka yang lebih rendah dalam loop yang ketat.
Konstruksi
MessageDigest
akan relatif mahal. Ini memiliki jumlah yang adil dan konstruksi melaluiProvider
mekanisme SPI. Anda mungkin dapat mengoptimalkan, misalnya, menggandakan atau menyediakanProvider
.Hanya karena mungkin lebih cepat untuk menyimpan dalam cache
ThreadLocal
daripada membuat tidak selalu berarti bahwa kinerja sistem akan meningkat. Anda akan memiliki biaya tambahan terkait GC yang memperlambat semuanya.Kecuali jika aplikasi Anda sangat banyak digunakan,
MessageDigest
Anda mungkin ingin mempertimbangkan untuk menggunakan cache aman-thread konvensional.sumber
new org.bouncycastle.crypto.digests.SHA1Digest()
. Saya yakin tidak ada cache yang bisa mengalahkannya.Pada tahun 2009, beberapa JVMs diimplementasikan
ThreadLocal
menggunakan unsynchronisedHashMap
diThread.currentThread()
objek. Ini membuatnya sangat cepat (meskipun tidak secepat menggunakan akses lapangan biasa, tentu saja), serta memastikan bahwaThreadLocal
objek dirapikan saatThread
mati. Memperbarui jawaban ini pada tahun 2016, tampaknya sebagian besar (semua?) JVM yang lebih baru menggunakan aThreadLocalMap
with linear probing. Saya tidak yakin tentang kinerja mereka - tetapi saya tidak dapat membayangkan ini jauh lebih buruk daripada penerapan sebelumnya.Tentu saja, hari
new Object()
ini juga sangat cepat, dan para pemulung juga sangat pandai mengambil kembali benda-benda yang berumur pendek.Kecuali jika Anda yakin bahwa pembuatan objek akan mahal, atau Anda perlu mempertahankan beberapa status berdasarkan utas demi utas, lebih baik Anda menggunakan solusi alokasi yang lebih sederhana saat diperlukan, dan hanya beralih ke
ThreadLocal
implementasi saat profiler memberi tahu Anda bahwa Anda perlu.sumber
Pertanyaan bagus, saya sudah menanyakan hal itu pada diri saya akhir-akhir ini. Untuk memberi Anda angka pasti, tolok ukur di bawah ini (di Scala, dikompilasi menjadi bytecode yang hampir sama dengan kode Java yang setara):
var cnt: String = "" val tlocal = new java.lang.ThreadLocal[String] { override def initialValue = "" } def loop_heap_write = { var i = 0 val until = totalwork / threadnum while (i < until) { if (cnt ne "") cnt = "!" i += 1 } cnt } def threadlocal = { var i = 0 val until = totalwork / threadnum while (i < until) { if (tlocal.get eq null) i = until + i + 1 i += 1 } if (i > until) println("thread local value was null " + i) }
tersedia di sini , dilakukan pada AMD 4x 2.8 GHz dual-core dan quad-core i7 dengan hyperthreading (2.67 GHz).
Ini angkanya:
i7
Spesifikasi: Intel i7 2x quad-core @ 2,67 GHz Tes: scala.threads.ParallelTests
Nama tes: loop_heap_read
Nomor benang .: 1 Total pengujian: 200
Waktu berjalan: (menunjukkan 5 terakhir) 9.0069 9.0036 9.0017 9.0084 9.0074 (rata-rata = 9.1034 mnt = 8.9986 maks = 21.0306)
Nomor benang .: 2 Total pengujian: 200
Waktu berjalan: (menunjukkan 5 terakhir) 4,5563 4,7128 4,5663 4,5617 4,5724 (rata-rata = 4,6337 menit = 4,5509 maks = 13,9476)
Nomor benang .: 4 Total pengujian: 200
Waktu berjalan: (menampilkan 5 terakhir) 2.3946 2.3979 2.3934 2.3937 2.3964 (rata-rata = 2.5113 menit = 2.3884 max = 13.5496)
Nomor benang .: 8 Total pengujian: 200
Waktu berjalan: (menunjukkan 5 terakhir) 2.4479 2.4362 2.4323 2.4472 2.4383 (rata-rata = 2.5562 menit = 2.4166 max = 10.3726)
Nama tes: threadlocal
Nomor benang .: 1 Total pengujian: 200
Waktu berjalan: (menampilkan 5 terakhir) 91.1741 90.8978 90.6181 90.6200 90.6113 (rata-rata = 91.0291 menit = 90.6000 maks = 129.7501)
Nomor benang .: 2 Total pengujian: 200
Waktu berjalan: (menampilkan 5 terakhir) 45,3838 45,3858 45,6676 45,3772 45,3839 (rata-rata = 46,0555 menit = 45,3726 maks = 90,7108)
Nomor benang .: 4 Total pengujian: 200
Waktu berjalan: (menunjukkan 5 terakhir) 22.8118 22.8135 59.1753 22.8229 22.8172 (rata-rata = 23.9752 menit = 22.7951 maks = 59.1753)
Nomor benang .: 8 Total pengujian: 200
Waktu berjalan: (menampilkan 5 terakhir) 22.2965 22.2415 22.3438 22.3109 22.4460 (rata-rata = 23.2676 menit = 22.2346 max = 50.3583)
AMD
Spesifikasi: AMD 8220 4x dual-core @ 2,8 GHz Test: scala.threads.ParallelTests
Nama tes: loop_heap_read
Pekerjaan total: 20000000 Jumlah benang .: 1 Tes total: 200
Waktu berjalan: (menunjukkan 5 terakhir) 12,625 12,631 12,634 12,632 12,628 (rata-rata = 12,7333 menit = 12,619 maks = 26,698)
Nama tes: loop_heap_read Total pekerjaan: 20000000
Waktu berjalan: (menampilkan 5 terakhir) 6.412 6.424 6.408 6.397 6.43 (rata-rata = 6.5367 min = 6.393 max = 19.716)
Nomor benang .: 4 Total pengujian: 200
Waktu berjalan: (menunjukkan 5 terakhir) 3.385 4.298 9.7 6.535 3.385 (rata-rata = 5.6079 min = 3.354 max = 21.603)
Nomor benang .: 8 Total pengujian: 200
Waktu berjalan: (menunjukkan 5 terakhir) 5.389 5.795 10.818 3.823 3.824 (rata-rata = 5.5810 min = 2.405 max = 19.755)
Nama tes: threadlocal
Nomor benang .: 1 Total pengujian: 200
Waktu berjalan: (menampilkan 5 terakhir) 200.217 207.335 200.241 207.342 200.23 (rata-rata = 202.2424 menit = 200.184 max = 245.369)
Nomor benang .: 2 Total pengujian: 200
Waktu berjalan: (menunjukkan 5 terakhir) 100.208 100.199 100.211 103.781 100.215 (rata-rata = 102.2238 menit = 100.192 maks = 129.505)
Nomor benang .: 4 Total pengujian: 200
Waktu berjalan: (menampilkan 5 terakhir) 62.101 67.629 62.087 52.021 55.766 (rata-rata = 65.6361 menit = 50.282 max = 167.433)
Nomor benang .: 8 Total pengujian: 200
Waktu berjalan: (menunjukkan 5 terakhir) 40.672 74.301 34.434 41.549 28.119 (rata-rata = 54.7701 menit = 28.119 max = 94.424)
Ringkasan
Lokal utas sekitar 10-20x dari heap yang dibaca. Tampaknya juga berskala baik pada implementasi JVM ini dan arsitektur ini dengan jumlah prosesor.
sumber
"!"
tidak pernah terjadi) dalam metode pertama - metode pertama secara efektif setara dengan subclassThread
dan memberinya bidang khusus. Tolok ukur mengukur kasus tepi ekstrim di mana seluruh komputasi terdiri dari membaca variabel / utas lokal - aplikasi nyata mungkin tidak terpengaruh tergantung pada pola aksesnya, tetapi dalam kasus terburuk, mereka akan berperilaku seperti di atas.Ini dia tes lain. Hasilnya menunjukkan bahwa ThreadLocal sedikit lebih lambat dari bidang biasa, tetapi dalam urutan yang sama. Aprox 12% lebih lambat
public class Test { private static final int N = 100000000; private static int fieldExecTime = 0; private static int threadLocalExecTime = 0; public static void main(String[] args) throws InterruptedException { int execs = 10; for (int i = 0; i < execs; i++) { new FieldExample().run(i); new ThreadLocaldExample().run(i); } System.out.println("Field avg:"+(fieldExecTime / execs)); System.out.println("ThreadLocal avg:"+(threadLocalExecTime / execs)); } private static class FieldExample { private Map<String,String> map = new HashMap<String, String>(); public void run(int z) { System.out.println(z+"-Running field sample"); long start = System.currentTimeMillis(); for (int i = 0; i < N; i++){ String s = Integer.toString(i); map.put(s,"a"); map.remove(s); } long end = System.currentTimeMillis(); long t = (end - start); fieldExecTime += t; System.out.println(z+"-End field sample:"+t); } } private static class ThreadLocaldExample{ private ThreadLocal<Map<String,String>> myThreadLocal = new ThreadLocal<Map<String,String>>() { @Override protected Map<String, String> initialValue() { return new HashMap<String, String>(); } }; public void run(int z) { System.out.println(z+"-Running thread local sample"); long start = System.currentTimeMillis(); for (int i = 0; i < N; i++){ String s = Integer.toString(i); myThreadLocal.get().put(s, "a"); myThreadLocal.get().remove(s); } long end = System.currentTimeMillis(); long t = (end - start); threadLocalExecTime += t; System.out.println(z+"-End thread local sample:"+t); } } }'
Keluaran:
0-Running contoh lapangan
Sampel bidang 0-End: 6044
0-Menjalankan sampel lokal thread
Sampel lokal utas 0-ujung: 6015
Sampel bidang 1-Running
Contoh bidang 1-ujung: 5095
Sampel lokal 1-Running thread
Sampel lokal utas 1-ujung: 5720
Sampel bidang 2-Running
Contoh bidang 2-ujung: 4842
2-Running thread sampel lokal
Sampel lokal utas 2 ujung: 5835
Sampel lapangan 3-Running
Sampel lapangan 3-End: 4674
3-Running thread sampel lokal
Sampel lokal utas 3-ujung: 5287
Contoh bidang 4-Running
Contoh bidang 4-ujung: 4849
4-Running thread sampel lokal
Sampel lokal utas 4-ujung: 5309
5-sampel lapangan lari
Contoh bidang 5-ujung: 4781
5-Running thread sampel lokal
Sampel lokal benang 5-ujung: 5330
6-Running contoh lapangan
Contoh bidang 6-End: 5294
6-Running thread local sample
Sampel lokal utas 6-ujung: 5511
7-Running sampel lapangan
Contoh bidang 7-End: 5119
7-Running thread sampel lokal
Sampel lokal utas 7-ujung: 5793
8-Running contoh lapangan
Contoh bidang 8-ujung: 4977
8-Running thread sampel lokal
Sampel lokal utas 8-ujung: 6374
Contoh lapangan 9-Running
Contoh bidang 9-End: 4841
9-Running thread sampel lokal
Sampel lokal utas 9-ujung: 5471
Rata-rata bidang: 5051
Rata-rata ThreadLocal: 5664
Env:
openjdk versi "1.8.0_131"
Intel® Core ™ i7-7500U CPU @ 2,70GHz × 4
Ubuntu 16.04 LTS
sumber
Int.toString)
, yang sangat mahal dibandingkan dengan apa yang Anda uji. B) Anda melakukan dua operasi peta setiap iterasi, juga sama sekali tidak terkait dan mahal. Coba tambahkan int primitif dari ThreadLocal sebagai gantinya. C) GunakanSystem.nanoTime
alih-alihSystem.currentTimeMillis
, yang pertama untuk pembuatan profil, yang terakhir adalah untuk tujuan tanggal-waktu pengguna dan dapat berubah di bawah kaki Anda. D) Anda harus menghindari allocs sepenuhnya, termasuk yang tingkat atas untuk kelas "contoh" Anda@Pete adalah tes yang benar sebelum Anda mengoptimalkan.
Saya akan sangat terkejut jika membangun MessageDigest memiliki overhead yang serius jika dibandingkan dengan menggunakannya secara akurat.
Kehilangan penggunaan ThreadLocal dapat menjadi sumber kebocoran dan referensi yang menggantung, yang tidak memiliki siklus hidup yang jelas, umumnya saya tidak pernah menggunakan ThreadLocal tanpa rencana yang sangat jelas tentang kapan sumber daya tertentu akan dihapus.
sumber
Bangun dan ukurlah.
Selain itu, Anda hanya memerlukan satu threadlocal jika merangkum perilaku mencerna pesan Anda menjadi sebuah objek. Jika Anda memerlukan MessageDigest lokal dan byte lokal [1000] untuk tujuan tertentu, buat objek dengan kolom messageDigest dan byte [], lalu letakkan objek tersebut ke ThreadLocal daripada keduanya secara individual.
sumber