我正在将一些代码转换为Scala。它的代码位于一个内部循环中,有大量的数据,所以它需要快速,它涉及到在哈希表中查找键并计算概率。它需要根据是否找到一个键做不同的事情。使用"标准"习语,代码看起来像这样:
counts.get(word) match {
case None => {
WordDist.overall_word_probs.get(word) match {
case None => (unseen_mass*WordDist.globally_unseen_word_prob
/ WordDist.num_unseen_word_types)
case Some(owprob) => unseen_mass * owprob / overall_unseen_mass
}
}
case Some(wordcount) => wordcount.toDouble/total_tokens*(1.0 - unseen_mass)
}
,但我担心这种类型的代码将是非常缓慢的,因为所有这些临时的Some()对象被创建,然后被垃圾收集。Scala2e书声称智能JVM"可能"会优化这些问题,从而使代码在效率方面做正确的事情,但是在使用Sun的JVM时,这真的发生了吗?有人知道吗?
如果您在jvm中启用转义分析,并使用:
-XX:+DoEscapeAnalysis
在JRE 1.6上。从本质上讲,它应该检测正在创建的对象,这些对象没有逃离方法激活框架,然后在堆栈上分配它们,或者在它们不再需要时立即GC它们。
您可以做的一件事是使用scala.testing.Benchmark
特征对代码进行微基准测试。只需用一个单例对象扩展它,并实现run
方法,编译并运行它。它将多次运行run
方法,并测量执行时间。
是的,Some
对象将被创建(None
是一个单例)。当然,除非JVM忽略了这一点——这取决于许多因素,包括JVM是否认为代码被调用了那么多。
无论如何,我是这样重写的:
( counts
get word
map (_.toDouble / total_tokens * (1.0 - unseen_mass))
getOrElse (
WordDist.overall_word_probs
get word
map (unseen_mass * _ / overall_unseen_mass)
getOrElse (unseen_mass * WordDist.globally_unseen_word_prob
/ WordDist.num_unseen_word_types)
)
)
然后您可以重构它——两个getOrElse
参数可以用不同的方法拆分,并使用好名称。因为它们只是返回一个值而不需要输入,所以它们应该非常快。
现在,我们在Option
上只调用两个方法:map
和getOrElse
。下面是它们实现的开始:
@inline final def map
@inline final def getOrElse
由于getOrElse
的参数是通过名称传递的,因此它涉及匿名函数的创建。当然,map
的参数也是一个函数。除此之外,这些方法被内联的机会是相当大的。
所以,这是重构的代码,尽管我对它的了解不够,不能给它起个好名字。
def knownWordsFrequency = counts get word map computeKnownFrequency
def computeKnownFrenquency =
(_: Int).toDouble / total_tokens * (1.0 - unseen_mass)
def probableWordsFrequency = (
WordDist.overall_word_probs
get word
map computeProbableFrequency
)
def computeProbableFrequency = unseen_mass * (_: Double) / overall_unseen_mass
def unknownFrequency = (unseen_mass * WordDist.globally_unseen_word_prob
/ WordDist.num_unseen_word_types)
def estimatedWordsFrequency = probablyWordsFrequency getOrElse unknownFrequency
knownWordsFrequency getOrElse estimatedWordsFrequency