concurrency - क्या एफ#स्पेलिंग और हत्या की प्रक्रियाओं में एरलांग की तुलना में वास्तव में तेज है?




f# erlang (2)

Erlang की VM नई थ्रलंग प्रक्रिया पर स्विच करने के लिए OS थ्रेड्स या प्रक्रिया का उपयोग नहीं करता है। यह VM केवल आपके कोड / प्रक्रिया में फ़ंक्शन कॉल की गणना करता है और कुछ (उसी OS प्रक्रिया और समान OS थ्रेड) के बाद अन्य VM की प्रक्रिया में कूदता है।

सीएलआर ओएस प्रक्रिया और थ्रेड्स के आधार पर यांत्रिकी का उपयोग करता है, इसलिए एफ # में प्रत्येक संदर्भ स्विच के लिए बहुत अधिक ओवरहेड लागत है।

तो आपके प्रश्न का उत्तर है "नहीं, एरलांग स्पैनिंग और हत्या प्रक्रियाओं की तुलना में बहुत तेज है"।

PS आप उस व्यावहारिक प्रतियोगिता के परिणाम दिलचस्प पा सकते हैं।

अपडेट किया गया: इस प्रश्न में एक त्रुटि है जो बेंचमार्क को अर्थहीन बनाता है। मैं एफ # और एर्लैंग की बुनियादी समरूपता कार्यक्षमता की तुलना करने वाले एक बेहतर बेंचमार्क का प्रयास करूंगा और दूसरे प्रश्न में परिणामों के बारे में पूछूंगा।

मैं एर्लांग और एफ # की प्रदर्शन विशेषताओं को समझने की कोशिश कर रहा हूं। मुझे लगता है कि एरलांग का कंसीलर मॉडल बहुत आकर्षक है लेकिन इंटरऑपरेबिलिटी कारणों से एफ # का उपयोग करने के लिए इच्छुक हूं। बॉक्स से बाहर F # एर्लैंग की संगामिति आदिमों की तरह कुछ भी पेश नहीं करता है - जो मैं एसिंक्स और मेलबॉक्‍सप्रोसेसर को बता सकता हूं उसमें से केवल एक छोटा सा हिस्सा कवर करता है जो एर्लैंग अच्छा करता है - मैं यह समझने की कोशिश कर रहा हूं कि एफ में क्या संभव है। बुद्धिमान।

जो आर्मस्ट्रांग की प्रोग्रामिंग एरलांग पुस्तक में, उन्होंने कहा कि इरलांग में प्रक्रियाएं बहुत सस्ती हैं। वह इस तथ्य को प्रदर्शित करने के लिए निम्नलिखित कोड का उपयोग करता है:

-module(processes).
-export([max/1]).

%% max(N) 
%%   Create N processes then destroy them
%%   See how much time this takes

max(N) ->
    statistics(runtime),
    statistics(wall_clock),
    L = for(1, N, fun() -> spawn(fun() -> wait() end) end),
    {_, Time1} = statistics(runtime),
    {_, Time2} = statistics(wall_clock),
    lists:foreach(fun(Pid) -> Pid ! die end, L),
    U1 = Time1 * 1000 / N,
    U2 = Time2 * 1000 / N,
    io:format("Process spawn time=~p (~p) microseconds~n",
          [U1, U2]).

wait() ->
    receive
        die -> void
    end.

for(N, N, F) -> [F()];
for(I, N, F) -> [F()|for(I+1, N, F)].

मेरी मैकबुक प्रो पर, 100 हज़ार प्रक्रियाओं ( processes:max(100000) ) को स्पैनिंग और मारना प्रति प्रक्रियाओं के बारे में 8 माइक्रोसेकंड लेता है। मैं प्रक्रियाओं की संख्या को थोड़ा और बढ़ा सकता हूं, लेकिन एक लाख चीजों को लगातार तोड़ता हुआ प्रतीत होता है।

बहुत कम एफ # जानने के बाद, मैंने इस उदाहरण को एस्किंक और मेलबॉक्‍सप्रोसेसर का उपयोग करके लागू करने का प्रयास किया। मेरा प्रयास, जो गलत हो सकता है, इस प्रकार है:

#r "System.dll"
open System.Diagnostics

type waitMsg =
    | Die

let wait =
    MailboxProcessor.Start(fun inbox ->
        let rec loop =
            async { let! msg = inbox.Receive()
                    match msg with 
                    | Die -> return() }
        loop)

let max N =
    printfn "Started!"
    let stopwatch = new Stopwatch()
    stopwatch.Start()
    let actors = [for i in 1 .. N do yield wait]
    for actor in actors do
        actor.Post(Die)
    stopwatch.Stop()
    printfn "Process spawn time=%f microseconds." (stopwatch.Elapsed.TotalMilliseconds * 1000.0 / float(N))
    printfn "Done."

