थ्रेडलोकल वैरिएबल का प्रदर्शन


86

ThreadLocalनियमित क्षेत्र की तुलना में चर धीमे से कितना पढ़ा जाता है ?

अधिक ठोस रूप से सरल वस्तु निर्माण तेजी से या ThreadLocalचर की पहुंच से धीमी है ?

मैं मानता हूं कि यह काफी तेज है, ताकि ThreadLocal<MessageDigest>उदाहरण ज्यादा तेज हो और फिर MessageDigestहर बार उदाहरण बन सके। लेकिन क्या यह उदाहरण के लिए बाइट [10] या बाइट [1000] के लिए भी लागू होता है?

संपादित करें: प्रश्न यह है कि कॉल करते समय वास्तव में क्या हो रहा है ThreadLocal? यदि वह सिर्फ एक क्षेत्र है, किसी भी अन्य की तरह, तो उत्तर होगा "यह हमेशा सबसे तेज़ है", है ना?


2
एक थ्रेड लोकल बेसकली हैशमैप और लुकअप वाला एक फील्ड है, जहां की करंट थ्रेड ऑब्जेक्ट है। यह बहुत धीमी है, लेकिन अभी भी तेजी से है। :)
Eckes

1
@eeses: यह निश्चित रूप से ऐसा ही व्यवहार करता है, लेकिन यह आमतौर पर इस तरह से लागू नहीं होता है। इसके बजाय, Threadएक ( ThreadLocal
असम्बद्ध) हैशमैप

जवाबों:


40

अप्रकाशित मानदंड चलाना, ThreadLocal.getमेरी मशीन पर प्रति चक्कर लगभग 35 चक्र लेता है। बहुत बड़ी बात नहीं। सूर्य की कार्यान्वयन में में हैश नक्शे की जांच कर रैखिक एक कस्टम Threadनक्शे ThreadLocalमूल्यों के रों। क्योंकि यह केवल कभी-कभी किसी एक थ्रेड द्वारा एक्सेस किया जाता है, यह बहुत तेज़ हो सकता है।

छोटी वस्तुओं के आवंटन में समान संख्या में चक्र होते हैं, हालांकि कैश की थकावट के कारण आपको एक तंग पाश में कुछ कम आंकड़े मिल सकते हैं।

निर्माण MessageDigestअपेक्षाकृत महंगा होने की संभावना है। इसमें उचित मात्रा में राज्य है और निर्माण Providerएसपीआई तंत्र के माध्यम से होता है। आप उदाहरण के लिए, क्लोनिंग या प्रदान करने के द्वारा अनुकूलन करने में सक्षम हो सकते हैं Provider

सिर्फ इसलिए कि इसे ThreadLocalबनाने के बजाय कैश करने में तेज हो सकता है जरूरी नहीं कि सिस्टम प्रदर्शन में वृद्धि होगी। आपके पास GC से संबंधित अतिरिक्त ओवरहेड्स होंगे जो सब कुछ धीमा कर देते हैं।

जब तक आपका आवेदन बहुत भारी उपयोग नहीं करता है, MessageDigestआप इसके बजाय एक पारंपरिक थ्रेड-सुरक्षित कैश का उपयोग करने पर विचार कर सकते हैं।


5
IMHO, सबसे तेज़ तरीका सिर्फ SPI को अनदेखा करना और कुछ का उपयोग करना है new org.bouncycastle.crypto.digests.SHA1Digest()। मुझे पूरा यकीन है कि कोई भी कैश इसे हरा नहीं सकता है।
मारार्टिनस

57

2009 में, कुछ JVM ने Thread.currentThread () ऑब्जेक्ट में एक अनसिंक्रनाइज़ किए गए HashMap का उपयोग करके ThreadLocal को लागू किया। इसने इसे बहुत तेज़ बना दिया (हालाँकि, लगभग उतना ही तेज़ नहीं था, जो नियमित रूप से फील्ड एक्सेस का उपयोग कर रहा था), साथ ही साथ यह सुनिश्चित करने के लिए कि थ्रेडलोक ऑब्जेक्ट थ्रेड मर जाने पर टिक हो गया। 2016 में इस उत्तर को अपडेट करते हुए, यह सबसे (सभी?) लगता है नए जेवीएम रैखिक जांच के साथ थ्रेडलोकलपैप का उपयोग करते हैं। मैं उन के प्रदर्शन के बारे में अनिश्चित हूं - लेकिन मैं कल्पना नहीं कर सकता कि यह पहले के कार्यान्वयन से काफी खराब है।

