2015-07-13 2 views
5

В настоящее время я очень заинтересован в изучении Эликсира, и мой типичный подход к изучению нового языка - это создание с ним простых программ.Почему этот код Эликсира так медленно?

Так что я решил написать (очень простой) 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 

ответ

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 преобразования, но это может быть что «вытягивать» из файла построчно по телефону IO.read, вместо того, чтобы линии «толкнули» в ваши операции фильтрации/печати с использованием IO.stream|binstream, вы несете дополнительную стоимость. Я должен был бы взглянуть на источник Эликсира, чтобы знать наверняка, но приведенный выше код был довольно впечатляющим на моей машине (я искал 143kb-файл из базы данных часовых поясов Olson, не уверен, как это будет выполняться на файлах очень большого размера, как У меня нет хорошего файла с образцами).

+0

Благодарим за подробный ответ! Я обязательно попробую ваше предложение. Я использовал http://www.generatedata.com/ для генерации данных примера. Я все еще удивляюсь, почему это так медленно. Конечно, преобразование UTF-8 замедляет его, но я получил выход, как одна линия на 500 мс. – koehr

+0

Я действительно сомневаюсь, что это конверсия UTF-8. Работа с файлами через File.read очень медленная, так как каждая транзакция ввода-вывода является сообщением между процессами в elixir. Почти всегда быстрее читать один большой кусок, а затем анализировать этот единственный бинарный файл. –

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 
+0

Далеко быстрее. : timer.tc говорит от 2600 до 3000 мс, тогда как вариант биллинга от битвакера занимает от 6400 до 6900 мс. Теперь я буду играть с преобразованием utf-8, чтобы убедиться, что это источник этой оригинальной медлительности. – koehr

+0

Таким образом, использование File.steam! (Путь, [: utf8]) не изменило скорость вообще. Я до сих пор не понимаю, почему так медленно, что я сделал. Я вижу, что это медленнее, да. Но медленнее, чем в 500 мс между выводами каждой линии? – koehr

+0

Это действительно странно, так как «IO.stream» и «File.stream» делают то же самое («IO.stream» абстрагируется с использованием любого ввода ввода-вывода, но реализация чтения потока в основном идентична). Странно, что между решением Романа и моей будет такая большая разница, но я должен буду помнить об этом. 'File.stream!' Не имеет режима: utf8, но вам нужно передать ': raw', чтобы имитировать мою реализацию binstream', поскольку' File.stream! 'Делает преобразование UTF-8 по умолчанию из того, что я могу видеть. – bitwalker

Смежные вопросы