我目前对学习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
答案 0 :(得分:6)
不是像在lad2025的回答中那样阅读整个文件,而是通过做两件事来获得良好的表现。首先,使用IO.binstream
构建文件行的流,但作为原始二进制(用于性能)。使用IO.stream
读取为UTF-8,因此在读取文件时会产生额外的转换成本。如果你需要UTF-8转换,那么它会变慢。此外,使用Stream.filter/2
和Stream.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