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

Mik*_*rle 45 dependencies scala compilation

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

它就像70K行的scala代码.我们在scala 2.11.7上

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

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

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

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

例如,更小的图书馆?

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

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

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

或者我真的无能为力吗?

我觉得这个很长的编译在某种程度上是由于依赖性导致了大量的重新编译,我正在考虑如何减少错误依赖......但这只是一个理论

我希望其他人可以对我们可能做的事情有所了解,这将提高增量变更的编译速度.

kha*_*iab 6

以下是scala编译器的各个阶段,以及来自源代码的略微编辑的注释版本.请注意,此编译器在对类型检查以及更像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
Run Code Online (Sandbox Code Playgroud)

一些解决scala编译器

因此scala编译器必须比Java编译器做更多的工作,但是特别是有些东西会使Scala编译器变得非常慢,其中包括

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

    Martin Odersky的一篇非常好的文章

    此外,Java和Scala编译器将源代码转换为JVM字节码并进行非常少的优化.在大多数现代JVM中,一旦运行程序字节码,它就会转换为运行它的计算机体系结构的机器代码.这称为即时编译.但是,对于即时编译,代码优化的级别很低,因为它必须很快.为了避免重新编译,所谓的HotSpot编译器只优化频繁执行的部分代码.

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

    性能恶化的一个常见原因还有装箱和拆箱,当将原始类型作为参数传递给通用方法并且频繁使用GC时,会隐式发生.

    在测量过程中有几种方法可以避免上述影响,例如它应该使用HotSpot JVM的服务器版本运行,它可以进行更积极的优化.Visualvm是分析JVM应用程序的绝佳选择.这是一个可视化工具,整合多个命令行JDK工具和轻量级分析capabilities.However斯卡拉abstracions是非常复杂的,不幸的VisualVM还不支持其花费很长的时间使用大量的事业一样来处理this.parsing机制existsforall它们的方法Scala集合采用谓词,谓词为FOL,因此可以通过整个序列最大化性能.

    同时使模块具有连贯性和较少依赖性是一种可行的解决方案.使用中间代码来取决于机器,并且各种架构提供了不同的结果.

    替代方案:Typesafe发布了Zinc,它将快速增量编译器与sbt分开,并让maven /其他构建工具使用它.因此,使用带有scala maven插件的Zinc可以更快地编译.

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

下面是解决方案的版本(我猜的平均值).

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

这是Scala惯用,简洁,并使用一些很好的列表功能.这也是非常低效的.它遍历列表至少3或4次.

现在考虑这个类似Java的解决方案.这也是一个合理的Java开发人员(或Scala新手)会写的.

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
}
Run Code Online (Sandbox Code Playgroud)

完全非Scala惯用,非功能性,非简洁,但它非常有效.它只遍历列表一次!

因此,权衡也应该优先考虑,有时你可能不得不像java开发人员一样工作.


小智 0

您可以尝试使用Fast Scala Compiler