为什么这个Elixir代码这么慢?

时间:2015-07-13 23:19:39

标签: elixir

我目前对学习Elixir非常感兴趣,而我学习新语言的典型方法是用它来构建简单的程序。

所以我决定编写一个(非常简单的)类似grep的程序(或模块),如下所示:

defmodule LineMatch do
  def file(s, path) do
    case File.open(path, [:read]) do
      {:ok, fd} -> match s, fd
      {_, error} -> IO.puts "#{:file.format_error error}"
    end
  end
  defp match(s, fd) do
    case IO.read fd, :line do
      {:error, error} -> IO.puts("oh noez! Error: #{error}")
      line -> match(s, line, fd)
    end
  end
  defp match(s, line, fd) when line !== :eof do
    if String.contains?(line, s) do
      IO.write line
    end
    match(s, IO.read(fd, :line), fd)
  end
  defp match(_, _line, _) when _line === :eof do
  end
end

这很可能不是最优雅的方式,而且我对函数式编程也很陌生,所以我没想到它会超级快。但它不仅不快,实际上是超慢。这么慢,我可能做了一些非常明显的错误。

谁能告诉我,它是什么以及如何让它变得更好?

我通常使用单独的.exs文件测试代码,例如

case System.argv do
  [searchTerm, path] -> LineMatch.file(searchTerm, path)
  _ -> IO.puts "Usage: lineMatch searchTerm path"
end

2 个答案:

答案 0 :(得分:6)

不是像在lad2025的回答中那样阅读整个文件,而是通过做两件事来获得良好的表现。首先,使用IO.binstream构建文件行的流,但作为原始二进制(用于性能)。使用IO.stream读取为UTF-8,因此在读取文件时会产生额外的转换成本。如果你需要UTF-8转换,那么它会变慢。此外,使用Stream.filter/2Stream.map/2函数应用过滤和映射操作可以防止您多次迭代这些行。

defmodule Grepper do
  def grep(path, str) do
    case File.open(path) do
      {:error, reason} -> IO.puts "Error grepping #{path}: #{reason}"
      {:ok, file} ->
        IO.binstream(file, :line)
        |> Stream.filter(&(String.contains?(&1, str)))
        |> Stream.map(&(IO.puts(IO.ANSI.green <> &1 <> IO.ANSI.reset)))
        |> Stream.run
    end
  end
end

我怀疑你的代码最大的问题是UTF-8转换,但它可能是通过&#34; pull&#34;从文件中逐行调用IO.read,而不是让行&#34;推出&#34;使用IO.stream|binstream进行过滤/打印操作,会产生一些额外费用。我必须查看Elixir的来源才能确定,但​​上面的代码在我的机器上非常高效(我正在从Olson时区数据库中搜索143kb文件,不确定它将如何执行文件非常大,因为我没有方便的样本文件。

答案 1 :(得分:1)

使用File.stream!会更有效率。 试试吧:

defmodule Grepper do
  def grep(path, str) do
    File.stream!(path)
      |> Stream.filter(&(String.contains?(&1, str)))
      |> Stream.map(&(IO.puts(IO.ANSI.green <> &1 <> IO.ANSI.reset)))
      |> Stream.run
  end
end