我应该如何让我的解析器并发?

时间:2014-01-15 21:32:57

标签: multithreading clojure concurrent-programming reference-type

我正在Clojure中实现一个音乐编程语言解析器。我们的想法是使用文本文件作为命令行参数运行解析器程序;文本文件包含我正在开发的这种音乐语言的代码;解析器解释代码并确定声明了什么“工具实例”,并且对于每个工具实例,它解析代码并返回一系列音乐“事件”(音符,和弦,休止符等)。 。因此,在最后一步之前,我们有多个“音乐代码”字符串,每个乐器实例一个字符串。

我对Clojure有点新鲜,并且仍在学习如何使用引用类型和线程/并发性的细微差别。我的解析器将进行一些复杂的解析,因此我认为使用并发来提高性能会受益。以下是我的问题:

  1. 最简单的方法似乎是在初始解析(单线程操作)“分割”乐器之后保存并发性,然后解析每个乐器的代码同时使用不同的线程(而不是等待每个工具完成解析,然后再移动到下一个)。我是在正确的轨道上,还是有更有效和/或逻辑的方式来构建我的“并发计划”?

  2. 从性能或代码维护的角度来看,我有哪些选项可以实现这种并发解析,哪种方法可能效果最好?看起来它可能就像:(map #(future (process-music-code %)) instrument-instances)一样简单,但我不确定是否有更好的方法可以像使用代理或通过Java互操作的手动线程那样做,或者是什么。我是并发编程的新手,所以不同方法的任何输入都会很棒。

  3. 从我读过的内容来看,Clojure的引用类型似乎在并发编程中起着重要的作用,我可以看到原因,但是在使用多个线程时是否总是需要使用它们?我是否应该担心我的一些数据是否可变?如果是这样,我正在编写的解析器的代码中应该是什么特别可变的?什么参考类型最适合我正在做的事情?我的程序工作方式的本质(用户以文本文件作为参数运行程序 - 程序处理它并将其转换为音频)使得看起来我不需要任何可变的东西,因为输入数据永远不会改变,所以我的直觉告诉我我不需要使用任何引用类型,但是再一次,我可能不完全理解引用类型和Clojure中的并发之间的关系。

3 个答案:

答案 0 :(得分:6)

我建议你过早优化可能会让自己分散更重要的事情(比如制定音乐语言的细节)。最好编写最简单,最容易编码的解析器,您可以先启动并运行它。 如果你觉得它太慢了,那么你可以看看如何优化以获得更好的性能。

解析器应该是相当自包含的,并且可能不会占用大量代码,因此即使您稍后将其丢弃并重写它,也不会是一个很大的损失。编写第一个解析器的经验将有助于编写第二个解析器。

其他要点:

你对引用类型绝对正确 - 你可能不需要任何引用类型。你的程序是一个编译器 - 它需要输入,转换它,写入输出,然后退出。这是纯函数式编程的理想情况,没有任何可变性,所有数据流纯粹通过函数参数和返回值。

使用解析器生成器通常是获得有效解析器的最快方法,但我还没有为Clojure找到一个非常好的解析器生成器。 Parsley有一个非常好的API,但它会生成LR(0)解析器,对于每个“section”的开头/结尾都没有明确,明确的标记的任何东西几乎都没用。 (就像S表达式用parens打开和关闭一样。)那里有几个解析器组合库,比如squarepeg,但我不喜欢他们的API,而是喜欢用自己编写的手写编码,递归下降解析器我自己实现的解析器组合器。 (它们并不快,但代码读得非常好。)

答案 1 :(得分:3)

我只能支持Alex Ds指出编写解析器是一个很好的练习。你绝对应该在C中做到这一点。根据我自己的经验,至少有很多调试培训。

除此之外,鉴于您处于Clojure的美丽世界,请注意以下事项:

  • 您的解析器会将普通字符串转换为数据结构,例如

    {:command :declare, :args {:name "bazooka-violin", ...}, ...}

  • 在Clojure中,您可以轻松地从EDN文件中读取此类数据结构。可能更有价值的方法是在你过度地限制语言的语法之前直接寻找合适的结构,以便以后灵活地改变语言的工作方式。

  • 不要考虑为性能而写作。除非您的用户在文件中描述了Bach的收集工作,否则解析时间不会超过一秒。

  • 如果您以功能性,模块化和简洁的方式编写解释器,则应该很容易将其分解为可以使用pmapcore.reducers中的各种技术并行化的步骤。当然,所有其他代码和解析器也是如此(如果需要多线程)。

  • 即使Clojure也不是并行编译的。但是它支持重新编译(在JVM上),相反,这是一种更有价值的思考方式。

答案 2 :(得分:0)

顺便说一句,我一直在阅读The Joy of Clojure,我刚刚了解到有一个漂亮的clojure.core函数叫做pmap(并行映射),它提供了一种简单易用的方法并行操作数据序列。它的语法就像map,但区别在于它并行执行序列中每个项目的函数,并返回结果的延迟序列!这通常可以提高性能,但它取决于协调序列结果的固有性能成本,因此pmap是否会提高性能将取决于具体情况。

在我的MPL解析器的这个阶段,我的计划是map对一系列乐器/音乐数据的功能,将每个乐器的音乐数据从解析树转换为音频。我不知道这种转换会有多么昂贵,但如果事实证明每个乐器单独生成音频需要一段时间,我想我可以尝试将map更改为pmap并查看如果这可以提高性能。