Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this Elixir code so slow?

Tags:

elixir

I'm currently very interested in learning Elixir and my typical approach of learning a new language is to build simple programs with it.

So I decided to write a (very simple) grep-like program (or module) which is shown here:

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

This is most probably not the most elegant way to do it and I'm also quite new to functional programming, so I didn't expect it to be super fast. But it is not only not fast, it is actually super slow. So slow that I probably did some super obvious mistake.

Can anyone tell me, what it is and how to make it better?

I typically test the code with a seperate .exs file like

case System.argv do
  [searchTerm, path] -> LineMatch.file(searchTerm, path)
  _ -> IO.puts "Usage: lineMatch searchTerm path"
end
like image 399
koehr Avatar asked Jul 13 '15 23:07

koehr


People also ask

Is Elixir faster than go?

As such, Go produces applications that run much faster than Elixir. As a rule, Go applications will run comparative to Java applications, but with a tiny memory footprint. Elixir, on the other hand, will typically run faster than platforms such as Ruby and Python, but cannot compete with the sheer speed of Go.

Is Phoenix fast Elixir?

Phoenix is a framework for Elixir, much like Rails is for Ruby; it is a toolset for building highly performant and scalable web applications. Phoenix is very fast, generating response times measured in microseconds, and its patterns provide a good balance of quick-to-build and easy-to-maintain code.

Is Elixir any good?

If you are still wondering whether Elixir is a good programming language or not, the answer is simple: It is a very functional and dynamic programming language as it is built on top of Ruby and Erlang. Since the language is so easy to work with and learn, it usually takes less than a week to get a hang of it.

What is Elixir programming good for?

Elixir is great for web applications of any size, web APIs (such as JSON or GraphQL), event-driven systems, distributed systems, internet of things, embedded systems, and much more.


2 Answers

Rather than reading in the whole file as in lad2025's answer, you can get good performance by doing two things. First, use IO.binstream to build a stream of the file's lines, but as raw binary (for performance). Using IO.stream reads as UTF-8, and as such incurs extra cost to do the conversion as you are reading the file. If you need UTF-8 conversion, then it's going to be slow. Additionally, applying the filtering and mapping operations using the Stream.filter/2 and Stream.map/2 functions prevents you from iterating over the lines multiple times.

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

I suspect the greatest issue with your code is the UTF-8 conversion, but it may be that by "pulling" from the file line by line by calling IO.read, rather than having the lines "pushed" to your filtering/printing operations using IO.stream|binstream, you are incurring some extra cost there. I'd have to look at Elixir's source to know for sure, but the above code was quite performant on my machine (I was searching a 143kb file from the Olson timezone database, not sure how it will perform on files of very large size as I don't have a good sample file handy).

like image 114
bitwalker Avatar answered Oct 24 '22 16:10

bitwalker


Using of File.stream! will be much more efficient. Try it:

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
like image 1
Roman Smirnov Avatar answered Oct 24 '22 14:10

Roman Smirnov