Повышение производительности вычисления ограничивающей рамки облака точек в Clojure

Я вычисляю ограничивающую рамку трехмерного облака точек в Clojure. Облако точек представлено в виде примитивного массива с плавающей запятой Java, и каждая точка в облаке точек хранится с использованием 4 с плавающей запятой, где последняя с плавающей запятой не используется. Как это:

[x0 y0 z0 u0  x1 y1 z1 u1 .... ]

Размер облака точек 19200, то есть 4*19200 поплавков в массиве.

Некоторые из этих значений могут не быть конечными (либо они бесконечны, либо NaN). Таким образом, любая точка, которая содержит не конечное значение, должна быть полностью исключена из вычислений. Я реализовал это вычисление как на Java, так и на Clojure, но по какой-то причине версия Clojure все еще примерно в 4-5 раз медленнее.

Вот как выглядит код Clojure:

(defn good-compute-bbox [^floats data]
  (let [n (alength data)]
    (loop [i (int 0)
           minx (float (aget data 0))
           maxx (float (aget data 0))
           miny (float (aget data 1))
           maxy (float (aget data 1))
           minz (float (aget data 2))
           maxz (float (aget data 2))]
      (if (< i n)
        (let [x (float (aget data (unchecked-add i 0)))
              y (float (aget data (unchecked-add i 1)))
              z (float (aget data (unchecked-add i 2)))]
          (if (and (Float/isFinite x)
                   (Float/isFinite y)
                   (Float/isFinite z))
            (recur (unchecked-add-int i (int 4))
                   (min minx x)
                   (max maxx x)
                   (min miny y)
                   (max maxy y)
                   (min minz z)
                   (max maxz z))
            (recur (unchecked-add-int i (int 4))
                   minx
                   maxx
                   miny
                   maxy
                   minz
                   maxz
                   ))
          )
        [minx maxx miny maxy minz maxz]))))

и вот как выглядит код Java:

public class BBox {

    public static float[] computeBBox(float[] data) {
        long n = data.length;
        float[] result = new float[6];

        float minx = data[0];
        float maxx = data[0];
        float miny = data[1];
        float maxy = data[1];
        float minz = data[2];
        float maxz = data[2];
        for (int i = 0; i < n; i += 4) {
            float x = data[i + 0];
            float y = data[i + 1];
            float z = data[i + 2];
            if (java.lang.Float.isFinite(x) &&
                java.lang.Float.isFinite(y) &&
                java.lang.Float.isFinite(z)) {

                minx = x < minx? x : minx;
                maxx = x > maxx? x : maxx;

                miny = y < miny? y : miny;
                maxy = y > maxy? y : maxy;

                minz = z < minz? z : minz;
                maxz = z > maxz? z : maxz;
            }
        }

        result[0] = minx;
        result[1] = maxx;
        result[2] = miny;
        result[3] = maxy;
        result[4] = minz;
        result[5] = maxz;

        return result;
    }
};

У меня вопрос: какие изменения я могу внести в свой код Clojure, чтобы он работал так же быстро, как код Java? Бонусный балл, если вы протестировали свой код и измерили его ускорение.

Если вы хотите воспроизвести эксперимент и опубликовать его на моем Репозиторий Github здесь.


person Rulle    schedule 22.03.2018    source источник


Ответы (1)


Виновником здесь — как я вижу из вашего кода, который вы уже подозревали — является min. Он работает со всеми типами и не так быстро.

Вы можете получить в пределах 10% от Java, используя быструю математику Apache:

(defn primitive-compute-bbox-new [^floats data]
  (let [n (alength data)]
    (loop [i (int 0)
           minx (aget data 0)
           maxx (aget data 0)
           miny (aget data 1)
           maxy (aget data 1)
           minz (aget data 2)
           maxz (aget data 2)]
      (if (< i n)
        (let [x (aget data (unchecked-add i 0))
              y (aget data (unchecked-add i 1))
              z (aget data (unchecked-add i 2))]
          (if (and (Float/isFinite x)
                   (Float/isFinite y)
                   (Float/isFinite z))
            (recur (unchecked-add-int i 4)
                   (FastMath/min (float minx) x)
                   (FastMath/max (float maxx) x)
                   (FastMath/min (float miny) y)
                   (FastMath/max (float maxy) y)
                   (FastMath/min (float minz) z)
                   (FastMath/max (float maxz) z))
            (recur (unchecked-add-int i 4)
                   minx
                   maxx
                   miny
                   maxy
                   minz
                   maxz)))
        [minx maxx miny maxy minz maxz]))))

Отд:

[org.apache.commons/commons-math3 "3.6.1"]
person ClojureMostly    schedule 22.03.2018