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

dam*_*uar 5 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
Run Code Online (Sandbox Code Playgroud)

由于代码使用SAX解析器,我期望内存占用量不变.当我尝试使用时首先阅读2000页

Enum.each(1..2000, fn(x) -> Pages.next(Process.whereis(:pages)); end)
Run Code Online (Sandbox Code Playgroud)

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

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

当我erl_crash.dump使用dump viewer 打开时,我看到以下内容: 在此输入图像描述

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

PS这是今天崩溃转储的链接:https://ufile.io/becba.原子数为14490,其中所有其他过程MsgQ为2 :pages和0.

小智 2

默认的最大原子数略高于100 万个原子。鉴于英语维基百科有超过 500 万篇文章,并且 xmerl 似乎为每个名称空间 URI 创建一个原子,我认为它可能是罪魁祸首。

另外,在 Elixir 上尝试下面的代码失败,只是出现“堆​​栈崩溃错误”。

Enum.each(1..2000000, fn (x) ->
  x
  |> Integer.to_string
  |> String.to_atom
end) 
Run Code Online (Sandbox Code Playgroud)

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