我有一个大型的scala代码库。 (https://opensource.ncsa.illinois.edu/confluence/display/DFDL/Daffodil%3A+Open+Source+DFDL)
这就像70K行的scala代码。我们在scala 2.11.7
开发变得越来越困难,因为编译 - 编辑 - 编译 - 测试 - 调试周期对于小的更改来说太长了。
增量重新编译时间可能是一分钟,而且没有启用优化。有时更长。而且没有在文件中编辑很多更改。有时,一个非常小的变化会导致大量的重新编译。
所以我的问题是:我可以通过组织代码来做些什么来改善编译时间?
例如,将代码分解为更小的文件?这会有帮助吗?
例如,更小的图书馆?
例如,避免使用隐含? (我们很少)
例如,避免使用特征? (我们有吨)
,例如,避免大量进口? (我们有吨 - 包边界在这一点上非常混乱)
或者我真的无能为力吗?
我觉得这个很长的编译是由于依赖性导致了一些大量的重新编译,而我正在考虑如何减少 false 依赖...但这只是一个理论< / p>
我希望其他人可以了解我们可能会做的某些事情,这会提高增量更改的编译速度。
答案 0 :(得分:6)
以下是scala编译器的各个阶段,以及略微编辑过的阶段 来自源代码的评论版本。请注意这一点 编译器在进行类型检查时非常重要 以及更像荒谬的转变。其他编译器 包括很多代码:优化,寄存器分配和 转换为IR。
一些顶级积分: 有很多树重写。每个阶段都倾向于在树中阅读 从上一阶段开始,将其转换为新树。符号,到 相反,在编译器的整个生命周期中保持有意义。所以 树有符号指针,反之则不然。代替 重写符号,新信息作为阶段附加到它们 进展。
以下是Global的各个阶段列表:
analyzer.namerFactory: SubComponent,
analyzer.typerFactory: SubComponent,
superAccessors, // add super accessors
pickler, // serializes symbol tables
refchecks, // perform reference and override checking,
translate nested objects
liftcode, // generate reified trees
uncurry, // uncurry, translate function values to anonymous
classes
tailCalls, // replace tail calls by jumps
explicitOuter, // replace C.this by explicit outer pointers,
eliminate pattern matching
erasure, // erase generic types to Java 1.4 types, add
interfaces for traits
lambdaLift, // move nested functions to top level
constructors, // move field definitions into constructors
flatten, // get rid of inner classes
mixer, // do mixin composition
cleanup, // some platform-specific cleanups
genicode, // generate portable intermediate code
inliner, // optimization: do inlining
inlineExceptionHandlers, // optimization: inline exception handlers
closureElimination, // optimization: get rid of uncalled closures
deadCode, // optimization: get rid of dead cpde
if (forMSIL) genMSIL else genJVM, // generate .class files
some work around with scala compiler
因此scala编译器必须比Java编译器做更多的工作,但是特别是有些东西会使Scala编译器变得非常慢,其中包括
A very nice writeup by Martin Odersky
此外,Java和Scala编译器将源代码转换为JVM字节码并进行非常少的优化。对于大多数现代JVM,一旦运行程序字节码,它就会转换为计算机体系结构的机器代码。正在运行。这称为即时编译。但是,对于即时编译,代码优化的级别很低,因为它必须很快。为避免重新编译,所谓的 HotSpot 编译器仅优化频繁执行的部分代码。
程序每次运行时可能会有不同的性能。在同一JVM实例中多次执行相同的代码片段(例如方法)可能会产生非常不同的性能结果,具体取决于特定代码是否在运行之间进行了优化。另外,测量某段代码的执行时间可能包括JIT编译器本身执行优化的时间,从而产生不一致的结果。
性能恶化的一个常见原因还有装箱和拆箱,当将原始类型作为参数传递给通用方法并且频繁GC时,会隐式发生。
在测量过程中有几种方法可以避免上述影响,它应该使用HotSpot JVM的服务器版本运行,它可以进行更积极的优化.Visualvm是分析JVM应用程序的绝佳选择。它是一个集成了几个命令行JDK工具和轻量级分析功能的可视化工具。但是scala abstracions非常复杂,不幸的是VisualVM还不支持这个。使用大量{{1}需要花费很长时间才能处理的分析机制。 }和exists
这些是Scala集合的方法,它们将谓词,谓词作为FOL,因此可以通过整个序列最大化性能。
同时使模块具有连贯性和较少依赖性是一种可行的解决方案。使用中间代码来取决于机器,并且各种架构提供了不同的结果。
另类:Typesafe发布了Zinc,它将快速增量编译器与sbt分开,并让maven /其他构建工具使用它。因此,使用带有scala maven插件的Zinc可以更快地编译。
一个简单的问题:给定一个整数列表,删除最大的整数。订购没有必要。
以下是解决方案的版本(我猜的平均值)。
forall
这是Scala惯用的,简洁的,并使用了一些不错的列表函数。这也是非常低效的。它遍历列表至少3或4次。
现在考虑这个类似Java的解决方案。这也是一个合理的Java开发人员(或Scala新手)会写的。
def removeMaxCool(xs: List[Int]) = {
val maxIndex = xs.indexOf(xs.max);
xs.take(maxIndex) ::: xs.drop(maxIndex+1)
}
完全非Scala惯用,非功能性,非简洁,但它非常有效。它只遍历列表一次!
因此,权衡也应该优先考虑,有时你可能不得不像java开发人员那样工作。
答案 1 :(得分:3)
可能有所帮助的一些想法 - 取决于您的案例和发展方式:
~compile
或由IDE提供。答案 2 :(得分:1)
您正在触及面向对象设计(过度工程)的主要问题之一,在我看来,您必须展平您的类 - 对象 - 特征层次结构并减少类之间的依赖性。制动包装到不同的jar文件,并将它们用作迷你库,这些库被冻结&#34;并专注于新的代码。
查看Brian Will的一些视频,他们提出反对OO过度工程的案例
即https://www.youtube.com/watch?v=IRTfhkiAqPw(你可以采取好处)
我不是100%同意他的意见,但这样可以防止过度工程化。
希望有所帮助。
答案 3 :(得分:0)
您可以尝试使用Fast Scala Compiler。
答案 4 :(得分:0)
除了(例如Faulted
注释)之类的次要代码改进之外,根据您的勇敢程度,您还可以使用Dotty,其中包括更快的编译时间。