ThreadLocal değişkeninin performansı


87

ThreadLocalNormal alandan daha yavaş değişkenden ne kadar okunur ?

Daha somut olarak, basit nesne oluşturma, ThreadLocaldeğişkene erişimden daha hızlı mı yoksa daha mı yavaş ?

Yeterince hızlı olduğunu varsayıyorum, böylece ThreadLocal<MessageDigest>örneğe sahip olmak , MessageDigesther seferinde örnek oluşturmaktan çok daha hızlıdır . Ancak bu, örneğin bayt [10] veya bayt [1000] için de geçerli mi?

Düzenleme: Soru ThreadLocal's get arandığında gerçekten neler oluyor ? Bu da diğerleri gibi sadece bir alansa, cevap "her zaman en hızlıdır" olur, değil mi?


2
Yerel bir evre temelde bir karma eşlem içeren bir alandır ve anahtarın mevcut evre nesnesi olduğu bir aramadır. Bu nedenle çok daha yavaş ama yine de hızlı. :)
eckes

1
@eckes: Kesinlikle böyle davranır, ancak genellikle bu şekilde uygulanmaz. Bunun yerine, ' Threadler, anahtarın geçerli ThreadLocalnesne olduğu bir (senkronize edilmemiş)
karma haritası içerir

Yanıtlar:


40

Yayınlanmamış karşılaştırmaları çalıştırmak ThreadLocal.get, makinemde yineleme başına yaklaşık 35 döngü sürüyor. Çok değil. Sun'ın uygulamasında , değerleri Threadeşlemelerde özel bir doğrusal problama hash haritası ThreadLocal. Sadece tek bir iş parçacığı ile erişildiği için çok hızlı olabilir.

Küçük nesnelerin tahsisi benzer sayıda döngü alır, ancak önbelleğin tükenmesi nedeniyle sıkı bir döngüde biraz daha düşük rakamlar elde edebilirsiniz.

İnşaatı MessageDigestmuhtemelen nispeten pahalı olacaktır. Oldukça fazla bir duruma sahiptir ve inşaat ProviderYSİ mekanizmasından geçer. Örneğin, klonlayarak veya sağlayarak optimize edebilirsiniz Provider.

Oluşturmaktan ThreadLocalziyade önbelleğe almanın daha hızlı olabilmesi , sistem performansının artacağı anlamına gelmez. GC ile ilgili her şeyi yavaşlatan ek genel giderleriniz olacaktır.

Uygulamanız çok fazla kullanmadığı sürece, MessageDigestbunun yerine geleneksel bir iş parçacığı güvenli önbellek kullanmayı düşünebilirsiniz.


5
IMHO, en hızlı yol sadece SPI'yi görmezden gelmek ve benzeri bir şey kullanmaktır new org.bouncycastle.crypto.digests.SHA1Digest(). Eminim hiçbir önbellek onu yenemez.
maaartinus

57

2009 yılında, bazı JVMs uygulanan ThreadLocalbir senkronlaştınlmamış kullanarak HashMapiçinde Thread.currentThread()nesne. Bu, onu son derece hızlı hale getirdi (tabii ki, normal bir alan erişimini kullanmak kadar hızlı olmasa da) ve ThreadLocalnesnenin Threadöldüğünde toparlanmasını sağladı . Bu yanıtı 2016'da güncellediğimizde, çoğu (tümü?) Yeni JVM'nin ThreadLocalMapdoğrusal problama ile kullandığı görülmektedir . Bunların performansından emin değilim - ancak önceki uygulamadan çok daha kötü olduğunu hayal edemiyorum.

Tabii ki, new Object()bu günlerde de çok hızlı ve çöp toplayıcılar da kısa ömürlü nesneleri geri kazanmada çok iyi.

Nesne oluşturmanın pahalı olacağından emin olmadığınız veya iş parçacığı bazında bir durumu devam ettirmeniz gerekmediği sürece, gerektiğinde daha basit ayırma çözümüne gitmeniz ve yalnızca ThreadLocalprofil oluşturucu size ihtiyacınız olduğunu söyler.


4
Soruyu gerçekten ele alan tek cevap olduğu için +1.
cletus

ThreadLocalMap için doğrusal problama kullanmayan modern bir JVM örneği verebilir misiniz? Java 8 OpenJDK hala ThreadLocalMap'i doğrusal problama ile kullanıyor gibi görünüyor. grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…
Karthick

1
@Karthick Üzgünüm, yapamam. Bunu 2009'da yazdım. Güncelleyeceğim.
Bill Michell

34

Güzel soru, son zamanlarda kendime soruyordum. Size kesin sayılar vermek için, aşağıdaki kriterler (Scala'da, eşdeğer Java koduyla hemen hemen aynı bayt kodlarına göre derlenmiştir):

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)
}

