【问题标题】:Erlang word counter二郎字计数器
【发布时间】:2011-05-29 05:09:29
【问题描述】:

我正在尝试在 Erlang 中创建一个多线程(?)程序:

  1. 读取大文件 (600mb)
  2. 向一组创建的进程触发消息,其中包含从文件中读取的行
  3. 创建的进程处理单词并存储在哈希树中
  4. 创建的进程然后将哈希树返回给主进程
  5. master 打印树。

到目前为止,我想我已经完成了前三个...我无法弄清楚如何通过在每次插入时打印出每个密钥哈希对来测试我的树。

主线程:

-module(lineread).
-export([read/1]).

read(Wordlength) ->
    {ok, Input} = file:open("/home/ml/datasets/tweets/first60kTweets.txt", [read]),
    Threads = makeThreads(Wordlength),
    read_lines(Input, Threads).

read_lines(Input, Threads) ->
    case file:read_line(Input) of
    eof ->
        file:close(Input);
    {ok, Line} ->
        send_to_all(Threads, Line),
        read_lines(Input, Threads)
    end.

send_to_all(Threads, Line) ->
    lists:foreach(fun(Pid) ->
                  Pid ! {line, Line} end,
              Threads).

makeThreads(NumThreads) ->
     [ spawn(counter, run, [N]) || N <- lists:seq(1, NumThreads) ].

其他话题:

-module(counter).
-export([run/1]).

%%entry point for the code
run(K) ->
    loop(K, gb_trees:empty()).

%%loops for a recieved message         
loop(Size, Tree) ->
    receive
    {line, Line} ->
        %%io:format("~p~n", [Line]),
        Splits = re:split(Line, " "),
        NewTree = build_tree(Splits, Tree),
        loop(Size, NewTree);
    {char, Char} ->
        io:format("~p", [Char])
        %%loop(Size, )
    end.

%%puts the data into a Tree...
build_tree([], Tree) ->
    Tree;
build_tree([W|R], Tree) ->
    case gb_trees:is_defined(W, Tree) of
    true ->
        I = gb_trees:get(W, Tree),
        NewTree = gb_trees:update(W, I + 1, Tree),
        io:format(I),
        build_tree(R, NewTree);
    false ->
        NewTree = gb_trees:insert(W, 1, Tree),
        %%io:format("~p/~n"),
            build_tree(R, NewTree)
        end.

感谢您的帮助。

