2011-11-02 19 views
9

संपादित करें: मेरा नमूना आकार बहुत छोटा था। जब मैंने 8 सीपीयू के वास्तविक डेटा के खिलाफ इसे चलाया, तो मैंने 7.2x की गति में वृद्धि देखी। मेरे कोड में 4 वर्ण जोड़ने के लिए बहुत शर्मीली नहीं है;)स्कैला समांतर संग्रह रनटाइम गूढ़

मैं वर्तमान में स्कैला का उपयोग करने के लाभों पर 'बिक्री' प्रबंधन की कोशिश करने की प्रक्रिया में हूं, खासकर जब यह CPU के साथ स्केलिंग की बात आती है। इसके अंत में, मैंने एक साधारण परीक्षण अनुप्रयोग बनाया जो वेक्टर गणित का एक गुच्छा करता है और यह पता लगाने में थोड़ा आश्चर्य हुआ कि रनटाइम मेरी क्वाड-कोर मशीन पर बेहतर नहीं था। दिलचस्प बात यह है कि, मैंने पाया कि रनटाइम पहली बार संग्रह के माध्यम से सबसे खराब है और बाद के कॉल के साथ बेहतर हो जाता है। क्या समानांतर संग्रह में कुछ आलसी चीजें हैं जो इसका कारण बन रही हैं, या क्या मैं यह गलत कर रहा हूं? यह ध्यान दिया जाना चाहिए कि मैं सी ++/सी # दुनिया से आया हूं, इसलिए यह पूरी तरह से संभव है कि मैंने किसी भी तरह से मेरी कॉन्फ़िगरेशन को गड़बड़ कर दिया है।

InteliJ स्काला प्लगइन

स्काला 2.9.1.final

विंडोज 7 64 बिट, क्वाड-कोर प्रोसेसर (कोई हाइपरथ्रेडिंग)

import util.Random 

    // simple Vector3D class that has final x,y,z components a length, and a '-' function 
    class Vector3D(val x:Double, val y:Double, val z:Double) 
    { 
    def length = math.sqrt(x*x+y*y+z*z) 
    def -(rhs : Vector3D) = new Vector3D(x - rhs.x, y - rhs.y, z - rhs.z) 
    } 

object MainClass { 

    def main(args : Array[String]) = 
    { 
    println("Available CPU's: " + Runtime.getRuntime.availableProcessors()) 
    println("Parallelism Degree set to: " + collection.parallel.ForkJoinTasks.defaultForkJoinPool.getParallelism); 
    // my position 
    val myPos = new Vector3D(0,0,0); 

    val r = new Random(0); 

    // define a function nextRand that gets us a random between 0 and 100 
    def nextRand = r.nextDouble() * 100; 

    // make 10 million random targets 
    val targets = (0 until 10000000).map(_ => new Vector3D(nextRand, nextRand, nextRand)).toArray 
    // take the .par hit before we start profiling 
    val parTargets = targets.par 

    println("Created " + targets.length + " vectors") 

    // define a range function 
    val rangeFunc : (Vector3D => Double) = (targetPos) => (targetPos - myPos).length 

    // we'll select ones that are <50 
    val within50 : (Vector3D => Boolean) = (targetPos) => rangeFunc(targetPos) < 50 

    // time it sequentially 
    val startTime_sequential = System.currentTimeMillis() 
    val numTargetsInRange_sequential = targets.filter(within50) 
    val endTime_sequential = System.currentTimeMillis() 
    println("Sequential (ms): " + (endTime_sequential - startTime_sequential)) 

    // do the parallel version 10 times 
    for(i <- 1 to 10) 
    { 

     val startTime_par = System.currentTimeMillis() 
     val numTargetsInRange_parallel = parTargets.filter(within50) 
     val endTime_par = System.currentTimeMillis() 

     val ms = endTime_par - startTime_par; 
     println("Iteration[" + i + "] Executed in " + ms + " ms") 
    } 
    } 
} 

के उत्पादन: भले ही, यहां मेरे सेटअप है यह प्रोग्राम है:

Available CPU's: 4 
Parallelism Degree set to: 4 
Created 10000000 vectors 
Sequential (ms): 216 
Iteration[1] Executed in 227 ms 
Iteration[2] Executed in 253 ms 
Iteration[3] Executed in 76 ms 
Iteration[4] Executed in 78 ms 
Iteration[5] Executed in 77 ms 
Iteration[6] Executed in 80 ms 
Iteration[7] Executed in 78 ms 
Iteration[8] Executed in 78 ms 
Iteration[9] Executed in 79 ms 
Iteration[10] Executed in 82 ms 

तो यहां क्या हो रहा है? पहले 2 बार हम फ़िल्टर करते हैं, यह धीमा है, और फिर चीजें तेज हो जाती हैं? मैं समझता हूं कि मूल रूप से समांतरता स्टार्टअप लागत होगी, मैं सिर्फ यह पता लगाने की कोशिश कर रहा हूं कि मेरे एप्लिकेशंस में समांतरता को व्यक्त करने के लिए यह समझ में आता है, और विशेष रूप से मैं प्रबंधन को एक प्रोग्राम दिखाने में सक्षम होना चाहता हूं जो 3-4 बार चलता है एक क्वाड कोर बॉक्स पर तेजी से। क्या यह सिर्फ एक अच्छी समस्या नहीं है?

विचार?

+2

आप देख रहे हैं कि कैसे प्रबंधन बेचने के लिए पर कुछ विचार के लिए, आप (तीर कुंजियों का उपयोग अगली स्लाइडों को देखने के लिए) http://scala-boss.heroku.com/#1 पर एक नज़र हो सकता है। – huynhjl

+1

