lein uberjar приводит к "Слишком большой код метода!" - PullRequest
0 голосов
/ 25 апреля 2018

У меня есть проект clojure, который работает нормально, используя lein run, но lein uberjar приводит к

java.lang.RuntimeException: Method code too large!, compiling:(some_file.clj:1:1)

для того же проекта. Я мог бы отследить его до some_file.clj , используя большой вектор из более чем 2000 записей, который определен примерно так:

(def x
  [[1 :qwre :asdf]
   [2 :zxcv :fafa]
   ...
])

При удалении достаточного количества записей из этого вектора, lein uberjar компилируется без проблем. Как я могу заставить uberjar выполнять свою работу, оставляя все записи в векторе?

N.B. При изменении x на постоянную а-ля (def ^:const x, lein run также вызовет слишком большой метод ! Ошибка. И, между прочим, эта ошибка возникает в том месте, где x используется используется , поэтому просто определить константу можно, если она не используется.

Ответы [ 3 ]

0 голосов
/ 25 апреля 2018

Оказывается, что ранняя компиляция была разницей между lein run и lein uberjar, так как мой project.clj содержит строку (по умолчанию)

:profiles {:uberjar {:aot :all}})

, когда я удаляю :aot :all, uberjar заканчивается без жалоб - но также и без компиляции.Так что я думаю, мне нужно отключить aot или придумать, чтобы ограничить его, чтобы исключить этот вектор.

Но я бы хотел иметь этот, хотя и огромный, вектор "literal", и все же компилироватьвсе.Но, возможно, включение этого огромного вектора в файл данных, который читается при запуске, тоже не плохая идея.Тогда я смогу оставить настройку :aot :all как есть.

0 голосов
/ 25 апреля 2018

Существует ограничение в 64 КБ на размер метода в Java.Кажется, что в вашем случае метод, который создает большой векторный литерал, превышает этот предел.

На самом деле, вы можете проверить это самостоятельно, используя необычную библиотеку под названием clj-java-decompiler.Вот краткий пример использования boot:

(set-env!
 :dependencies '[[com.clojure-goes-fast/clj-java-decompiler "0.1.0"]])

(require '[clj-java-decompiler.core :as d])

(d/decompile-form
 {}
 [[1 :qwre :asdf]
  [2 :zxcv :fafa]
  [3 :zxcv :fafa]])

;; ==> prints:
;;
;; // Decompiling class: cjd__init
;; import clojure.lang.*;
;; 
;; public class cjd__init
;; {
;;  public static final AFn const__10;
;;  
;;  public static void load() {
;;                             const__10;
;;                             }
;;  
;;  public static void __init0() {
;;                                const__10 = (AFn)Tuple.create((Object)Tuple.create((Object)1L, (Object)RT.keyword((String)null, "qwre"), (Object)RT.keyword((String)null, "asdf")), (Object)Tuple.create((Object)2L, (Object)RT.keyword((String)null, "zxcv"), (Object)RT.keyword((String)null, "fafa")), (Object)Tuple.create((Object)3L, (Object)RT.keyword((String)null, "zxcv"), (Object)RT.keyword((String)null, "fafa")));
;;                                }
;;  
;;  static {
;;          __init0();
;;          Compiler.pushNSandLoader(RT.classForName("cjd__init").getClassLoader());
;;          try {
;;               load();
;;               Var.popThreadBindings();
;;               }
;;          finally {
;;                   Var.popThreadBindings();
;;                   }
;;          }
;;  }
;; 

Как видите, есть метод __init0, который создает фактический объект Java для векторного литерала.Учитывая достаточное количество элементов в векторе, размер метода может легко превысить предел в 64 КБ.

Я думаю, что в вашем случае самое простое решение этой проблемы - поместить ваш вектор в файл, а затем slurp + прочитайте этот файл.Примерно так:

файл vector.edn:

[[1 :qwre :asdf]
 [2 :zxcv :fafa]
 ...
 ]

и затем в вашем коде:

(def x (clojure.edn/read-string (slurp "vector.edn"))
0 голосов
/ 25 апреля 2018

сработает ли в вашем случае верхнее значение в вызове delay, чтобы вычислить его при первом использовании?

Существуют ограничения на то, насколько большой метод может быть в файле классов Java, и каждая форма верхнего уровня в Clojure (обычно) создает класс.

Чтобы решить эту проблему, полезно организовать генерацию и сохранение констант:

  • в памяти путем вычисления их во время выполнения
  • в каталоге ресурсов, где они могут быть прочитаны во время выполнения
  • не генерируйте файл класса, не скомпилировав его заранее, это генерирует данные при запуске программы.

если вы используете будущее или создаете запомненную функцию для получения данных, вы будете вычислять их при запуске программы и сохранять их в памяти, а не в файле класса.

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

если вы отключите компиляцию AOT, тогда предел класса никогда не будет достигнут, потому что он будет вычислен во время загрузки при запуске программы.

...