我可以做什么我的scala代码,所以它会更快地编译?

我有一个大的Scala代码库。 ( https://opensource.ncsa.illinois.edu/confluence/display/DFDL/Daffodil%3A+Open+Source+DFDL )

这就像70K行的scala代码。 我们在斯卡拉2.11.7

开发变得越来越困难,因为编译 – 编辑 – 编译 – testing – debugging周期对于小的变化来说太长了。

增量重新编译时间可以是一分钟,这是没有优化打开。 有时候更长。 这是没有编辑到文件中的很多更改。 有时候,一个非常小的变化会导致巨大的重新编译。

所以我的问题:我可以通过组织代码的方式来做什么,这将提高编译时间?

例如,将代码分解成更小的文件? 这会有帮助吗?

比如,更小的图书馆?

例如,避免使用implicits? (我们有很less)

例如,避免使用特质? (我们有吨)

例如,避免大量的import? (我们有很多 – 包的边界在这一点上是非常混乱的)

还是我真的没有什么可以做的呢?

我觉得这个非常长的汇编在某种程度上是由于依赖关系导致了大量的重新编译,而我正在考虑如何减less错误的依赖关系……但这只是一个理论

我希望别人能够阐明一些我们可能做的事情,这会提高编译速度,从而增加更改。

下面是scala编译器的阶段,以及源代码中他们的评论的略微编辑版本。 请注意,这种编译器在进行types检查和更像desugarings的转换方面非常不寻常。 其他编译器包括大量代码:优化,寄存器分配和翻译到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 

有些解决scala编译器

因此,scala编译器必须比Java编译器做更多的工作,但是特别是有一些使得Scala编译器急剧变慢的原因,包括

  • 隐式parsing 。 隐式的解决方法(例如,当您进行隐式声明时,试图find隐式值的scalac)在声明中的每个父范围上冒出来,这个search时间可能很长(特别是如果多次引用相同的隐式variables,在一些图书馆中一直沿着你的依赖链条宣告)。 如果考虑隐式特征分辨率和types类,编译时间就会变得更糟,而scalaz和没有形状的类库会大量使用。 也使用了大量的匿名类(即lambdas,块,匿名函数).Macros显然增加了编译时间。

    马丁·奥德斯基(Martin Odersky)写的一篇很好的文章

    此外,Java和Scala编译器将源代码转换成JVM字节码,并做了很less的优化。对于大多数现代JVM,一旦程序字节码运行,它就被转换为运行它的计算机体系结构的机器代码。 这就是所谓的即时编译。 代码优化的水平,即时编译低,因为它必须是快速的。 为了避免重新编译,所谓的HotSpot编译器只优化频繁执行的代码部分。

    一个程序每次运行时可能会有不同的性能。 在相同的JVM实例中多次执行同一段代码(例如一个方法)可能会给出非常不同的性能结果,具体取决于特定代码是否在运行之间进行了优化。 另外,测量某段代码的执行时间可能包括JIT编译器本身执行优化的时间,从而给出不一致的结果。

    性能恶化的一个常见原因是装箱和拆箱,当将基本types作为parameter passing给generics方法并频繁使用GC时,会发生隐式发生。

    在测量过程中,有几种方法可以避免上述的影响,比如应该使用HotSpot JVM的服务器版本来运行,这个版本会进行更积极的优化.Visualvm是分析JVM应用程序的一个很好的select。 它是一个集成了几个命令行JDK工具和轻量级分析function的可视化工具。但是scala的抽象是非常复杂的,不幸的是VisualVM还不支持这个。parsing机制需要很长时间来处理像使用很多exists和原因的原因是斯卡拉集合的方法,它将谓词,谓词带到FOL,从而可以传递整个序列使性能最大化。

    也使得模块更协调,更less依赖,是一个可行的解决scheme。注意到中间代码是机器依赖的,并且各种架构给出了不同的结果。

    另一种方法 :Typesafe发布了Zinc,它将快速增量编译器与sbt分开,并让Maven /其他构build工具使用它。 因此,使用Scala Maven插件的Zinc编译速度更快。

    一个简单的问题:给定一个整数列表,删除最大的一个。 订购是没有必要的。

下面是解决scheme的版本(我猜测的平均值)。

 def removeMaxCool(xs: List[Int]) = { val maxIndex = xs.indexOf(xs.max); xs.take(maxIndex) ::: xs.drop(maxIndex+1) } 

这是斯卡拉惯用,简洁,并使用一些不错的列表function。 这也是非常低效的。 它至less遍历三四次。

现在考虑这个,类似Java的解决scheme。 这也是一个合理的Java开发人员(或斯卡拉新手)会写。

 def removeMaxFast(xs: List[Int]) = { var res = ArrayBuffer[Int]() var max = xs.head var first = true; for (x <- xs) { if (first) { first = false; } else { if (x > max) { res.append(max) max = x } else { res.append(x) } } } res.toList } 

完全非Scala惯用,非function,非简明,但它是非常有效的。 它只经历一次列表!

所以权衡也应该优先考虑,有时你可能不得不像Java开发人员那样工作。

你碰触到面向对象devise的主要问题之一(在工程上),我认为你必须压扁你的类对象特性层次并减less类之间的依赖关系。 将程序包制动到不同的jar文件,并将它们用作“冻结”的小型库,专注于新代码。

请查看Brian Will的一些video,他会对OO过度devise提出异议

https://www.youtube.com/watch?v=IRTfhkiAqPw (你可以把好点)

我不完全同意他的观点,但是对于过度工程来说,这是一个很好的例子。

希望有所帮助。

一些想法可能有所帮助 – 取决于你的情况和发展风格:

  • 使用增量编译~compile在SBT中~compile或由IDE提供。
  • 使用sbt-revolver ,也许JRebel更快地重新加载你的应用程序。 更适合networking应用程序。
  • 使用TDD – 而不是运行和debugging整个应用程序写testing,只运行这些。
  • 把你的项目分解成库/ JAR。 通过您的构build工具将它们用作依赖项:SBT / Maven /等。 或者接下来的变化
  • 把你的项目分解成子项目(SBT)。 如果你需要的一切,分别编译需要什么或根项目。 增量编译仍然可用。
  • 把你的项目分解成微服务。
  • 等待Dotty在一定程度上解决你的问题。
  • 如果一切都失败了,请不要使用高级的Scala特性来减慢编译速度:implicits,metaprogramming等等
  • 不要忘记检查你是否为Scala编译器分配了足够的内存和CPU。 我还没有尝试过,但也许你可以使用RAM磁盘而不是HDD来源和编译工件(在Linux上很容易)。

您可以尝试使用Fast Scala编译器 。

除了小的代码改进之外(比如@tailrec注释),取决于你感觉多么勇敢,你也可以玩弄Dotty ,它拥有更快的编译时间等等。