寻找快速逐像素绘制的方法,为什么这段代码比Java慢了1000倍?

5

这是一个简单的代码,只是将窗口颜色修改了4次。

也许有一些很明显的东西我没看到。

我的目标是从零开始学习计算机图形学,并想要逐像素绘制以完全控制。我正在寻找一种快速的方法来做到这一点。

这里 是完整的代码。

相关的clojure部分:

(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))))

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);

2
这个问题应该发布在 https://codereview.stackexchange.com 上。 - jaco0646
1个回答

7

有几个问题:

  • 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.


说实话,我已经尽力了。它比以前快得多,但仍然比Java版本慢约20倍。我已经在这上面工作了近2个小时,现在我已经遇到了瓶颈。希望其他人能够挤出最后一点时间。


@raoof,我一直在进行一些基准测试,你的20倍慢的估计看起来是准确的。使用Criterium,Clojure的时间大约为200毫秒,而Java则大约为10毫秒。我将尝试将你的宏更改为doseq,因为这才是应该在这里使用的。 - Carcigenicate
1
谢谢你的努力。我想也许我应该放弃Clojure,改用C语言。 - raoof
1
@raoof 如果你必须追求速度,那么你不应该使用Clojure。我写Clojure是因为它很有趣,而且可以产生优美的代码。是否使用这种语言取决于你的立场和情况。 - Carcigenicate
1
@raoof 这总是一个权衡。追求完美的编程语言是永无止境的。我鼓励你重新考虑Clojure,但如果它对你的目的不实用,那就不实用。 - Carcigenicate
2
@raoof 我仍然会用Clojure编写它,但是使用Java编写代码的这些部分。在Leiningen项目的project.clj文件中,您可以使用:java-source-paths选项指定Java源代码的位置。要使这种混合的Clojure和Java代码与REPL一起工作,请查看lein-virgil项目,它将自动编译和重新加载您的Java类。 - Rulle
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接