使用xmerl读取大型XML文件会导致节点崩溃

时间:2016-08-18 12:21:55

标签: elixir

我有以下代码读取维基百科转储文件(~50 GB)并根据要求提供页面:

defmodule Pages do
  def start_link(filename) do
    pid = spawn_link(__MODULE__, :loop, [filename])
    Process.register(pid, :pages)
    pid
  end

  def next(xml_parser) do
    send(xml_parser, {:get_next, self()})
    receive do
      {:next_page, page} -> page
    end
  end

  def loop(filename) do
    :xmerl_sax_parser.file(filename,
      event_fun: &event_fun/3,
      event_state: :top)
    loop_done
  end

  defp loop_done do
    receive do
      {:get_next, from} -> send(from, {:next_page, nil})
    end
    loop_done
  end

  defp event_fun({:startElement, _, 'page', _, _}, _, :top) do
    :page
  end

  defp event_fun({:startElement, _, 'text', _, _}, _, :page) do
    :text
  end

  defp event_fun({:characters, chars}, _, :text) do
    s = List.to_string(chars)
    receive do
      {:get_next, from} -> send(from, {:next_page, s})
    end
    :text
  end

  defp event_fun({:endElement, _, 'text', _}, _, :text) do
    :page
  end

  defp event_fun({:endElement, _, 'page', _}, _, :page) do
    :top
  end

  defp event_fun({:endDocument}, _, state) do
    receive do
      {:get_next, from} -> send(from, {:done})
    end
    state
  end

  defp event_fun(_, _, state) do
    state
  end
end

由于代码使用SAX解析器,我希望内存占用空间不变。当我尝试使用

读取前2000页时
Enum.each(1..2000, fn(x) -> Pages.next(Process.whereis(:pages)); end)

:pages进程根据1,1 GB使用:observer.start()内存。当我尝试阅读10000页时,整个事情崩溃了:

Crash dump is being written to: erl_crash.dump...done
eheap_alloc: Cannot allocate 5668310376 bytes of memory (of type "heap").

当我使用dump viewer打开erl_crash.dump时,我看到以下内容: enter image description here

上面的代码有问题吗? GC不够快吗?虽然我可以看到每个进程的内存,但它并没有告诉我很多。我怎么能看到这个记忆的实际位置呢?

P.S。以下是今天崩溃转储的链接:https://ufile.io/becba。 原子数为14490,MsgQ:pages为2,所有其他进程为0。

1 个答案:

答案 0 :(得分:2)

默认的最大原子数略高于1 million atoms。鉴于英语维基百科has over 5 million articlesxmerl seems to create an atom for each namespace URI,我认为这可能是罪魁祸首。

此外,在Elixir上尝试下面的代码失败,只有#34;堆栈粉碎错误"。

Enum.each(1..2000000, fn (x) ->
  x
  |> Integer.to_string
  |> String.to_atom
end) 

但是如果我使用环境变量ELIXIR_ERL_OPTIONS="+t 5000000"将原子限制提高到500万,那么问题就会消失。