बेशक, नई वस्तु () भी इन दिनों बहुत तेज है, और कचरा संग्राहक अल्पकालिक वस्तुओं को पुनः प्राप्त करने में भी बहुत अच्छे हैं।

जब तक आप निश्चित नहीं हैं कि ऑब्जेक्ट निर्माण महंगा होने वाला है, या आपको थ्रेड के आधार पर थ्रेड पर कुछ स्थिति को बनाए रखने की आवश्यकता है, तो आप बेहतर हैं जब आवश्यक समाधान के लिए आवंटित सरल हो, और केवल थ्रेडलोक कार्यान्वयन पर स्विच करना जब एक प्रोफाइलर आपको बताता है कि आप की जरूरत है।


4
+1 वास्तव में प्रश्न का उत्तर देने के लिए एकमात्र उत्तर होने के लिए।
cletus

क्या आप मुझे एक आधुनिक जेवीएम का एक उदाहरण दे सकते हैं जो थ्रेडलोकलपेज़ के लिए रैखिक जांच का उपयोग नहीं करता है? Java 8 OpenJDK अभी भी रैखिक जांच के साथ थ्रेडलोकलपाइप का उपयोग कर रहा है। grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…
कार्तिक

1
@ कार्थिक क्षमा करें नहीं मैं नहीं कर सकता। मैंने 2009 में इसे वापस लिखा। मैं अपडेट करूंगा।
बिल माइकेल

34

अच्छा सवाल है, मैं हाल ही में खुद से पूछ रहा हूं। आपको निश्चित संख्या देने के लिए, नीचे दिए गए मानदंड (स्काला में, समान जावा कोड के समान वस्तुतः बायकोड्स के लिए संकलित):

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

यहां उपलब्ध है , एक एएमडी 4x 2.8 गीगाहर्ट्ज डुअल-कोर और एक क्वाड-कोर आई 7 पर हाइपरथ्रेडिंग (2.67 गीगाहर्ट्ज) के साथ किया गया था।

ये नंबर हैं:

i7

चश्मा: इंटेल i7 2x क्वाड-कोर @ 2.67 गीगाहर्ट्ज़ टेस्ट: scala.threads.ParallelTests

परीक्षण का नाम: loop_heap_read

संख्या संख्या: 1 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 9.0069 9.0036 9.0017 9.0084 9.0074 (avg = 9.1034 मिनट = 8.9986 अधिकतम = 21.0306)

संख्या संख्या: 2 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 4.5563 4.7128 4.5663 4.5617 4.5724 (avg = 4.6337 मिनट = 4.5509 अधिकतम = 13.9476)

संख्या संख्या: 4 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 2.3946 2.3979 2.3934 2.3937 2.3964 (औसत = 2.5113 मिनट = 2.3884 अधिकतम = 13.5496)

संख्या संख्या: 8 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 2.4479 2.4362 2.4323 2.4472 2.4383 (औसत = 2.5562 मिनट = 2.4166 अधिकतम = 10.3726)

टेस्ट का नाम: थ्रेडलोकल

संख्या संख्या: 1 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 91.1741 90.8978 90.6181 90.6200 90.6113 (avg = 91.0291 मिनट = 90.6000 अधिकतम = 129.7501)

संख्या संख्या: 2 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 45.3838 45.3858 45.6676 45.3772 45.3839 (avg = 46.0555 मिनट = 45.3726 अधिकतम = 90.7108)

संख्या संख्या: 4 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखा रहा है) 22.8118 22.8135 59.1753 22.8229 22.8172 (avg = 23.9752 मिनट = 22.7951 अधिकतम = 59.1753)

संख्या संख्या: 8 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 22.2965 22.2415 22.3438 22.3109 22.4460 (avg = 23.2676 मिनट = 22.2346 अधिकतम = 50.3583)

एएमडी

चश्मा: AMD 8220 4x दोहरे कोर @ 2.8 गीगाहर्ट्ज़ टेस्ट: scala.threads.ParallelTests

परीक्षण का नाम: loop_heap_read

कुल काम: 20000000 थ्रेड संख्या: 1 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 12.625 12.631 12.634 12.632 12.628 (avg = 12.7333 मिनट = 12.619 अधिकतम = 26.698)

