छह महीने पहले, क्रिस पेननेर ने हास्केल की 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
, , ? , , .
- -
, ? , . , ?\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
, , , 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
! , (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 ,
!