सामान्य रूप से, समानांतर वैक्टरों के समानांतर सरणी पसंद करते हैं, कम से कम जब तक वैक्टर में कॉन्सेट जोड़े जाते हैं। – axel22

+0

@huynhjl - मुझे पता था कि प्रस्तुति कुछ मूल्यवान थी जब मैंने अपने जीवन को पहले दो कॉमिक्स में चित्रित किया था। धन्यवाद! – fbl

उत्तर

11

आपके पास माइक्रो-बेंचमार्क बीमारी है। आप जेआईटी संकलन चरण की बेंचमार्किंग की संभावना रखते हैं। आपको पहले अपने प्री-रन के साथ अपने जेआईटी को गर्म करने की आवश्यकता होगी।

शायद सबसे अच्छा विचार माइक्रो-बेंचमार्किंग फ्रेमवर्क जैसे http://code.google.com/p/caliper/ का उपयोग करना है जो आपके लिए यह सब कुछ संभालता है।

संपादित: संदर्भित from this blog post

0

कैसे

val numTargetsInRange_sequential = parTargets.filter(within50) 

के बारे में?

इसके अलावा, आपको फ़िल्टर ऑपरेशन के बजाए मानचित्र के साथ शायद अधिक प्रभावशाली परिणाम मिलेंगे।

7

हालात में तेजी लाने के रूप में वहाँ, कैलिपर बेंच मार्किंग स्काला परियोजनाओं के लिए एक अच्छा SBT Template है, लेकिन यह समानांतर बनाम अनुक्रमिक, आप सेब के लिए सेब की तुलना नहीं कर रहे हैं के साथ कोई संबंध नहीं है। जेवीएम में एक जेआईटी (बस समय में) कंपाइलर है जो कुछ निश्चित कोड का उपयोग करने के बाद ही कुछ बाइट कोड संकलित करेगा। तो आप पहले पुनरावृत्तियों में जो देखते हैं वह कोड के लिए धीमी निष्पादन है जो अभी तक जेआईटी-एड के साथ-साथ चल रहे जेआईटी संकलन के लिए समय नहीं है।

Sequential (ms): 312 
Iteration[1] Executed in 117 ms 
Iteration[2] Executed in 112 ms 
Iteration[3] Executed in 112 ms 
Iteration[4] Executed in 112 ms 
Iteration[5] Executed in 114 ms 
Iteration[6] Executed in 113 ms 
Iteration[7] Executed in 113 ms 
Iteration[8] Executed in 117 ms 
Iteration[9] Executed in 113 ms 
Iteration[10] Executed in 111 ms 

लेकिन यह सब अनुक्रमिक है: .par निकाला जा रहा है इतना है कि यह सब अनुक्रमिक यहाँ क्या मैं अपने मशीन (10x कम यात्रा मैं एक पुराने मशीन का उपयोग कर रहा कारण) पर देख रहा है! आप JAVA_OPTS में JVM -XX:+PrintCompilation (सेट का उपयोग करके देख सकते हैं कि JVM JIT के मामले में करता है या का उपयोग -J-XX:+PrintCompilation स्केला विकल्प। पहली पुनरावृति में आप JVM प्रिंट बयान दिखा क्या JIT एड किया जा रहा है की एक बड़ी संख्या देखेंगे, तो यह स्थिर ।।

तो बराबर के बिना सेब के लिए सेब की तुलना में बाद में करने के लिए, आप पहली बार चलाने, तो बराबर जोड़ सकते हैं और एक ही कार्यक्रम चलाने मेरी डुअल कोर, जब .par का उपयोग करते हुए मैं:

Sequential (ms): 329 
Iteration[1] Executed in 197 ms 
Iteration[2] Executed in 60 ms 
Iteration[3] Executed in 57 ms 
Iteration[4] Executed in 58 ms 
Iteration[5] Executed in 59 ms 
Iteration[6] Executed in 73 ms 
Iteration[7] Executed in 56 ms 
Iteration[8] Executed in 60 ms 
Iteration[9] Executed in 58 ms 
Iteration[10] Executed in 57 ms 

तो अधिक या कम एक 2x speedup एक बार यह स्थिर है।

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

हालांकि यह इस मामले में लागू नहीं होता है के रूप में for समय के बाहर है, वहाँ भी while के बजाय for को उपयोग करने के लागत है, लेकिन जब -optimize scalac ध्वज का उपयोग कर 2.9.1 संकलक एक सभ्य काम करना चाहिए ।

3

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

आपके मामले में, हालांकि आप आपरेशनों की एक बड़ी राशि प्रदर्शन कर रहे हैं, अपने आप ही प्रत्येक ऑपरेशन लगभग सीपीयू तुच्छ है। कार्रवाई में समानांतर संग्रह देखने के लिए, एक इकाई के आधार पर भारी ऑपरेशन आज़माएं। def isPrime(x:Int) = (2 to x/2).forall(y=>x%y!=0)

तो यही तर्क का उपयोग आप एक संग्रह में संख्या कि प्रधानमंत्री हैं निर्धारित करने के लिए प्रस्तुत किया:

एक समान स्काला प्रस्तुति के लिए, मैं गणना करने के लिए एक नंबर एक प्रमुख है कि क्या एक साधारण (innefficient) algo इस्तेमाल किया :

val col = 1 to 1000000 
col.filter(isPrime(_)) // sequential 
col.par.filter(isPrime(_)) // parallel 

सीपीयू व्यवहार वास्तव में दोनों के बीच अंतर पता चला: prime numbers: sequential vs parallel

समय 3.5 बेट्टे के बारे में था 4-कोर सीपीयू में समांतर संग्रह के लिए आर।

संबंधित मुद्दे