टेस्ट का नाम: loop_heap_read कुल काम: 20000000

रन समय: (अंतिम 5 दिखा रहा है) 6.412 6.424 6.408 6.397 6.43 (औसत = 6.5367 मिनट = 6.393 अधिकतम = 19.716)

संख्या संख्या: 4 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 3.385 4.298 9.7 6.535 3.385 (औसत = 5.6079 मिनट = 3.354 अधिकतम = 21.603)

संख्या संख्या: 8 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखा रहा है) 5.389 5.795 10.818 3.823 3.824 (avg = 5.5810 मिनट = 2.405 अधिकतम = 19.755)

टेस्ट का नाम: थ्रेडलोकल

संख्या संख्या: 1 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 200.217 207.335 200.241 207.342 200.23 (औसत = 202.2424 मिनट = 200.184 अधिकतम = 245.369)

संख्या संख्या: 2 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 100.208 100.199 100.211 103.781 100.215 (avg = 102.2238 मिनट = 100.192 अधिकतम = 129.505)

संख्या संख्या: 4 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 62.101 67.629 62.087 52.021 55.766 (avg = 65.6361 मिनट = 50.282 अधिकतम = 167.433)

संख्या संख्या: 8 कुल परीक्षण: 200

रन समय: (अंतिम 5 दिखाते हुए) 40.672 74.301 34.434 41.549 28.119 (avg = 54.7701 मिनट = 28.119 अधिकतम = 94.424)

सारांश

एक धागा स्थानीय 10-20x के आसपास होता है जो ढेर पढ़ा जाता है। यह इस JVM कार्यान्वयन और प्रोसेसर की संख्या के साथ इन आर्किटेक्चर पर भी अच्छा लगता है।


5
मात्रात्मक परिणाम देने वाला एकमात्र कुदोस। मुझे थोड़ा संदेह है क्योंकि ये परीक्षण स्काला में हैं, लेकिन जैसा कि आपने कहा, जावा बायोटेक समान होना चाहिए ...
गुरुत्वाकर्षण

धन्यवाद! जबकि लूप परिणामी रूप से एक ही बाइटकोड के रूप में संबंधित जावा कोड का उत्पादन करेगा। अलग-अलग वीएम पर अलग-अलग समय मनाया जा सकता है, हालांकि - यह एक सूर्य जेवीएम 1.6 पर परीक्षण किया गया है।
axel22

यह बेंचमार्क कोड थ्रेडलोक के लिए एक अच्छे उपयोग के मामले में अनुकरण नहीं करता है। पहली विधि में: प्रत्येक थ्रेड में मेमोरी में एक साझा प्रतिनिधित्व होगा, स्ट्रिंग नहीं बदलता है। दूसरी विधि में आप हैशटेबल लुकअप की लागत को निर्धारित करते हैं जहां स्ट्रिंग सभी थ्रेड्स के बीच असंगत है।
जोएलमोब

स्ट्रिंग में परिवर्तन नहीं होता है, लेकिन यह "!"पहली विधि में मेमोरी ( कभी नहीं होता है) से पढ़ा जाता है - पहली विधि प्रभावी रूप से सबक्लासिंग Threadऔर इसे एक कस्टम फ़ील्ड देने के बराबर है । बेंचमार्क एक चरम बढ़त मामले को मापता है जहां संपूर्ण गणना में एक चर / थ्रेड स्थानीय पढ़ना होता है - वास्तविक अनुप्रयोग उनके पहुंच पैटर्न के आधार पर प्रभावित नहीं हो सकते हैं, लेकिन सबसे खराब स्थिति में, वे ऊपर की तरह व्यवहार करेंगे।
axel22

4

यहाँ यह एक और परीक्षा देता है। परिणामों से पता चलता है कि थ्रेडलोक एक नियमित क्षेत्र की तुलना में थोड़ा धीमा है, लेकिन उसी क्रम में। Aprox 12% धीमा

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

आउटपुट:

0-रनिंग फील्ड नमूना

0-एंड फील्ड नमूना: 6044

0-थ्रेडिंग लोकल सैंपल

0-एंड थ्रेड स्थानीय नमूना: 6015

1-रनिंग फील्ड नमूना

1-अंत क्षेत्र नमूना: 5095

1-थ्रेडिंग स्थानीय नमूना

1-अंत धागा स्थानीय नमूना: 5720

2-रनिंग फील्ड नमूना

