Мультиметоды clojure медленные по своей природе

Я просматривал Функция clojure.core перегруппирована:

(defn re-groups [^java.util.regex.Matcher m]
    (let [gc  (. m (groupCount))]
      (if (zero? gc)
        (. m (group))
        (loop [ret [] c 0]
          (if (<= c gc)
            (recur (conj ret (. m (group c))) (inc c))
             ret))))) 

И подумал, что было бы "лучше" переписать его как мультиметод:

(defmulti re-groups (fn [^java.util.regex.Matcher m] (.groupCount m)))
(defmethod re-groups 0 [m] (.group m))
(defmethod re-groups :default [m]
        (let [idxs (range (inc (.groupCount m)))]
             (reduce #(conj %1 (.group m %2)) [] idxs))) 

Тем не менее, сравнивая время, я был удивлен, увидев, что перезапись выполняется в 4 раза медленнее:

clojure.core: "Elapsed time: 668.029589 msecs"
multi-method: "Elapsed time: 2632.672379 msecs" 

Является ли это естественным результатом мультиметодов или здесь что-то еще не так?


person M Smith    schedule 31.08.2011    source источник
comment
Кажется, вы используете мультиметоды как своего рода конструкцию сопоставления с образцом. Это действительно не цель мультиметода, который является (мульти) диспетчеризацией. Думайте об этом как о надмножестве того, что возможно в объектно-ориентированных языках. Для однократной отправки (например, в обычном объектно-ориентированном программировании) используйте протоколы. Для сопоставления с образцом используйте это: github.com/swannodette/match   -  person Nicolas Buduroi    schedule 01.09.2011
comment
Кроме того, код clojure.core создан именно из соображений производительности, поэтому большинство других реализаций будут работать медленнее.   -  person Nicolas Buduroi    schedule 01.09.2011


Ответы (3)


Мультиметоды Clojure допускают полиморфное поведение во время выполнения, основанное на произвольных функциях диспетчеризации. Это очень удобно для построения специальных иерархий и абстракций, но за такую ​​гибкость приходится платить ударом по производительности. Возможно, вы захотите повторно реализовать свое решение с помощью протокола. Используйте мультиметоды только тогда, когда вам нужна полная гибкость типа среды выполнения.

person Julien Chastang    schedule 31.08.2011
comment
Я должен был рассмотреть протокол, но из документации The resulting functions dispatch on the type of their first argument, and thus must have at least one argument, и что необходимо, так это отправка value аргумента. - person M Smith; 31.08.2011

в целом все, что делает больше, займет больше времени. поскольку мультиметоды предлагают множество способов отправки, они займут больше времени, чем протоколы. Я должен ответить на ваш вопрос «да, по сравнению с протоколами».

На практике начните с протоколов и переходите к мультиметодам, когда это необходимо (и похоже, что они вам нужны). вы не можете сделать компьютер быстрее с помощью кода, но вы можете заставить его работать меньше

person Arthur Ulfeldt    schedule 31.08.2011

Я думаю, что причина, по которой ваша реализация с несколькими методами работает медленнее, может быть также связана с тем, что вы используете сокращение для ленивой последовательности (предоставленной диапазоном) вместо цикла/повторения для увеличивающегося индекса, который используется в clojure.core. Попробуйте скопировать часть цикла реализации clojure.core/re-groups во второй метод def и посмотрите, не увеличит ли это производительность.

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

Еще одна вещь, которую вы могли бы рассмотреть, - это возможность того, что замедление связано с отражением. Попробуйте установить для параметра *warn-on-reflection* значение true и посмотрите, жалуется ли он. Возможно, поможет еще одна подсказка стратегического типа.

person A. Levy    schedule 01.09.2011
comment
Я использовал это, чтобы получить тайминги: (time (count (map re-matches (cycle [#"[-+]?[0-9]+/([0-9])+" #"([-+]? [0-9]+)/([0-9])+" #"[-+]?[0-9]+/[0-9]+" #"hamster"]) (take 200000 (for [x (range) y (range x)] (str x "/" y)))))) - person M Smith; 02.09.2011