ElixirとErlangは、いくつかの、おそらく類似のタスクを並行して実行する分散アプリケーションの作成に最適です。 Erlang仮想マシンを開発する際の主要な側面の1つは、単独で動作する競合プロセスの多くをサポートすることでした。
この機会をテストして、簡単な例を使用してマルチコアプロセッサの可能性を活用してみます。 1つのディレクトリ内のテキストファイルに配置された作家O. Henryの物語に「馬」という単語が何回現れるかを計算します。 技術的には、単語ではなく、「horse」という文字列の出現回数をカウントします。小文字のみです。
ファイル内の部分文字列の出現をカウントする
テキストファイルのコンテンツ内の部分文字列の出現回数をカウントする機能から始めましょう。
word_count = fn(file, word) -> {:ok, content} = File.read(file) length(String.split(content, word)) - 1 end
ファイルの内容を読み取り、単語への参照の数を返します。 簡単にするため、エラー処理は省略されています。
関数に1秒の遅延を追加し、カウントの結果を返す前にコンソールに出力します。
word_count = fn(file, word) -> :timer.sleep(1000) {:ok, content} = File.read(file) count = length(String.split(content, word)) - 1 IO.puts "Found #{inspect count} occurrence(s) of the word in file #{inspect file}" count end
次に、各ファイルの部分文字列の数を計算し、合計を出力します。
Path.wildcard("/data/OGENRI/*.txt") |> Enum.map(fn(file) -> word_count.(file, "") end) |> Enum.reduce(fn(x, acc) -> acc + x end) |> IO.puts
同時に、プログラム全体の実行時間を測定します。
# sync_word_count.exs start_time = :os.system_time(:milli_seconds) word_count = fn(file, word) -> :timer.sleep(1000) {:ok, content} = File.read(file) count = length(String.split(content, word)) - 1 IO.puts "Found #{inspect count} occurrence(s) of the word in file #{inspect file}" count end Path.wildcard("/data/OGENRI/*.txt") |> Enum.map(fn(file) -> word_count.(file, "") end) |> Enum.reduce(fn(x, acc) -> acc + x end) |> IO.puts end_time = :os.system_time(:milli_seconds) IO.puts "Finished in #{(end_time - start_time) / 1000} seconds"
合計で12個のファイルがあり、約12秒待機しなければなりませんでした。各ファイルのカウント結果がモニターに表示されるので、1秒ごとに検討しました。
iex sync_word_count.exs Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] Found 0 occurrence(s) of the word in file "/data/OGENRI/businessmen.txt" Found 1 occurrence(s) of the word in file "/data/OGENRI/choose.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/four.txt" Found 1 occurrence(s) of the word in file "/data/OGENRI/light.txt" Found 10 occurrence(s) of the word in file "/data/OGENRI/prevr.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/r_dl.txt" Found 1 occurrence(s) of the word in file "/data/OGENRI/r_linii.txt" Found 10 occurrence(s) of the word in file "/data/OGENRI/r_sixes.txt" Found 9 occurrence(s) of the word in file "/data/OGENRI/serdce.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/stihi.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/voice.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/ways.txt" 32 Finished in 12.053 seconds Interactive Elixir (1.3.1) - press Ctrl+C to exit (type h() ENTER for help)
非同期タスク実行
部分文字列の出現回数をカウントするには、 spawn
プロセス作成メソッドとsend
メソッドとreceive
メソッドを使用して、それぞれメッセージをreceive
します。
ファイルごとに個別のプロセスを作成します
async_word_count = fn(file, word) -> caller = self spawn(fn -> send(caller, {:result, word_count.(file, word)}) end) end
self
は現在のプロセスです。 self
と同じ値でcaller
変数を作成します。 生成されたプロセスはword_count/2
関数を呼び出し、結果を親プロセスに送り返します。
逆の方法で値を取得するには、親プロセスでreceive
を使用する必要があります(プロセスがある限り)。 このためにget_result/0
メソッドを作成しましょう。
get_result = fn -> receive do {:result, result} -> result end end
プログラムを更新します。
# async_word_count.exs start_time = :os.system_time(:milli_seconds) word_count = fn(file, word) -> :timer.sleep(1000) {:ok, content} = File.read(file) count = length(String.split(content, word)) - 1 IO.puts "Found #{inspect count} occurrence(s) of the word in file #{inspect file}" count end async_word_count = fn(file, word) -> caller = self spawn(fn -> send(caller, {:result, word_count.(file, word)}) end) end get_result = fn -> receive do {:result, result} -> result end end Path.wildcard("/data/OGENRI/*.txt") |> Enum.map(fn(file) -> async_word_count.(file, "") end) |> Enum.map(fn(_) -> get_result.() end) |> Enum.reduce(fn(x, acc) -> acc + x end) |> IO.puts end_time = :os.system_time(:milli_seconds) IO.puts "Finished in #{(end_time - start_time) / 1000} seconds"
ご覧ください。
iex async_word_count.exs Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] Found 9 occurrence(s) of the word in file "/data/OGENRI/serdce.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/businessmen.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/four.txt" Found 1 occurrence(s) of the word in file "/data/OGENRI/choose.txt" Found 1 occurrence(s) of the word in file "/data/OGENRI/light.txt" Found 10 occurrence(s) of the word in file "/data/OGENRI/prevr.txt" Found 1 occurrence(s) of the word in file "/data/OGENRI/r_linii.txt" Found 10 occurrence(s) of the word in file "/data/OGENRI/r_sixes.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/stihi.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/voice.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/ways.txt" Found 0 occurrence(s) of the word in file "/data/OGENRI/r_dl.txt" 32 Finished in 1.014 seconds Interactive Elixir (1.3.1) - press Ctrl+C to exit (type h() ENTER for help)
おわりに
冒険と馬はかつて切り離せないものでしたが、今ではおそらくそうではありません。
参照資料
» Http://elixir-lang.org/getting-started/processes.html
» Http://culttt.com/2016/07/27/understanding-concurrency-parallelism-elixir/
» Https://elixirschool.com/lessons/advanced/concurrency/
» コードおよびテキストファイル(OGENRIフォルダー)