मोनो पर एफ # का उपयोग करना, 100,000 अभिनेताओं / प्रोसेसर को शुरू करना और मारना, प्रति प्रक्रिया 2 माइक्रोसेकंड के तहत लेता है, एरलांग की तुलना में लगभग 4 गुना तेज है। अधिक महत्वपूर्ण बात, शायद, यह है कि मैं लाखों प्रक्रियाओं तक बिना किसी स्पष्ट समस्याओं के पैमाने बना सकता हूं। 1 या 2 मिलियन प्रक्रियाओं को शुरू करने में अभी भी प्रति प्रक्रिया के बारे में 2 माइक्रोसेकंड लगते हैं। 20 मिलियन प्रोसेसर शुरू करना अभी भी संभव है, लेकिन प्रति प्रक्रिया लगभग 6 माइक्रोसेकंड तक धीमी हो जाती है।

मुझे अभी तक पूरी तरह से समझने में समय नहीं लगा है कि एफ # एस्क्लाइन और मेलबॉक्सप्रोसेसर को कैसे लागू करता है, लेकिन ये परिणाम उत्साहजनक हैं। क्या कुछ ऐसा है जो मैं बुरी तरह से गलत कर रहा हूं?

यदि नहीं, तो क्या कुछ जगह एर्लैंग की संभावना एफ # से बेहतर होगी? क्या कोई कारण है कि Erlang की संगामिति आदिमियों को लाइब्रेरी के माध्यम से F # में नहीं लाया जा सकता है?

EDIT: ब्रायन ने बताया कि त्रुटि के कारण उपरोक्त संख्या गलत है। जब मैं इसे ठीक करूंगा तो मैं पूरे प्रश्न को अपडेट कर दूंगा।


अपने मूल कोड में, आपने केवल एक मेलबॉक्स शुरू किया है। wait() एक फ़ंक्शन, और इसे प्रत्येक yield साथ कॉल करें। इसके अलावा, आप उनके लिए संदेशों को स्पिन करने या प्राप्त करने के लिए इंतजार नहीं कर रहे हैं, जो मुझे लगता है कि समय की जानकारी को अमान्य करता है; नीचे मेरा कोड देखें।

उस ने कहा, मुझे कुछ सफलता मिली है; अपने बॉक्स पर मैं प्रत्येक के बारे में 25us पर 100,000 कर सकते हैं। बहुत अधिक होने के बाद, मुझे लगता है कि आप संभवतया आवंटनकर्ता / जीसी से लड़ना शुरू कर सकते हैं, लेकिन मैं एक मिलियन भी कर सकता था (लगभग 27us प्रत्येक पर, लेकिन इस बिंदु पर 1.5G मेमोरी की तरह उपयोग कर रहा था)।

मूल रूप से प्रत्येक 'सस्पेंडेड एस्किंस्ट' (जो एक मेलबॉक्स की तरह लाइन पर प्रतीक्षा कर रहा है, वह स्थिति है

let! msg = inbox.Receive()

) अवरुद्ध होने पर केवल कुछ संख्या बाइट्स लेता है। यही कारण है कि आपके पास थ्रेड्स की तुलना में रास्ता, रास्ता, रास्ता अधिक asyncs हो सकता है; एक धागा आम तौर पर स्मृति या अधिक की मेगाबाइट की तरह लेता है।

ठीक है, यहाँ कोड मैं उपयोग कर रहा हूँ। आप एक छोटी संख्या का उपयोग कर सकते हैं जैसे कि 10, और --define DEBUG यह सुनिश्चित करने के लिए कि कार्यक्रम के शब्दार्थ क्या हैं (प्रिंटफ आउटपुट्स को इंटरलीव किया जा सकता है, लेकिन आपको विचार मिलेगा)।

open System.Diagnostics 

let MAX = 100000

type waitMsg = 
    | Die 

let mutable countDown = MAX
let mre = new System.Threading.ManualResetEvent(false)

let wait(i) = 
    MailboxProcessor.Start(fun inbox -> 
        let rec loop = 
            async { 
#if DEBUG
                printfn "I am mbox #%d" i
#endif                
                if System.Threading.Interlocked.Decrement(&countDown) = 0 then
                    mre.Set() |> ignore
                let! msg = inbox.Receive() 
                match msg with  
                | Die -> 
#if DEBUG
                    printfn "mbox #%d died" i
#endif                
                    if System.Threading.Interlocked.Decrement(&countDown) = 0 then
                        mre.Set() |> ignore
                    return() } 
        loop) 

let max N = 
    printfn "Started!" 
    let stopwatch = new Stopwatch() 
    stopwatch.Start() 
    let actors = [for i in 1 .. N do yield wait(i)] 
    mre.WaitOne() |> ignore // ensure they have all spun up
    mre.Reset() |> ignore
    countDown <- MAX
    for actor in actors do 
        actor.Post(Die) 
    mre.WaitOne() |> ignore // ensure they have all got the message
    stopwatch.Stop() 
    printfn "Process spawn time=%f microseconds." (stopwatch.Elapsed.TotalMilliseconds * 1000.0 / float(N)) 
    printfn "Done." 

max MAX

यह सब कहा, मैं एर्लैंग को नहीं जानता, और मैंने इस बारे में गहराई से नहीं सोचा है कि क्या एफ # नीचे ट्रिम करने का एक तरीका है (हालांकि यह बहुत ही मुहावरेदार है)।







actor