लगभग 20 पंक्तियों, एक ही परिणाम के बारे में: एलिक्सिर पर डब्ल्यूसी

छह महीने पहले, क्रिस पेननेर ने हास्केल की 80 लाइन्स के साथ बीटिंग सी प्रकाशित की : डब्ल्यूसीप्रस्तावना कहती है:


चुनौती यह है कि एक स्मार्ट क्लोन बनाने के लिए मैन्युअल रूप से उपकरण wcको लागू करने के लिए C हमारी पसंदीदा उच्च-स्तरीय भाषा में कचरा संग्रह कार्यक्रम के साथ - हास्केल पर ! बहुत आसान लगता है, है ना?

क्रिस बाइटस्ट्रीम का उपयोग करके एक सरल कार्यान्वयन से सभी तरह से चला गया , मोनॉयड्स , बिल्ट-इन मोनॉयड्स के माध्यम से, और अंत में ऊपर के समानांतर मल्टी-कोर संस्करण में आया, जो चार कोर पर रन समय पर थोड़ा शुद्ध सी- कोड को हरा देने में कामयाब रहा


कुछ दिनों पहले हीबे से इसी विषय पर एक और नोट पोस्ट किया गया था 0xd34df00d C Haskell: wcलेखक ने एल्गोरिथ्म को लागू करने के लिए कोड के मुहावरेदार हास्केल और बीस (20) लाइनों का उपयोग करने की संभावना को साबित कर दिया , जो सी में मुहावरेदार कार्यान्वयन से लगभग दस गुना तेज है


इस बीच, मैं हास्केल का उपयोग करने की वकालत करता हूं (वास्तव में, यहां तक ​​कि इदरीस अपने आश्रित प्रकारों के कारण) और कोक वास्तव में हमारे कोड बेस में महत्वपूर्ण अवधारणाओं को साबित करने के लिए; लेकिन सब कुछ जो उत्पादन में चला जाता है, अभी भी 100% एलिक्जिर / एर्लांग / ओटीपी है , क्योंकि उनकी अभूतपूर्व गलती सहनशीलता के कारण है। मैं यह सुनिश्चित करना चाहता था कि मैं एक जिद्दी राम नहीं था जो सिर्फ एरलैंग सिंटैक्स पसंद करता था, और इसलिए मैंने यह जांचने का फैसला किया कि हम एक ही कार्य के लिए मुहावरेदार अमृत ​​के साथ क्या कर सकते हैं


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


मैं बिल्कुल वैसा ही उपयोग करने के लिए जा रहा हूँ परीक्षण फ़ाइल के रूप में0xd34df00dइसलिए मैंने इसे डाउनलोड किया और इसे दस बार अपने आप से चिपका दिया, जैसा कि वसीयत किया गया था।


% for i in `seq 1 10`; cat part.txt >> test.txt
% du -sh test.txt
1.8G    test.txt

मेरे लैपटॉप पर (8 कोर / 16 जी रैम), wcइसे संसाधित करने में लगभग 10 सेकंड लगते हैं।


time LANG=C LC_ALL=C wc data/test.txt 
  15000000   44774631 1871822210 data/test.txt
LANG=C LC_ALL=C wc data/test.txt  9,69s user 0,36s system 99% cpu 10,048 total

खैर, देखते हैं कि ओटीपी इस कार्य में कितना खराब प्रदर्शन करेगा


बहुत भोली कोशिश


, , , .


@acc %{bc: 0, wc: 1, lc: 0, ns?: 1}

@type acc :: %{
        bc: non_neg_integer(),
        wc: non_neg_integer(),
        lc: non_neg_integer(),
        ns?: 0 | 1
      }

@spec parse(<<_::_*8>>, acc()) :: acc()
def parse("", acc), do: acc

def parse(
      <<?\n, rest::binary>>,
      %{bc: bc, wc: wc, lc: lc, ns?: ns}
    ),
    do: parse(rest, %{bc: bc + 1, wc: wc + ns, lc: lc + 1, ns?: 0})

def parse(
      <<?\s, rest::binary>>,
      %{bc: bc, wc: wc, lc: lc, ns?: ns}
    ),
    do: parse(rest, %{bc: bc + 1, wc: wc + ns, lc: lc, ns?: 0})

