Подтвердить что ты не робот

Что я могу сделать с моим кодом scala, чтобы он быстрее скомпилировался?

У меня большая база кода scala. (https://opensource.ncsa.illinois.edu/confluence/display/DFDL/Daffodil%3A+Open+Source+DFDL)

Он похож на 70K строк кода scala. Мы находимся на scala 2.11.7

Развитие становится трудным, потому что компиляция - цикл редактирования-компиляции-теста-отладки слишком длинный для небольших изменений.

Инкрементное время перекомпиляции может быть минутой, и это без оптимизации. Иногда дольше. И это не редактировало очень много изменений в файлы. Иногда очень небольшое изменение вызывает огромную перекомпиляцию.

Итак, мой вопрос: что я могу сделать, организуя код, который улучшит время компиляции?

Например, разложение кода на более мелкие файлы? Это поможет?

Например, более маленькие библиотеки?

Например, избегая использования имплицитов? (у нас очень мало)

Например, избегая использования признаков? (у нас есть тонны)

Например, избегая большого количества импорта? (у нас есть тонны - границы пакетов в этот момент довольно хаотичны)

Или я действительно ничего не могу с этим поделать?

Мне кажется, что эта очень длинная компиляция каким-то образом обусловлена ​​некоторой огромной суммой перекомпиляции из-за зависимостей, и я думаю о том, как уменьшить ложные зависимости... но это просто теория

Я надеюсь, что кто-то еще может пролить свет на то, что мы можем сделать, что улучшит скорость компиляции для инкрементных изменений.

4b9b3361

Ответ 1

Вот этапы компилятора scala, а также слегка отредактированные версии их комментариев из исходного кода. Обратите внимание, что это компилятор необычен в том, что он сильно взвешен для проверки типа и к преобразованиям, которые больше похожи на десурагирование. Другие компиляторы включают много кода для: оптимизации, распределения регистров и перевод в ИК.

Некоторые точки верхнего уровня:Существует много переписывания деревьев. Каждая фаза имеет тенденцию читать в дереве от предыдущего этапа и преобразовать его в новое дерево. Символы, чтобы контрастность, остаются значимыми на протяжении всей жизни компилятора. Так деревья имеют указатели на символы, а не наоборот. Вместо переписывание символов, новая информация присоединяется к ним как фазы прогресс.

Вот список фаз из 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 значительно медленнее, включая

  • Неявное разрешение. Неявное разрешение (т.е. Скаляр, пытающееся найти неявное значение при создании неявного объявления) пузырится над каждой родительской областью в объявлении, это время поиска может быть массивным (особенно если вы ссылаетесь на одну и ту же неявную переменную много раз, а ее объявленные в некоторой библиотеке вплоть до вашей последовательности зависимостей). Время компиляции становится еще хуже, когда вы принимаете во внимание неявное распознавание признаков и классы типов, которые в значительной степени используются библиотеками, такими как scalaz и бесформенными. Также используется огромное количество анонимных классов (т.е. Lambdas, блоки, анонимные функции). Макросы, очевидно, добавляют к времени компиляции.

    Очень приятная запись Мартина Одерского

    Далее компиляторы Java и scala преобразуют исходный код в байт-код JVM и делают очень мало оптимизации. На большинстве современных JVM, как только запущен программный байт-код, он преобразуется в машинный код для компьютерной архитектуры, на которой он запустить. Это называется компиляцией "точно в срок". Однако уровень оптимизации кода, однако, невысок с компиляцией "точно вовремя", поскольку он должен быть быстрым. Чтобы избежать повторной компиляции, так называемый HotSpot компилятор только оптимизирует части кода, которые выполняются часто.

    Программа может иметь разную производительность при каждом запуске. Выполнение одного и того же фрагмента кода (например, метода) несколько раз в одном экземпляре JVM может дать очень разные результаты производительности в зависимости от того, был ли оптимизирован конкретный код между прогонами. Кроме того, измерение времени выполнения некоторого фрагмента кода может включать в себя время, в течение которого сам компилятор JIT выполнял оптимизацию, что давало противоречивые результаты.

    Одной из распространенных причин ухудшения производительности является также бокс и unboxing, которые происходят неявно при передаче примитивного типа в качестве аргумента в общий метод, а также частые GC.

    Существует несколько подходов, чтобы избежать вышеупомянутых эффектов во время измерения, например It запускаться с использованием серверной версии JSM HotSpot, которая делает более агрессивную оптимизацию. Visualvm - отличный выбор для профилирования приложения JVM. Это визуальный инструмент, объединяющий несколько инструментов JDK командной строки и облегченные возможности профилирования. Однако абстракции scala очень сложны и, к сожалению, VisualVM еще не поддерживает this.parsing механизмы, которые занимали много времени, чтобы обрабатывать как причина, используя много exists и forall, которые являются методами коллекций scala, которые принимают предикаты, предикаты к FOL и, таким образом, могут передавать полную последовательность, максимизирующую производительность.

    Кроме того, создание модулей cohisive и менее зависимым является жизнеспособным решением. Помните, что промежуточный код gen somtimes зависит от машины, и различные архитектурные заставки дают разнообразные результаты.

    Альтернатива: Typesafe выпустил Zinc, который отделяет быстрый инкрементный компилятор от sbt и позволяет использовать maven/другие инструменты сборки. Таким образом, использование цинка с плагином scala maven сделало компиляцию намного быстрее.

    Простая задача: учитывая список целых чисел, удалите самый большой. Заказ не требуется.

Ниже приведена версия решения (в среднем, я думаю).

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

Он 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
}

