2013-06-28 4 views
2

Предполагая, что у меня есть следующий Clojure код:Действительно ли Clojure эффективно выполняет примитивные операции?

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

Will генераторной класса производят байт-код эквивалентен компиляции следующего кода Java:

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

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

Edit:

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

Вот версия 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

«эквивалент» кряжа:

(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 раза медленнее.

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

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.

ответ

0

Хорошо, я наконец получил идентичную производительность 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)))))) 
2

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

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

+0

Компилятор выводит только примитивные математические операции при использовании непроверенных операции, таких как 'незарегистрированная-добавить '. Если вы используете '+', он генерирует вызов 'clojure.lang.Numbers/add', который выполняет проверку переполнения. – amalloy

 Смежные вопросы

  • Нет связанных вопросов^_^