Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Looking for a fast way to draw pixel by pixel and why this code 1000 times slower than java?

This is a simple code that just color the window 4 times.

Maybe there is something obvious that I don't see.

My goal is to learn computer graphic from scratch and I want to draw pixel by pixel to have full control. I'm looking for a fast way to do it.

Here is the full code.

The relevant clojure part:

(defmacro for-loop [[sym init check change :as params] & steps]
  `(loop [~sym ~init value# nil]
     (if ~check
       (let [new-value# (do ~@steps)]
         (recur ~change new-value#))
       value#)))
(time
 (for-loop
  [k 0 (< k 2) (inc k)]
  (for-loop
   [c 0 (< c 2) (inc c)]
   (for-loop
    [i 0 (< i width) (inc i)]
    (for-loop
     [j 0 (< j height) (inc j)]
     (aset ^ints @pixels (+ i (* j width)) (get cs c))))
   (.repaint canvas))))

The same code in java:

long t = System.currentTimeMillis();
for (int k = 0 ; k < 2; k++) {
  for (int c = 0; c < 2; c++) {
    for (int i = 0 ; i < width; i++) {
      for (int j = 0; j < height; j++) {
        pixels[i + j * width] = cs[c];
      }
    }
    repaint();
  }
}
System.out.println(System.currentTimeMillis() - t);
like image 540
raoof Avatar asked Apr 21 '18 13:04

raoof


1 Answers

There's a couple problems:

  • If you run lein check, you'll see reflection warnings. You're forcing reflection at runtime which can slow things down. I changed the canvas creation to:

    (defonce canvas (doto (proxy [Frame] []
                            (update [g] (.paint this g))
                            (paint [^Graphics2D g]
                              (.drawImage g, ^BufferedImage image, 0, 0 nil)))
                      (.setSize width height)
                      (.setBackground Color/black)
                      (.setFocusableWindowState false)
                      (.setVisible true)))
    

    Notice the type hints I'm using. It didn't know which overload of drawImage to use, and outright couldn't find the paint method.

  • The main issue however is the use of aset. From aset's docs:

    Sets the value at the index/indices. Works on Java arrays of reference types. Returns val.

    Emphasis mine.

    The problem is that aset doesn't work with primitives. It forces each number to be wrapped as an Integer, then unwrapped again when it's used in the image. This is quite expensive when multiplied over each pixel of an image.

    Change aset to aset-int to use int primitives instead. This takes the execution time down from roughly 20 seconds to half a second.


Honestly though, I can't get it any lower. It's much faster than it was, but is still about 20x slower than the Java version. I've been working on this for almost 2 hours now, and I've hit a wall. Hopefully someone else can squeeze the last bit of time out.

like image 125
Carcigenicate Avatar answered Nov 15 '22 07:11

Carcigenicate