2-एंड फील्ड नमूना: 4842

2-थ्रेडिंग स्थानीय नमूना

2-अंत धागा स्थानीय नमूना: 5835

3-रनिंग फील्ड नमूना

3-एंड फील्ड नमूना: 4674

3-थ्रेडिंग स्थानीय नमूना

3-अंत धागा स्थानीय नमूना: 5287

4-रनिंग फील्ड नमूना

4-एंड फील्ड नमूना: 4849

4-थ्रेडिंग स्थानीय नमूना

4-अंत धागा स्थानीय नमूना: 5309

5-रनिंग फील्ड नमूना

5-एंड फील्ड नमूना: 4781

5-थ्रेडिंग स्थानीय नमूना

5-अंत धागा स्थानीय नमूना: 5330

6-रनिंग फील्ड नमूना

6-एंड फील्ड नमूना: 5294

6-रनिंग थ्रेड स्थानीय नमूना

6-अंत धागा स्थानीय नमूना: 5511

7-रनिंग फील्ड नमूना

7-एंड फील्ड नमूना: 5119

7-थ्रेडिंग स्थानीय नमूना

7-अंत धागा स्थानीय नमूना: 5793

8-रनिंग फील्ड नमूना

8-एंड फील्ड नमूना: 4977

8-थ्रेडिंग स्थानीय नमूना

8-अंत धागा स्थानीय नमूना: 6374

9-रनिंग फील्ड नमूना

9-अंत क्षेत्र नमूना: 4841

9-थ्रेडिंग स्थानीय नमूना

9-अंत धागा स्थानीय नमूना: 5471

फील्ड एवीजी: 5051

थ्रेडलोक अवग: 5664

env:

Openjdk संस्करण "1.8.0_131"

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

उबंटू 16.04 एलटीएस


क्षमा करें, यह वैध परीक्षण होने के करीब भी नहीं है। ए) सबसे बड़ा मुद्दा: आप स्ट्रिंग्स को हर पुनरावृत्ति के साथ आवंटित कर रहे हैं ( Int.toString), जो आपके परीक्षण के मुकाबले बहुत महंगा है। बी) आप हर पुनरावृत्ति से दो मैप ऑप्स कर रहे हैं, यह भी पूरी तरह से असंबंधित और महंगा है। इसके बजाय थ्रेडलोक से एक आदिम int बढ़ाने की कोशिश करें। सी) के System.nanoTimeबजाय का उपयोग करें System.currentTimeMillis, पूर्व प्रोफाइलिंग के लिए है, बाद वाला उपयोगकर्ता तिथि-समय प्रयोजनों के लिए है और आपके पैरों के नीचे बदल सकता है। डी) आपको अपने "उदाहरण" वर्गों के लिए शीर्ष स्तर वालों सहित पूरी तरह से अल्कोस से बचना चाहिए
गिनी गिनी

3

@ अनुकूलन से पहले परीक्षण सही है।

मुझे बहुत आश्चर्य होगा अगर मैसेजडिग का निर्माण किसी भी गंभीर ओवरहेड के साथ होता है जब तुलनात्मक रूप से इसका उपयोग किया जाता है।

थ्रेडलोक का उपयोग करते हुए मिस लीक और झूलने वाले संदर्भों का एक स्रोत हो सकता है, जिसमें एक स्पष्ट जीवन चक्र नहीं होता है, आमतौर पर मैं थ्रेडलोक का उपयोग किसी विशेष संसाधन को हटाए जाने की बहुत स्पष्ट योजना के बिना कभी नहीं करता।


0

इसका निर्माण करें और इसे मापें।

इसके अलावा, आपको केवल एक थ्रेडोकॉकल की आवश्यकता है यदि आप किसी वस्तु में अपने संदेश को पचाने वाले व्यवहार को संलग्न करते हैं। यदि आपको किसी उद्देश्य के लिए एक स्थानीय MessageDigest और एक स्थानीय बाइट [1000] की आवश्यकता है, तो एक MessageDigest और एक बाइट [] फ़ील्ड के साथ एक ऑब्जेक्ट बनाएं और व्यक्तिगत रूप से दोनों के बजाय थ्रेडलोक में उस ऑब्जेक्ट को डालें।


धन्यवाद, MessageDigest और बाइट [] विभिन्न उपयोग हैं, इसलिए एक वस्तु की आवश्यकता नहीं है।
सरमुन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.