Эффективно ли Clojure описывает примитивные операции?

Предполагая, что у меня есть следующий код Clojure:

(defn foo ^double []
  (- 
    (* 123.31
     (+ 4 5 6 (Math/sin 34.2))
     123.31) 
    123))

Будет ли gen-class производить байт-код, эквивалентный компиляции следующего Java-кода:

public static double foo(){
   return (123.31 * (4 + 5 + 6 + Math.sin(34.2)) * 123.31) - 123;
}

Или, другими словами, я могу использовать Clojure как очень удобный DSL для создания эффективного динамического байтового кода?

Редактировать:

Хорошо, я сделал несколько тестов, чтобы проиллюстрировать мою проблему:

Вот версия Java:

public class FooTest {

  public static double foo(double a, double b, double c){
    return (a * (b + c + (b*c) + Math.sin(a)) * Math.log(b)) - b;
  }

  public static long benchmark(){
    long start = System.currentTimeMillis();
    for (double i = 0; i < 100000000.0; i++) { // 100 mln
      double r = foo(i, i+1, i+2);
    }
    long end = System.currentTimeMillis();
    return (end-start);
  }

  public static void main(String[] args) {
    System.out.println("Time took: "+benchmark());
  }
}

Это производит вывод: Время заняло: 39200

Clojure "эквивалент":

(defn foo ^double 
  (^double [a b c]
  (- 
    (* a
     (+ b c (* b c) (Math/sin a))
     (Math/log b)) 
    b)))

(time
  (loop [i 0.0] 
    (when (< i 100000000)   
      (foo i (+ i 1) (+ i 2))
      (recur (inc i)))))

Что дает: "Прошедшее время: 121242,902 мсек"

Что в 3 раза медленнее.

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

Edit2:

Я изменил тест, чтобы он использовал непроверенные примитивные математические операторы:

(defn foo ^double 
  (^double [a b c]
  (binding [*unchecked-math* true] 
    (- 
      (* a
       (+ b c (* b c) (Math/sin a))
       (Math/log b)) 
      b))))

"Истекшее время: 64386,187 мсек" Так что это почти в 2 раза лучше, но все еще в 1,6 раза больше версии Java.

2 ответа

Решение

Хорошо, я наконец-то получил ту же производительность Clojure, что и Java. Нужно изменить три вещи:

  1. Правильный намек на аргументы функции (ранее я намекал на возвращаемое значение, а не на аргументы функции)
  2. Я переместил привязку из тела функции.
  3. Использование непроверенных математических операций в помощнике по микропроцессорам

Полученный код:

(binding [*unchecked-math* true]   
  (defn foo ^double [^double a ^double b ^double c]  
    (- 
      (* a
         (+ b c (* b c) (Math/sin a))
         (Math/log b)) 
      b)))

(binding [*unchecked-math* true]   
  (time
    (loop [i (double 0.0)] 
      (when (< i 100000000)   
        (foo i (+ i 1) (+ i 2))
        (recur (inc i))))))

Это немного больше, чем просто компилятор Clojure, потому что JVM и JIT горячей точки также могут оптимизировать код. Компилятор Clojure создает примитивные математические выражения, когда все значения примитивны, а любые переменные имеют подсказки примитивного типа. после этого оптимизатор Hotspot выполняет встраивание, когда код работает на JVM.

PS: использовать или не использовать gen-class не имеет значения в этом процессе. Весь код Clojure компилируется и выполняется одинаково, за исключением того, что gen-class также вызывает создание файла, содержащего байт-код

Другие вопросы по тегам