def parse(<<_, rest::binary>>, acc),
  do: parse(rest, %{acc | bc: acc.bc + 1, ns?: 1})

File.read!/1, File.stream!/3:


@spec lazy(binary) :: acc()actually
def lazy(file),
  do: file |> File.stream!() |> Enum.reduce(@acc, &parse/2)

@spec greedy(binary) :: acc()
def greedy(file),
  do: file |> File.read!() |> parse(@acc)

, , . ; , wc . , ( μs).


iex|1> :timer.tc fn -> Wc.lazy "data/part.txt" end
#⇒ {16_737094, %{bc: 185682221, lc: 1500000, ns?: 1, wc: 4477464}}
iex|2> :timer.tc fn -> Wc.greedy "data/part.txt" end
#⇒ {13_659356, %{bc: 187182221, lc: 1500000, ns?: 1, wc: 4477464}}

, , ? , , .


- -


, ? , . , ?\s ?\n —  —  . , , , . , - ( .)


@prehandle 42
@sink @prehandle + 1

@spec parse(<<_::_*8>>, acc()) :: acc()

Enum.each(0..@prehandle, fn i ->
  def parse(
        <<_::binary-size(unquote(i)), ?\n, rest::binary>>,
        %{bc: bc, wc: wc, lc: lc, ns?: ns}
      ),
      do: parse(rest, acc!(unquote(i), bc, wc, lc + 1, ns))

  def parse(
        <<_::binary-size(unquote(i)), ?\s, rest::binary>>,
        %{bc: bc, wc: wc, lc: lc, ns?: ns}
      ),
      do: parse(rest, acc!(unquote(i), bc, wc, lc, ns))
end)

def parse(<<_::binary-size(@sink), rest::binary>>, acc),
  do: parse(rest, %{acc | bc: acc.bc + @sink, ns?: 1})

Enum.each(@prehandle..0, fn i ->
  def parse(<<_::binary-size(unquote(i))>>, acc),
    do: %{acc | bc: acc.bc + unquote(i), ns?: 1}
end)

acc!  —  , , .


, ,  — ? , -, . 130 (43 EOL, ,  — :



.


iex|1> :timer.tc fn -> Wc.greedy "data/part.txt" end      
#⇒ {2_569929, %{bc: 187182221, lc: 1500000, ns?: 1, wc: 4477464}}
iex|2> :timer.tc fn -> Wc.lazy "data/part.txt" end  
#⇒ {6_616190, %{bc: 185682221, lc: 1500000, ns?: 1, wc: 4477464}}

, , , wc (, 2.5 ).


, , , , . , , . , NIF, ,  —  . , . , Erlang/OTP , , , . - ( ), . , ; , Flow.


12% ,


, , , (, , - ). mix.exs ( escript, , ).


def project do
  [
    app: :wc,
    version: "0.1.0",
    elixir: "~> 1.9",
    start_permanent: Mix.env() == :prod,

    escript: [main_module: Wc.Main],
    deps: [{:flow, "~> 1.0"}]
  ]
end

.


@chunk 1_000_000

@spec flowy(binary()) :: acc()
def flowy(file) do
  file
  |> File.stream!([], @chunk)
  |> Flow.from_enumerable()
  |> Flow.partition()
  |> Flow.reduce(fn -> @acc end, &parse/2)
  |> Enum.to_list()
end

( ) , , 1M. .


!


iex|1> :timer.tc fn -> Wc.flowy "data/part.txt" end
#⇒ {0_752568, %{bc: 187182221, lc: 1500000, ns?: 1, wc: 4477464}}
iex|2> :timer.tc fn -> Wc.flowy "data/test.txt" end
#⇒ {7_815592, %{bc: 1871822210, lc: 15000000, ns?: 1, wc: 44774631}}

! , (1.8 ). , , wc , , , , . , , : , , . mix escript.build , , .


time LANG=C LC_ALL=C wc data/test.txt
  15000000   44774631 1871822210 data/test.txt
LANG=C LC_ALL=C wc data/test.txt  9,71s user 0,39s system 99% cpu 10,104 total

time ./wc data/test.txt
    15000000    44774631    1871822210  data/test.txt
./wc data/test.txt  41,72s user 2,31s system 706% cpu 6,355 total

.




( main, escript) —  . , , —  20 ,




!


All Articles