【发布时间】:2013-06-18 18:41:51
【问题描述】:
问题
我编写了一个 Haskell 程序,它遍历文件夹并找到文件夹中每个图像的平均颜色。它使用来自 hackage 的 repa-devil 包将图像加载到 repa 数组中。我通过将所有红色、蓝色和绿色值相加然后除以像素数来找到平均值:
-- compiled with -O2
import qualified Data.Array.Repa as R
import Data.Array.Repa.IO.DevIL
import Control.Monad.Trans (liftIO)
import System.Directory (getDirectoryContents)
size :: (R.Source r e) => R.Array r R.DIM3 e -> (Int, Int)
size img = (w, h)
where (R.Z R.:. h R.:. w R.:. 3) = R.extent img
averageColour :: (R.Source r e, Num e, Integral e) => R.Array r R.DIM3 e -> (Int, Int, Int)
averageColour img = (r `div` n, g `div` n, b `div` n)
where (w, h) = size img
n = w * h
(r,g,b) = f 0 0 0 0 0
f row col r g b
| row >= w = f 0 (col + 1) r g b
| col >= h = (r, g, b)
| otherwise = f (row + 1) col (addCol 0 r) (addCol 1 g) (addCol 2 b)
where addCol x v = v + fromIntegral (img R.! (R.Z R.:. col R.:. row R.:. x))
main :: IO ()
main = do
files <- fmap (map ("images/olympics_backup/" ++) . filter (`notElem` ["..", "."])) $ getDirectoryContents "images/olympics_backup"
runIL $ do
images <- mapM readImage files
let average = zip (map (\(RGB img) -> averageColour img) images) files
liftIO . print $ average
我还使用 Python Image Library 用 Python 编写了这个程序。它以相同的方式找到图像的平均值:
import Image
def get_images(folder):
images = []
for filename in os.listdir(folder):
images.append(folder + filename)
return images
def get_average(filename):
image = Image.open(filename)
pixels = image.load()
r = g = b = 0
for x in xrange(0, image.size[0]):
for y in xrange(0, image.size[1]):
colour = pixels[x, y]
r += colour[0]
g += colour[1]
b += colour[2]
area = image.size[0] * image.size[1]
r /= area
g /= area
b /= area
return [(r, g, b), filename, image]
def get_colours(images):
colours = []
for image in images:
try:
colours.append(get_average(image))
except:
continue
return colours
imgs = get_images('images/olympics_backup/')
print get_colours(imgs)
当这两个都在一个包含 301 个图像的文件夹上运行时,Haskell 版本的性能优于 0.2 秒(0.87 对 0.64)。这看起来很奇怪,因为 Haskell 是一种编译语言(通常比解释语言快),而且我听说 repa 数组具有良好的性能(尽管这可能只是与其他 Haskell 数据类型相比,比如列表)。
我尝试了什么
我做的第一件事是注意到我正在使用显式递归,因此我决定使用折叠替换它,这也意味着我不再需要检查我是否超出了数组的范围:
(r,g,b) = foldl' f (0,0,0) [(x, y) | x <- [0..w-1], y <- [0..h-1]]
f (r,g,b) (row,col) = (addCol 0 r, addCol 1 g, addCol 2 b)
where addCol x v = v + fromIntegral (img R.! (R.Z R.:. col R.:. row R.:. x))
这使其运行速度变慢(1.2 秒),因此我决定分析代码并查看大部分时间都花在了哪些地方(以防我造成了明显的瓶颈或 repa-devil 包运行缓慢)。配置文件告诉我,约 58% 的时间花在 f 函数上,约 35% 的时间花在 addCol 上。
不幸的是,我想不出任何方法可以让这个运行得更快。该函数只是一个数组索引和一个加法 - 与 python 代码相同。有没有办法提高此代码的性能,或者 Python 图像库只是提供更高的性能?
【问题讨论】:
-
在
(r,g,b) = foldl' f (0,0,0) [(x, y) | x <- [0..w-1], y <- [0..h-1]]中使用foldl'对您没有任何好处。foldl'将中间结果评估为弱头范式,即此处的(,,)构造函数。组件仍然是 thunk。我不会为了检查而安装软件包,所以我必须猜测。在r、g和b、f row col !r !g !b(当然需要BangPatterns)中使f严格有帮助吗? -
使用自定义
fold和 repa 让我觉得转换为列表可能无法解决这个坏主意。我敢打赌使用backpermute将颜色通道作为三个独立的延迟数组,然后调用sumS如果有人想检查的话,性能会更好。 -
@DanielFischer 使 r、g 和 b 严格确实有帮助。事实上,它使程序变慢了很多(17 秒)。感谢您对 foldl' 的建议和解释。
-
不要超越 repa —— 转换为列表是性能杀手,并且会抵消你在 repa 中从融合或并行性中获得的任何好处。
-
@matio2matio 从不如果涉及使用 fusion(字节串、文本、向量、repa 等,我敢肯定)的库,则使用为分析而编译的代码进行性能测量。为分析而编译会抑制很多优化,而对于依赖它们的库来说,这是致命的。
标签: python haskell optimization repa