【问题讨论】:

    标签: erlang count word parallel-processing


    【解决方案1】:

    我发现您的程序有很多问题,但要回答您的问题,您需要学习如何使用 receive 以便让您的进程相互通信。我建议阅读前几章here - 特别是this one。话虽如此,以下是我关于代码的 cmets:

    • 知道最终目标是什么会很有帮助。该代码没有多大意义,尤其是因为从未使用过Size,因此所有进程都是相同的。
    • 目前还不清楚你为什么要生成任何东西。生成 W 进程并将每一行发送到所有进程是一个陷阱 - 由于 IO 开销(发送到每个进程时必须复制文本),这最终会比不生成慢得多。您最终将在它们之间发送类似 (W)(600MB)(2) 的内容。哎呀。
    • 您可能需要 dict 或 orddict 而不是 gb 树。见 dict:update_counter/3。另请参阅this 了解更多详情。使用 ets 表可能有意义,但我再次不确定目标是什么。
    • 在这种情况下,应该使用 string:tokens/2 而不是 re:split/2 - 您不需要 re 的开销来进行简单的空格分割。

    【讨论】:

    • 感谢您迄今为止的意见。目标是让每个进程返回每个给定单词长度的前 (K) 个单词,长度从 1 到 20 不等。因此,如果 K 是 20,那么它将返回前 20 个长度为 1 到 20 的单词
    • 另外,我真的应该将 cmets 放在我将使用 size 的代码中......
    • 是的,现在我明白了。我建议使用以下两种方法之一在单个进程中执行此操作:(1)使用尾递归函数循环遍历行,该函数将值存储到 gb_trees 的字典中(每个字长一棵树)。 (2)使用ets表:每个字长一张表——键是词,值是频率。第一个可能写起来有点棘手,但可能运行得更快,因为它避免了最后的全表扫描。在这两种情况下,您可能都希望存储二进制文件 (list_to_binary/1) 以节省内存。
    【解决方案2】:

    我没有

    您可以在循环中添加一条新消息,如下所示

    {打印} -> io:format("~p~n", gb_trees:to_list(Tree)), io:format("单词~p~n", gb_trees:size(Tree)), 循环(大小,新树);

    另外我不明白你为什么将每一行发送到所有进程..这只是为了练习?或者尝试构建可以快速计算单词的东西?这不会。

    我的一些建议: 您还可以使用 stringtokens 来拆分单词。 http://www.erlang.org/doc/man/string.html

    我会使用进程而不是线程这个词,因为 Erlang 声称它创建轻量级进程而不是线程 http://www.erlang.org/doc/efficiency_guide/processes.html

    【讨论】:

    • 感谢您的帮助。这只是为了练习,真的,所以如果它非常低效,不用担心。目标是让每个进程返回每个给定单词长度的前 (K) 个单词,长度从 1 到 20 不等。因此,如果 K 是 20,那么它将返回前 20 个长度为 1 到 20 的单词。
    【解决方案3】:

    最大的问题是这个练习的目的是什么?它是用于实际工作还是仅用于某种​​学习。如果它应该是真正的产品,你就不能很好地开始它。您的第一个问题应该是这些数据将来自哪里,然后您应该尝试确定瓶颈在哪里。在您的情况下,您谈论的是 600MB。将它保存在内存中的数据并不多。如果您必须从某些存储中读取它,您的解决方案将强烈依赖于该存储,因为从 CPU 的角度来看,如果您期望平均字长 10B 或 100Mwords 如果您期望平均 6B 字,则 600MB 的字可能约为 60Mwords。你能多快数出 100Mwords?如果您使用基于非常快的 k/v 解决方案,您可以在 1 秒内在一个商品 CPU 内核上完成。听起来很疯狂?不,这比第一眼看上去要容易。如果您的解决方案基于Judy Array 之类的东西或您自己手工制作的 trie 实现,如果您不害怕用 C 编码,那么您可以相对容易地获得这样的结果。那么,您能以多快的速度从数据存储中受够这头野兽呢?如果你使用商品磁盘,你可以在 6s 内读取数据!如果您将其调整得更好,您可以做得更好,但在可比较的处理时间内完成将是一个挑战。为什么你要让它平行?仅当您必须真正从存储中读取数据时,以上才是正确的。例如,当您读取之前由某个进程存储的数据时,情况并非如此。在这种情况下,您可以更快地访问此数据,因为它很有可能位于缓存中。在这种情况下,它将以非常不同的方式表现。例如,您可以随机访问数据。上述读取时序只能通过从经典磁盘顺序读取来实现。 SSD 的行为不同,但整体数据吞吐量并没有好几个数量级。因此,只有当你足够快地厌倦了你的 CPU 工作时,你为并行性所做的努力才值得。那么最大的问题将是如何厌倦数据并对其进行分区。如需灵感,您可以查看Tim Bray's Wide Finder Pro­ject。但请记住,第一个 Wide Finder 项目不是使用刷新的 IO 缓存来衡量的,因此可以从缓存中随机访问数据。即使在这种情况下,您也可以使用 Erlang,但不要忘记它的优势之一是与其他语言的交互,尤其是 C,我将向您指出 NIF。

    如果目的只是在 Erlang 中练习,我的问题是为什么要树?为什么不ets:update_counter/3?如果您认为 ets 不够快,请尝试使用进程字典而不是 gb_trees。当您在自己控制的过程中使用它时,它并没有那么脏。考虑在内存中读取整个文件,但使用二进制文件。大量使用binary 模块等等...

    【讨论】:

    • 这不是真正的工作,顺便说一句,这是为了练习。我想我会切换到 pdict 而不是 gb_trees。数据来自传统磁盘,因此将其读取到 RAM 是个好主意?
    • @Sam P:对于这种大小的数据,将其读入 RAM 几乎总是一个好主意。生成的代码应该简单得多。如果您查看 Wide Finder 项目的我的解决方案,如果您想一次保持顺序读取、低 RAM 使用率和并行处理,它会变得非常复杂。
    猜你喜欢
    • 2012-11-15
    • 1970-01-01
    • 1970-01-01
    • 2010-12-13
    • 2010-11-02
    • 2010-10-02
    • 2011-03-06
    • 1970-01-01
    • 2014-03-19
    相关资源
    最近更新 更多