Полностью не-w610 > идиоматический, нефункциональный, нечеткий, но очень эффективный. Он перемещает список только один раз!

Таким образом, компромиссы также должны быть приоритетными, и иногда вам, возможно, придется работать над такими вещами, как разработчик java, если нет.

Ответ 2

Вы касаетесь одной из основных проблем объектно-ориентированного проектирования (над инженерными), на мой взгляд, вам нужно сгладить иерархию объектов класса и снизить зависимость между классами. Упакуйте пакеты в разные файлы jar и используйте их как мини-библиотеки, которые "заморожены" и сконцентрированы на новом коде.

Проверьте некоторые видео также от Брайана Уилла, который делает дело против OO over-engineering

i.e https://www.youtube.com/watch?v=IRTfhkiAqPw (вы можете воспользоваться хорошими точками)

Я не согласен с ним на 100%, но это хороший аргумент в пользу чрезмерной инженерии.

Надеюсь, что это поможет.

Ответ 3

Некоторые идеи, которые могут вам помочь, зависят от вашего случая и стиля разработки:

  • Используйте инкрементную компиляцию ~compile в SBT или предоставленную вашей IDE.
  • Используйте sbt-revolver и, возможно, JRebel быстрее перезагрузит ваше приложение. Лучше подходит для веб-приложений.
  • Используйте TDD - вместо того, чтобы запускать и отлаживать все тесты записи приложений и запускать их только.
  • Разбейте свой проект на библиотеки /JAR. Используйте их как зависимости через свой инструмент сборки: SBT/Maven/etc. Или вариант следующего...
  • Разбейте свой проект на подпроекты (SBT). Скомпилируйте отдельно то, что нужно, или проект root, если вам нужно все. Инкрементальная компиляция по-прежнему доступна.
  • Разбейте свой проект на микросервисы.
  • Подождите, пока Dotty решит вашу проблему в некоторой степени.
  • Если все не удается, не используйте расширенные функции Scala, которые замедляют компиляцию: implicits, metaprogramming и т.д.
  • Не забудьте проверить, что вы выделяете достаточно памяти и процессора для своего компилятора Scala. Я не пробовал, но, возможно, вы можете использовать RAM-диск вместо HDD для своих источников и компилировать артефакты (легко в Linux).

Ответ 4

Вы можете попытаться использовать Fast Scala Compiler.

Ответ 5

Помимо незначительных улучшений кода, таких как (например, @tailrec аннотации), в зависимости от того, насколько вы храбры, вы также можете играть с Dotty, который может похвастаться более быстрым временем компиляции между прочим.