この記事の続き
速度の問題
> yes | pv -r > /dev/null [31.0MiB/s]
> ./no | pv -r > /dev/null [85.6KiB/s]
自作のコマンドno
はこのようにyes
コマンドに比べて遅い。
英語の練習も兼ねて dev.to に同じ記事を投稿したところ、以下のようなコメントをいただけた。
you've better to use buffer for writing. Actually, yes command does not call write(2) for each lines.
なるほど、I/Oの回数が多いことが原因と思われるのでバッファを使うほうがよいとのこと。
改善してみる
IO List を使う
Elixir and IO Lists, Part 1: Building Output Efficiently
この記事によると、文字列を結合しながらIO.puts
に渡す場合、
結合された文字列を1つだけ渡すのではなく、結合前の文字列のパーツをリストに格納するほうがパフォーマンスが良いとのこと。
リスト内に同じ文字が格納されている場合、同じ文字は同じアドレスを参照するため効率がよいらしい。
バッファリングするようにしたものがこちら。
no.exs
defmodule No do @moduledoc """ Documentation for No. """ def main(args) do args |> parse_args() |> run() |> wait() end def say(args) do {_, _, _, debug} = args message = buffer(args) case debug do true -> IO.inspect({:debug, message, self()}) _ -> IO.puts(message) end say(args) end defp buffer(args) do {argv, process_num, _, debug} = args message = case debug do true -> "#{expletive(argv)} from process no.#{process_num}" _ -> expletive(argv) end buffer(args, message, []) end defp buffer(args, message, list) do # faster than append last # https://hexdocs.pm/elixir/List.html list = [message | list] cond do IO.iodata_length(list) >= 4096 -> list true -> buffer(args, message, list) end end defp expletive(_ = ''), do: "n\n" defp expletive(value), do: value defp parse_args(args) do {opts, argv, _} = OptionParser.parse( args, swithces: options_list(), aliases: aliases_list() ) {argv, opts} end defp options_list() do [ processes: :integer, debug: :boolean, ] end defp aliases_list() do [ p: :processes, d: :debug, ] end defp run(args) do {argv, opts} = args num = case opts[:processes] do nil -> 1 _ -> String.to_integer(opts[:processes]) end Enum.each((1..num), fn(n) -> spawn_link(No, :say, [{argv, n, self(), opts[:debug]}]) end) end defp wait(args) do wait(args) end end
やったこと
- List は要素を先頭に追加するほうが速いのでそのように
- 親プロセス側でメッセージを待つのをやめて各子プロセスで出力するように
測定する
before
> ./no | pv -r > /dev/null [85.6KiB/s]
after
> ./no | pv -r > /dev/null [ 802KiB/s]
SUCCESS 😇 😇 😇 😇 😇 😇 😇 😇 😇 😇
並列実行してみる
> ./no -p 4 | pv -r > /dev/null [1.33MiB/s]
単位が上がりましたね....これで無限にプロセスを増やせば....
> ./no -p 10000 | pv -r > /dev/null [5.00MiB/s] [3.16MiB/s] [1.84MiB/s] [ 458KiB/s] [ 466KiB/s] [ 569KiB/s] [ 724KiB/s] [ 805KiB/s] [1.27MiB/s] [6.17MiB/s] [15.3KiB/s] [1.13MiB/s]
ブレがすごい
まとめ
- IO List など速度改善の知見が得られた
- 自分で作ったものが速くなるのは単純に楽しい
- まだ
yes
には全然敵わないので速度改善の余地はありそう - ブレがすごい
ブレについては気が向いたら調べる
Github: GitHub - tokoyax/no: Say No!!!!

- 作者: DaveThomas
- 出版社/メーカー: オーム社
- 発売日: 2017/07/14
- メディア: Kindle版
- この商品を含むブログを見る
(参考)