3

我已将Scala 函数式编程linesGt1第 15 章开头的命令式行计数代码(请参阅 参考资料)翻译成使用scalaz-stream的解决方案(请参阅参考资料)。然而,它的表现并不是那么出色。命令式代码比我的 scalaz-stream 解决方案快大约 30 倍。所以我想我在做一些根本错误的事情。如何提高 scalaz-stream 代码的性能?linesGt2linesGt2

这是我的完整测试代码:

import scalaz.concurrent.Task
import scalaz.stream._

object Test06 {

val minLines = 400000

def linesGt1(filename: String): Boolean = {
  val src = scala.io.Source.fromFile(filename)
  try {
    var count = 0
    val lines: Iterator[String] = src.getLines
    while (count <= minLines && lines.hasNext) {
      lines.next
      count += 1
    }
    count > minLines
  }
  finally src.close
}

def linesGt2(filename: String): Boolean =
  scalaz.stream.io.linesR(filename)
    .drop(minLines)
    .once
    .as(true)
    .runLastOr(false)
    .run

def time[R](block: => R): R = {
  val t0 = System.nanoTime()
  val result = block
  val t1 = System.nanoTime()
  println("Elapsed time: " + (t1 - t0) / 1e9 + "s")
  result
}

time(linesGt1("/home/frank/test.txt"))        //> Elapsed time: 0.153122057s
                                              //| res0: Boolean = true
time(linesGt2("/home/frank/test.txt"))        //> Elapsed time: 4.738644606s
                                              //| res1: Boolean = true
}
4

1 回答 1

2

当您进行分析或计时时,您可以使用它Process.range来生成输入以将您的实际计算与 I/O 隔离开来。调整你的例子:

time { Process.range(0,100000).drop(40000).once.as(true).runLastOr(false).run }

当我第一次运行它时,我的机器上花了大约 2.2 秒,这似乎与您所看到的一致。在运行了几次之后,可能是在 JIT 之后,我一直在 0.64 秒左右,原则上,我看不出有任何理由为什么即使使用 I/O 也不能那么快(参见下面的讨论) .

在我的非正式测试中,scalaz-stream 的每个“步骤”的开销似乎约为 1-2 微秒(例如,尝试Process.range(0,10000).如果您有一个具有多个阶段的管道,那么整个流的每个步骤都将包含其他几个步骤。考虑最小化 scalaz-stream 开销的方法只是确保您在每个步骤中都做了足够的工作,以使 scalaz-stream 本身增加的任何开销相形见绌。这篇文章有更多关于这种方法的详细信息。行计数示例是一种最坏的情况,因为您几乎每一步都没有做任何工作,只是在计算步数。

所以我会尝试编写一个linesR每步读取多行的版本,并确保在 JIT 之后进行测量。

于 2013-09-18T14:26:25.550 回答