我可以对我的scala代码做什么,以便更快地编译?

时间:2016-11-04 00:57:17

标签: scala compilation dependencies

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

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

开发变得越来越困难,因为编译 - 编辑 - 编译 - 测试 - 调试周期对于小的更改来说太长了。

增量重新编译时间可能是一分钟,而且没有启用优化。有时更长。而且没有在文件中编辑很多更改。有时,一个非常小的变化会导致大量的重新编译。

所以我的问题是:我可以通过组织代码来做些什么来改善编译时间?

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

例如,更小的图书馆?

例如,避免使用隐含? (我们很少)

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

,例如,避免大量进口? (我们有吨 - 包边界在这一点上非常混乱)

或者我真的无能为力吗?

我觉得这个很长的编译是由于依赖性导致了一些大量的重新编译,而我正在考虑如何减少 false 依赖...但这只是一个理论< / p>

我希望其他人可以了解我们可能会做的某些事情,这会提高增量更改的编译速度。

5 个答案:

答案 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编译器变得非常慢,其中包括

  • 隐式解决方案。隐式解析(即scalac在进行隐式声明时试图找到隐含值)会在声明中的每个父作用域上冒泡,这个搜索时间可能很大(特别是如果你多次引用相同的隐式变量,并且在您的依赖链中一直在某些库中声明)。当您考虑隐式特征解析和类型类时,编译时间会变得更糟,这些类被scalaz和shapeless等库大量使用。 还使用了大量的匿名类(即lambda,块,匿名函数).Macros显然增加了编译时间。

    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)

可能有所帮助的一些想法 - 取决于您的案例和发展方式:

  • 在SBT中使用增量编译~compile或由IDE提供。
  • 使用sbt-revolver和JRebel可以更快地重新加载您的应用。更适合网络应用。
  • 使用TDD - 而不是运行和调试整个应用程序写入测试,只运行它们。
  • 将您的项目分解为库/ JAR。通过构建工具将它们用作依赖项:SBT / Maven / etc.或者下一个的变化......
  • 将您的项目分解为子项目(SBT)。如果您需要所有内容,请单独编译所需的内容或根项目。增量编译仍然可用。
  • 将您的项目分解为微服务。
  • 等待Dotty在某种程度上解决您的问题。
  • 如果一切都失败,请不要使用高级Scala功能,使编译速度变慢:暗示,元编程等。
  • 不要忘记检查是否为Scala编译器分配了足够的内存和CPU。我还没有尝试过,但也许你可以使用RAM磁盘而不是HDD作为你的资源和编译工件(在Linux上很容易)。

答案 2 :(得分:1)

您正在触及面向对象设计(过度工程)的主要问题之一,在我看来,您必须展平您的类 - 对象 - 特征层次结构并减少类之间的依赖性。制动包装到不同的jar文件,并将它们用作迷你库,这些库被冻结&#34;并专注于新的代码。

查看Brian Will的一些视频,他们提出反对OO过度工程的案例

https://www.youtube.com/watch?v=IRTfhkiAqPw(你可以采取好处)

我不是100%同意他的意见,但这样可以防止过度工程化。

希望有所帮助。

答案 3 :(得分:0)

您可以尝试使用Fast Scala Compiler

答案 4 :(得分:0)

除了(例如Faulted注释)之类的次要代码改进之外,根据您的勇敢程度,您还可以使用Dotty,其中包括更快的编译时间。