burada mevcut , bir AMD 4x 2.8 GHz çift çekirdekli ve hiper iş parçacıklı (2.67 GHz) dört çekirdekli bir i7 üzerinde gerçekleştirildi.

Sayılar şunlardır:

i7

Özellikler: Intel i7 2x dört çekirdekli @ 2.67 GHz Test: scala.threads.ParallelTests

Test adı: loop_heap_read

Konu numarası: 1 Toplam testler: 200

Çalışma süreleri: (son 5'i göstererek) 9.0069 9.0036 9.0017 9.0084 9.0074 (ortalama = 9.1034 dk = 8.9986 maks = 21.0306)

İplik numarası: 2 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 4.5563 4.7128 4.5663 4.5617 4.5724 (ortalama = 4.6337 dk = 4.5509 maks = 13.9476)

İplik numarası: 4 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 2.3946 2.3979 2.3934 2.3937 2.3964 (ortalama = 2.5113 dk = 2.3884 maks = 13.5496)

İplik numarası: 8 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 2,4479 2,4362 2,4323 2,4472 2,4383 (ortalama = 2,5562 dk = 2,4166 maks = 10,3726)

Test adı: threadlocal

Konu numarası: 1 Toplam testler: 200

Çalışma süreleri: (son 5'i göstererek) 91.1741 90.8978 90.6181 90.6200 90.6113 (ortalama = 91.0291 dk = 90.6000 maks = 129.7501)

İplik numarası: 2 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 45.3838 45.3858 45.6676 45.3772 45.3839 (ortalama = 46.0555 dk = 45.3726 maks = 90.7108)

İplik numarası: 4 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 22.8118 22.8135 59.1753 22.8229 22.8172 (ortalama = 23.9752 dk = 22.7951 maks = 59.1753)

İplik numarası: 8 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 22.2965 22.2415 22.3438 22.3109 22.4460 (ortalama = 23.2676 dk = 22.2346 maks = 50.3583)

AMD

Özellikler: AMD 8220 4x çift çekirdekli @ 2.8 GHz Test: scala.threads.ParallelTests

Test adı: loop_heap_read

Toplam iş: 20000000 İplik numarası: 1 Toplam testler: 200

Çalışma süreleri: (son 5'i göstererek) 12.625 12.631 12.634 12.632 12.628 (ortalama = 12.7333 dk = 12.619 maks = 26.698)

Test adı: loop_heap_read Toplam çalışma: 20000000

Çalışma süreleri: (son 5'i göstererek) 6.412 6.424 6.408 6.397 6.43 (ortalama = 6.5367 min = 6.393 maks = 19.716)

İplik numarası: 4 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 3.385 4.298 9.7 6.535 3.385 (ort = 5.6079 dk = 3.354 maks = 21.603)

İplik numarası: 8 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 5.389 5.795 10.818 3.823 3.824 (ortalama = 5.5810 dk = 2.405 maks = 19.755)

Test adı: threadlocal

Konu numarası: 1 Toplam testler: 200

Çalışma süreleri: (son 5'i göstererek) 200.217 207.335 200.241 207.342 200.23 (ortalama = 202.2424 dk = 200.184 maks = 245.369)

İplik numarası: 2 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 100.208 100.199 100.211 103.781 100.215 (ortalama = 102.2238 dk = 100.192 maks = 129.505)

İplik numarası: 4 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 62.101 67.629 62.087 52.021 55.766 (ortalama = 65.6361 dk = 50.282 maks = 167.433)

İplik numarası: 8 Toplam test: 200

Çalışma süreleri: (son 5'i göstererek) 40.672 74.301 34.434 41.549 28.119 (ort = 54.7701 dk = 28.119 maks = 94.424)

Özet

Yerel bir iş parçacığı, okunan yığının yaklaşık 10-20x'idir. Ayrıca, bu JVM uygulamasında ve bu mimarilerde işlemci sayısıyla iyi bir şekilde ölçekleniyor gibi görünüyor.


5
+1 Kantitatif sonuçlar veren tek kişi olmanın şerefine. Biraz şüpheliyim çünkü bu testler Scala'da, ama dediğin gibi Java bayt kodlarının benzer olması gerekiyor ...
Gravity

Teşekkürler! Bu while döngüsü, karşılık gelen Java kodunun üreteceği neredeyse aynı bayt koduyla sonuçlanır. Yine de farklı VM'lerde farklı zamanlar gözlemlenebilir - bu bir Sun JVM1.6 üzerinde test edilmiştir.
axel22

Bu kıyaslama kodu, ThreadLocal için iyi bir kullanım durumunu simüle etmez. İlk yöntemde: her iş parçacığının bellekte paylaşılan bir gösterimi olacaktır, dizge değişmez. İkinci yöntemde, dizenin tüm iş parçacıkları arasında ayırıcı olduğu bir hashtable aramanın maliyetini karşılaştırırsınız.
Joelmob

Dize değişmez, ancak "!"ilk yöntemde bellekten okunur (yazma asla gerçekleşmez) - ilk yöntem, alt sınıflamaya Threadve ona özel bir alan vermeye etkin bir şekilde eşdeğerdir . Kıyaslama, tüm hesaplamanın bir değişken / iş parçacığı yerelini okumaktan oluştuğu aşırı uç durumu ölçer - gerçek uygulamalar erişim modellerine bağlı olarak etkilenmeyebilir, ancak en kötü durumda, yukarıdaki gibi davranacaklardır.
axel22

4

İşte başka bir teste gidiyor. Sonuçlar, ThreadLocal'ın normal bir alandan biraz daha yavaş, ancak aynı sırada olduğunu gösteriyor. Aprox% 12 daha yavaş

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);
    }
}
}'

Çıktı:

0-Çalışan alan örneği

0-Son alan örneği: 6044

0-iş parçacığı yerel örneği çalıştırılıyor

0-Son iş parçacığı yerel örnek: 6015

1-Çalışan alan örneği

1-Uç alan örneği: 5095

1-İş parçacığı yerel örneği çalıştırma

1 Uçlu yerel örnek: 5720

2-Çalışan alan örneği

2 Uçlu alan örneği: 4842

2-Çalışan iş parçacığı yerel örneği

2 Uçlu yerel örnek: 5835

3-Çalışan alan örneği

3 Uçlu alan örneği: 4674

3-Çalışan iş parçacığı yerel örneği

3 uçlu yerel örnek: 5287

4-Çalışan alan örneği

4 Uçlu alan örneği: 4849

4-Çalışan iş parçacığı yerel örneği

4 Uçlu yerel örnek: 5309

5-Çalışan alan örneği

5 Son alan örneği: 4781

5-Çalışan iş parçacığı yerel örneği

5 Uçlu yerel örnek: 5330

6-Çalışan alan örneği

6 Uçlu alan örneği: 5294

6-Çalışan iş parçacığı yerel örneği

6 Uçlu yerel örnek: 5511

7-Çalışan alan örneği

7 Uçlu alan örneği: 5119

7-Çalışan iş parçacığı yerel örneği

7 Uçlu iş parçacığı yerel örnek: 5793

8-Çalışan alan örneği

8 Uçlu alan örneği: 4977

8-Çalışan iş parçacığı yerel örneği

8 Uçlu yerel örnek: 6374

9-Çalışan alan örneği

9 Uç alan örneği: 4841

9-iş parçacığı yerel örneği çalıştırma

9 Uçlu iş parçacığı yerel örnek: 5471

Alan ort .: 5051

ThreadLocal ortalama: 5664

Env:

openjdk sürümü "1.8.0_131"

Intel® Core ™ i7-7500U CPU @ 2.70GHz × 4

Ubuntu 16.04 LTS


1
Üzgünüz, bu geçerli bir test olmaya yakın bile değil. A) En büyük sorun: Dizeleri her yinelemeyle tahsis ediyorsunuz ( Int.toString)ki bu, test ettiğinize kıyasla son derece pahalıdır. B) her yinelemede iki harita operasyonu yapıyorsunuz, ayrıca tamamen ilgisiz ve pahalı. Bunun yerine ThreadLocal'dan temel bir int artırmayı deneyin. C) System.nanoTimeBunun yerine kullanın System.currentTimeMillis, birincisi profilleme içindir, ikincisi kullanıcı tarih-saat amaçlıdır ve ayaklarınızın altında değişebilir. D) "Örnek" sınıflarınız için en üst düzey olanlar da dahil olmak üzere, tümden tamamen kaçınmalısınız
Philip Guin

3

@Pete, optimize etmeden önce doğru testtir.

Bir MessageDigest oluşturmanın onu kullanmakla karşılaştırıldığında ciddi bir ek yükü varsa çok şaşırırdım.

ThreadLocal'ı kullanarak özledim, net bir yaşam döngüsüne sahip olmayan sızıntıların ve sarkan referansların kaynağı olabilir, genellikle belirli bir kaynağın ne zaman kaldırılacağına dair çok net bir plan olmadan ThreadLocal kullanmıyorum.


0

Yapın ve ölçün.

Ayrıca, mesaj özetleme davranışınızı bir nesneye kapsüllediğinizde yalnızca bir iş parçacığına ihtiyacınız vardır. Bir amaçla yerel bir MessageDigest'e ve yerel bir bayta [1000] ihtiyacınız varsa, messageDigest ve bayt [] alanı içeren bir nesne oluşturun ve bu nesneyi tek tek yerine ThreadLocal'a koyun.


Teşekkürler, MessageDigest ve bayt [] farklı kullanımlardır, bu nedenle tek bir nesneye gerek yoktur.
Sarmun
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.