Performance concerns when converting GTK image to greyscale

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite












I have a function which takes GTK Image (It is RGB) and returns a representation of this image as list of grey levels.



This is my code:



grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
imgType <- img `get` imageStorageType
if imgType == ImageEmpty
then
return (replicate 256 0)
else do
pixbuf <- imageGetPixbuf img
pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
rgbList <- getElems pixels
nChannels <- pixbufGetNChannels pixbuf
return (map convert $ chunksOf nChannels rgbList)

where
convert channels = round $ foldl (+) 0 $ zipWith (*) [0.3, 0.59, 0.11] $ map fromIntegral channels


But I have a problem: this function uses too much memory and CPU time. For example for an image with a size of 500x500 it takes nearly 100MB of memory. Opening an image with a size of 2000x1000 it use more than 1GB of memory and it takes a lot of time.



How can it be written in an efficient way? What is the reason for this behavior?



Update:



Whole code which use this function (this program takes too much RAM):



import Graphics.Rendering.Cairo
import Graphics.UI.Gtk
import Data.List.Split
import Data.Array.MArray
import Data.Array.IO
import Data.Word
import Data.List
import Foreign.Storable

grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
imgType <- img `get` imageStorageType
if imgType == ImageEmpty
then
return (replicate 256 0)
else do
pixbuf <- imageGetPixbuf img
pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
rgbList <- getElems pixels
nChannels <- pixbufGetNChannels pixbuf
return (map convert $ chunksOf nChannels rgbList)

where
convert channels = round $ foldl' (+) 0 $ zipWith (*) [0.3, 0.59, 0.11] $ map fromIntegral channels

main :: IO ()
main= do
initGUI
window <- windowNew
set window [windowTitle := "Hello Cairo",
windowDefaultWidth := 300, windowDefaultHeight := 200,
containerBorderWidth := 30 ]

image <- imageNew
let menuBarData = [
("File", [("Open", openPressed image),
("Exit", mainQuit)
])
]

tab <- tableNew 5 10 True

menuBar <- createMenuBar menuBarData

scrolled <- scrolledWindowNew Nothing Nothing
scrolledWindowAddWithViewport scrolled image


tableAttachDefaults tab menuBar 0 10 0 1
tableAttachDefaults tab scrolled 0 5 1 10

containerAdd window tab

widgetShowAll window
onDestroy window mainQuit
mainGUI

createMenuBar menuBarData = do
menuBar <- menuBarNew

mapM_ (setMenuEntries menuBar) menuBarData
return menuBar

where
setMenuEntries menuBar (entryName, items) = do
entry <- menuItemNewWithLabel entryName
menu <- menuNew
mapM_ (addItemToEntry menu) items
menuItemSetSubmenu entry menu
menuShellAppend menuBar entry

addItemToEntry menu (name, action) = do
item <- menuItemNewWithLabel name
item `on` menuItemActivated $ action
menuShellAppend menu item


openPressed :: Image -> IO ()
openPressed image = do
chooser <- fileChooserDialogNew
(Just "Open file")
Nothing
FileChooserActionOpen
[("OK", ResponseOk),
("Cancel", ResponseCancel)]

widgetShow chooser
res <- dialogRun chooser
filename <- performResponse res chooser
widgetHide chooser

where
performResponse ResponseCancel _ = return ()
performResponse ResponseOk chooser = do
Just fname <- fileChooserGetFilename chooser
imageSetFromFile image fname
grey <- grayscaleFromRGB image
putStrLn $ show $ grey






share|improve this question





















  • Are you compiling with optimizations? Try using foldl' instead of foldl.
    – Li-yao Xia
    Mar 20 at 4:43










  • @Li-yaoXia, I even replaced convert function without using lists, it increased performance but not much. Profiling says that most allocations are made from grayscaleFromRGB by itself.
    – Shadasviar
    Mar 20 at 9:32










  • I can't find anything wrong with that function. Do you have a minimal compilable example?
    – Li-yao Xia
    Mar 20 at 11:41










  • @Li-yaoXia, updated with example
    – Shadasviar
    Mar 20 at 18:41
















up vote
4
down vote

favorite












I have a function which takes GTK Image (It is RGB) and returns a representation of this image as list of grey levels.



This is my code:



grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
imgType <- img `get` imageStorageType
if imgType == ImageEmpty
then
return (replicate 256 0)
else do
pixbuf <- imageGetPixbuf img
pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
rgbList <- getElems pixels
nChannels <- pixbufGetNChannels pixbuf
return (map convert $ chunksOf nChannels rgbList)

where
convert channels = round $ foldl (+) 0 $ zipWith (*) [0.3, 0.59, 0.11] $ map fromIntegral channels


But I have a problem: this function uses too much memory and CPU time. For example for an image with a size of 500x500 it takes nearly 100MB of memory. Opening an image with a size of 2000x1000 it use more than 1GB of memory and it takes a lot of time.



How can it be written in an efficient way? What is the reason for this behavior?



Update:



Whole code which use this function (this program takes too much RAM):



import Graphics.Rendering.Cairo
import Graphics.UI.Gtk
import Data.List.Split
import Data.Array.MArray
import Data.Array.IO
import Data.Word
import Data.List
import Foreign.Storable

grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
imgType <- img `get` imageStorageType
if imgType == ImageEmpty
then
return (replicate 256 0)
else do
pixbuf <- imageGetPixbuf img
pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
rgbList <- getElems pixels
nChannels <- pixbufGetNChannels pixbuf
return (map convert $ chunksOf nChannels rgbList)

where
convert channels = round $ foldl' (+) 0 $ zipWith (*) [0.3, 0.59, 0.11] $ map fromIntegral channels

main :: IO ()
main= do
initGUI
window <- windowNew
set window [windowTitle := "Hello Cairo",
windowDefaultWidth := 300, windowDefaultHeight := 200,
containerBorderWidth := 30 ]

image <- imageNew
let menuBarData = [
("File", [("Open", openPressed image),
("Exit", mainQuit)
])
]

tab <- tableNew 5 10 True

menuBar <- createMenuBar menuBarData

scrolled <- scrolledWindowNew Nothing Nothing
scrolledWindowAddWithViewport scrolled image


tableAttachDefaults tab menuBar 0 10 0 1
tableAttachDefaults tab scrolled 0 5 1 10

containerAdd window tab

widgetShowAll window
onDestroy window mainQuit
mainGUI

createMenuBar menuBarData = do
menuBar <- menuBarNew

mapM_ (setMenuEntries menuBar) menuBarData
return menuBar

where
setMenuEntries menuBar (entryName, items) = do
entry <- menuItemNewWithLabel entryName
menu <- menuNew
mapM_ (addItemToEntry menu) items
menuItemSetSubmenu entry menu
menuShellAppend menuBar entry

addItemToEntry menu (name, action) = do
item <- menuItemNewWithLabel name
item `on` menuItemActivated $ action
menuShellAppend menu item


openPressed :: Image -> IO ()
openPressed image = do
chooser <- fileChooserDialogNew
(Just "Open file")
Nothing
FileChooserActionOpen
[("OK", ResponseOk),
("Cancel", ResponseCancel)]

widgetShow chooser
res <- dialogRun chooser
filename <- performResponse res chooser
widgetHide chooser

where
performResponse ResponseCancel _ = return ()
performResponse ResponseOk chooser = do
Just fname <- fileChooserGetFilename chooser
imageSetFromFile image fname
grey <- grayscaleFromRGB image
putStrLn $ show $ grey






share|improve this question





















  • Are you compiling with optimizations? Try using foldl' instead of foldl.
    – Li-yao Xia
    Mar 20 at 4:43










  • @Li-yaoXia, I even replaced convert function without using lists, it increased performance but not much. Profiling says that most allocations are made from grayscaleFromRGB by itself.
    – Shadasviar
    Mar 20 at 9:32










  • I can't find anything wrong with that function. Do you have a minimal compilable example?
    – Li-yao Xia
    Mar 20 at 11:41










  • @Li-yaoXia, updated with example
    – Shadasviar
    Mar 20 at 18:41












up vote
4
down vote

favorite









up vote
4
down vote

favorite











I have a function which takes GTK Image (It is RGB) and returns a representation of this image as list of grey levels.



This is my code:



grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
imgType <- img `get` imageStorageType
if imgType == ImageEmpty
then
return (replicate 256 0)
else do
pixbuf <- imageGetPixbuf img
pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
rgbList <- getElems pixels
nChannels <- pixbufGetNChannels pixbuf
return (map convert $ chunksOf nChannels rgbList)

where
convert channels = round $ foldl (+) 0 $ zipWith (*) [0.3, 0.59, 0.11] $ map fromIntegral channels


But I have a problem: this function uses too much memory and CPU time. For example for an image with a size of 500x500 it takes nearly 100MB of memory. Opening an image with a size of 2000x1000 it use more than 1GB of memory and it takes a lot of time.



How can it be written in an efficient way? What is the reason for this behavior?



Update:



Whole code which use this function (this program takes too much RAM):



import Graphics.Rendering.Cairo
import Graphics.UI.Gtk
import Data.List.Split
import Data.Array.MArray
import Data.Array.IO
import Data.Word
import Data.List
import Foreign.Storable

grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
imgType <- img `get` imageStorageType
if imgType == ImageEmpty
then
return (replicate 256 0)
else do
pixbuf <- imageGetPixbuf img
pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
rgbList <- getElems pixels
nChannels <- pixbufGetNChannels pixbuf
return (map convert $ chunksOf nChannels rgbList)

where
convert channels = round $ foldl' (+) 0 $ zipWith (*) [0.3, 0.59, 0.11] $ map fromIntegral channels

main :: IO ()
main= do
initGUI
window <- windowNew
set window [windowTitle := "Hello Cairo",
windowDefaultWidth := 300, windowDefaultHeight := 200,
containerBorderWidth := 30 ]

image <- imageNew
let menuBarData = [
("File", [("Open", openPressed image),
("Exit", mainQuit)
])
]

tab <- tableNew 5 10 True

menuBar <- createMenuBar menuBarData

scrolled <- scrolledWindowNew Nothing Nothing
scrolledWindowAddWithViewport scrolled image


tableAttachDefaults tab menuBar 0 10 0 1
tableAttachDefaults tab scrolled 0 5 1 10

containerAdd window tab

widgetShowAll window
onDestroy window mainQuit
mainGUI

createMenuBar menuBarData = do
menuBar <- menuBarNew

mapM_ (setMenuEntries menuBar) menuBarData
return menuBar

where
setMenuEntries menuBar (entryName, items) = do
entry <- menuItemNewWithLabel entryName
menu <- menuNew
mapM_ (addItemToEntry menu) items
menuItemSetSubmenu entry menu
menuShellAppend menuBar entry

addItemToEntry menu (name, action) = do
item <- menuItemNewWithLabel name
item `on` menuItemActivated $ action
menuShellAppend menu item


openPressed :: Image -> IO ()
openPressed image = do
chooser <- fileChooserDialogNew
(Just "Open file")
Nothing
FileChooserActionOpen
[("OK", ResponseOk),
("Cancel", ResponseCancel)]

widgetShow chooser
res <- dialogRun chooser
filename <- performResponse res chooser
widgetHide chooser

where
performResponse ResponseCancel _ = return ()
performResponse ResponseOk chooser = do
Just fname <- fileChooserGetFilename chooser
imageSetFromFile image fname
grey <- grayscaleFromRGB image
putStrLn $ show $ grey






share|improve this question













I have a function which takes GTK Image (It is RGB) and returns a representation of this image as list of grey levels.



This is my code:



grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
imgType <- img `get` imageStorageType
if imgType == ImageEmpty
then
return (replicate 256 0)
else do
pixbuf <- imageGetPixbuf img
pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
rgbList <- getElems pixels
nChannels <- pixbufGetNChannels pixbuf
return (map convert $ chunksOf nChannels rgbList)

where
convert channels = round $ foldl (+) 0 $ zipWith (*) [0.3, 0.59, 0.11] $ map fromIntegral channels


But I have a problem: this function uses too much memory and CPU time. For example for an image with a size of 500x500 it takes nearly 100MB of memory. Opening an image with a size of 2000x1000 it use more than 1GB of memory and it takes a lot of time.



How can it be written in an efficient way? What is the reason for this behavior?



Update:



Whole code which use this function (this program takes too much RAM):



import Graphics.Rendering.Cairo
import Graphics.UI.Gtk
import Data.List.Split
import Data.Array.MArray
import Data.Array.IO
import Data.Word
import Data.List
import Foreign.Storable

grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
imgType <- img `get` imageStorageType
if imgType == ImageEmpty
then
return (replicate 256 0)
else do
pixbuf <- imageGetPixbuf img
pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
rgbList <- getElems pixels
nChannels <- pixbufGetNChannels pixbuf
return (map convert $ chunksOf nChannels rgbList)

where
convert channels = round $ foldl' (+) 0 $ zipWith (*) [0.3, 0.59, 0.11] $ map fromIntegral channels

main :: IO ()
main= do
initGUI
window <- windowNew
set window [windowTitle := "Hello Cairo",
windowDefaultWidth := 300, windowDefaultHeight := 200,
containerBorderWidth := 30 ]

image <- imageNew
let menuBarData = [
("File", [("Open", openPressed image),
("Exit", mainQuit)
])
]

tab <- tableNew 5 10 True

menuBar <- createMenuBar menuBarData

scrolled <- scrolledWindowNew Nothing Nothing
scrolledWindowAddWithViewport scrolled image


tableAttachDefaults tab menuBar 0 10 0 1
tableAttachDefaults tab scrolled 0 5 1 10

containerAdd window tab

widgetShowAll window
onDestroy window mainQuit
mainGUI

createMenuBar menuBarData = do
menuBar <- menuBarNew

mapM_ (setMenuEntries menuBar) menuBarData
return menuBar

where
setMenuEntries menuBar (entryName, items) = do
entry <- menuItemNewWithLabel entryName
menu <- menuNew
mapM_ (addItemToEntry menu) items
menuItemSetSubmenu entry menu
menuShellAppend menuBar entry

addItemToEntry menu (name, action) = do
item <- menuItemNewWithLabel name
item `on` menuItemActivated $ action
menuShellAppend menu item


openPressed :: Image -> IO ()
openPressed image = do
chooser <- fileChooserDialogNew
(Just "Open file")
Nothing
FileChooserActionOpen
[("OK", ResponseOk),
("Cancel", ResponseCancel)]

widgetShow chooser
res <- dialogRun chooser
filename <- performResponse res chooser
widgetHide chooser

where
performResponse ResponseCancel _ = return ()
performResponse ResponseOk chooser = do
Just fname <- fileChooserGetFilename chooser
imageSetFromFile image fname
grey <- grayscaleFromRGB image
putStrLn $ show $ grey








share|improve this question












share|improve this question




share|improve this question








edited Mar 20 at 18:41
























asked Mar 19 at 21:20









Shadasviar

1556




1556











  • Are you compiling with optimizations? Try using foldl' instead of foldl.
    – Li-yao Xia
    Mar 20 at 4:43










  • @Li-yaoXia, I even replaced convert function without using lists, it increased performance but not much. Profiling says that most allocations are made from grayscaleFromRGB by itself.
    – Shadasviar
    Mar 20 at 9:32










  • I can't find anything wrong with that function. Do you have a minimal compilable example?
    – Li-yao Xia
    Mar 20 at 11:41










  • @Li-yaoXia, updated with example
    – Shadasviar
    Mar 20 at 18:41
















  • Are you compiling with optimizations? Try using foldl' instead of foldl.
    – Li-yao Xia
    Mar 20 at 4:43










  • @Li-yaoXia, I even replaced convert function without using lists, it increased performance but not much. Profiling says that most allocations are made from grayscaleFromRGB by itself.
    – Shadasviar
    Mar 20 at 9:32










  • I can't find anything wrong with that function. Do you have a minimal compilable example?
    – Li-yao Xia
    Mar 20 at 11:41










  • @Li-yaoXia, updated with example
    – Shadasviar
    Mar 20 at 18:41















Are you compiling with optimizations? Try using foldl' instead of foldl.
– Li-yao Xia
Mar 20 at 4:43




Are you compiling with optimizations? Try using foldl' instead of foldl.
– Li-yao Xia
Mar 20 at 4:43












@Li-yaoXia, I even replaced convert function without using lists, it increased performance but not much. Profiling says that most allocations are made from grayscaleFromRGB by itself.
– Shadasviar
Mar 20 at 9:32




@Li-yaoXia, I even replaced convert function without using lists, it increased performance but not much. Profiling says that most allocations are made from grayscaleFromRGB by itself.
– Shadasviar
Mar 20 at 9:32












I can't find anything wrong with that function. Do you have a minimal compilable example?
– Li-yao Xia
Mar 20 at 11:41




I can't find anything wrong with that function. Do you have a minimal compilable example?
– Li-yao Xia
Mar 20 at 11:41












@Li-yaoXia, updated with example
– Shadasviar
Mar 20 at 18:41




@Li-yaoXia, updated with example
– Shadasviar
Mar 20 at 18:41










1 Answer
1






active

oldest

votes

















up vote
1
down vote



accepted










By commenting out various parts of grayscaleFromRGB, we can deduce that what is taking the most time is getElems (and possibly the computations after return). Indeed, for a 2000x1000 picture, there are 2M pixels (x3 or 4 channels), and getElems is putting them in a list word by word. Lists are quite inefficient for storing large amounts of data like pictures (at least 4 or 5 extra words per element, and the GC comes with an extra x2 factor).



To reduce memory usage, we can write a custom loop to process the elements in the array as we read them, like below. To reduce that further, don't use lists to store large amounts of data. It still takes a while to read millions of elements from memory byte by byte; there may be a better way using a specialized data processing library but I don't know it.



grayscaleFromRGB :: Image -> IO [Word8]
grayscaleFromRGB img = do
imgType <- img `get` imageStorageType
if imgType == ImageEmpty
then
return (replicate 256 0)
else do
pixbuf <- imageGetPixbuf img
pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
nChannels <- pixbufGetNChannels pixbuf
(_, bound) <- getBounds pixels
loop pixels nChannels bound 0

where
-- the returned list will be reversed compared to the original code
loop pixels n bound i acc | i + 2 > bound = return acc
loop pixels n bound i acc = do
let get i = fromIntegral <$> (readArray (pixels :: PixbufData Int Word8) i) :: IO Double
a <- (0.3 *) <$> get i
b <- (0.59 *) <$> get (i + 1)
c <- (0.11 *) <$> get (i + 2)
loop pixels n bound (i + n) $! (((:) $! round (a + b + c)) $! acc)





share|improve this answer





















    Your Answer




    StackExchange.ifUsing("editor", function ()
    return StackExchange.using("mathjaxEditing", function ()
    StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
    StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
    );
    );
    , "mathjax-editing");

    StackExchange.ifUsing("editor", function ()
    StackExchange.using("externalEditor", function ()
    StackExchange.using("snippets", function ()
    StackExchange.snippets.init();
    );
    );
    , "code-snippets");

    StackExchange.ready(function()
    var channelOptions =
    tags: "".split(" "),
    id: "196"
    ;
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function()
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled)
    StackExchange.using("snippets", function()
    createEditor();
    );

    else
    createEditor();

    );

    function createEditor()
    StackExchange.prepareEditor(
    heartbeatType: 'answer',
    convertImagesToLinks: false,
    noModals: false,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: null,
    bindNavPrevention: true,
    postfix: "",
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    );



    );








     

    draft saved


    draft discarded


















    StackExchange.ready(
    function ()
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f189971%2fperformance-concerns-when-converting-gtk-image-to-greyscale%23new-answer', 'question_page');

    );

    Post as a guest






























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    1
    down vote



    accepted










    By commenting out various parts of grayscaleFromRGB, we can deduce that what is taking the most time is getElems (and possibly the computations after return). Indeed, for a 2000x1000 picture, there are 2M pixels (x3 or 4 channels), and getElems is putting them in a list word by word. Lists are quite inefficient for storing large amounts of data like pictures (at least 4 or 5 extra words per element, and the GC comes with an extra x2 factor).



    To reduce memory usage, we can write a custom loop to process the elements in the array as we read them, like below. To reduce that further, don't use lists to store large amounts of data. It still takes a while to read millions of elements from memory byte by byte; there may be a better way using a specialized data processing library but I don't know it.



    grayscaleFromRGB :: Image -> IO [Word8]
    grayscaleFromRGB img = do
    imgType <- img `get` imageStorageType
    if imgType == ImageEmpty
    then
    return (replicate 256 0)
    else do
    pixbuf <- imageGetPixbuf img
    pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
    nChannels <- pixbufGetNChannels pixbuf
    (_, bound) <- getBounds pixels
    loop pixels nChannels bound 0

    where
    -- the returned list will be reversed compared to the original code
    loop pixels n bound i acc | i + 2 > bound = return acc
    loop pixels n bound i acc = do
    let get i = fromIntegral <$> (readArray (pixels :: PixbufData Int Word8) i) :: IO Double
    a <- (0.3 *) <$> get i
    b <- (0.59 *) <$> get (i + 1)
    c <- (0.11 *) <$> get (i + 2)
    loop pixels n bound (i + n) $! (((:) $! round (a + b + c)) $! acc)





    share|improve this answer

























      up vote
      1
      down vote



      accepted










      By commenting out various parts of grayscaleFromRGB, we can deduce that what is taking the most time is getElems (and possibly the computations after return). Indeed, for a 2000x1000 picture, there are 2M pixels (x3 or 4 channels), and getElems is putting them in a list word by word. Lists are quite inefficient for storing large amounts of data like pictures (at least 4 or 5 extra words per element, and the GC comes with an extra x2 factor).



      To reduce memory usage, we can write a custom loop to process the elements in the array as we read them, like below. To reduce that further, don't use lists to store large amounts of data. It still takes a while to read millions of elements from memory byte by byte; there may be a better way using a specialized data processing library but I don't know it.



      grayscaleFromRGB :: Image -> IO [Word8]
      grayscaleFromRGB img = do
      imgType <- img `get` imageStorageType
      if imgType == ImageEmpty
      then
      return (replicate 256 0)
      else do
      pixbuf <- imageGetPixbuf img
      pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
      nChannels <- pixbufGetNChannels pixbuf
      (_, bound) <- getBounds pixels
      loop pixels nChannels bound 0

      where
      -- the returned list will be reversed compared to the original code
      loop pixels n bound i acc | i + 2 > bound = return acc
      loop pixels n bound i acc = do
      let get i = fromIntegral <$> (readArray (pixels :: PixbufData Int Word8) i) :: IO Double
      a <- (0.3 *) <$> get i
      b <- (0.59 *) <$> get (i + 1)
      c <- (0.11 *) <$> get (i + 2)
      loop pixels n bound (i + n) $! (((:) $! round (a + b + c)) $! acc)





      share|improve this answer























        up vote
        1
        down vote



        accepted







        up vote
        1
        down vote



        accepted






        By commenting out various parts of grayscaleFromRGB, we can deduce that what is taking the most time is getElems (and possibly the computations after return). Indeed, for a 2000x1000 picture, there are 2M pixels (x3 or 4 channels), and getElems is putting them in a list word by word. Lists are quite inefficient for storing large amounts of data like pictures (at least 4 or 5 extra words per element, and the GC comes with an extra x2 factor).



        To reduce memory usage, we can write a custom loop to process the elements in the array as we read them, like below. To reduce that further, don't use lists to store large amounts of data. It still takes a while to read millions of elements from memory byte by byte; there may be a better way using a specialized data processing library but I don't know it.



        grayscaleFromRGB :: Image -> IO [Word8]
        grayscaleFromRGB img = do
        imgType <- img `get` imageStorageType
        if imgType == ImageEmpty
        then
        return (replicate 256 0)
        else do
        pixbuf <- imageGetPixbuf img
        pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
        nChannels <- pixbufGetNChannels pixbuf
        (_, bound) <- getBounds pixels
        loop pixels nChannels bound 0

        where
        -- the returned list will be reversed compared to the original code
        loop pixels n bound i acc | i + 2 > bound = return acc
        loop pixels n bound i acc = do
        let get i = fromIntegral <$> (readArray (pixels :: PixbufData Int Word8) i) :: IO Double
        a <- (0.3 *) <$> get i
        b <- (0.59 *) <$> get (i + 1)
        c <- (0.11 *) <$> get (i + 2)
        loop pixels n bound (i + n) $! (((:) $! round (a + b + c)) $! acc)





        share|improve this answer













        By commenting out various parts of grayscaleFromRGB, we can deduce that what is taking the most time is getElems (and possibly the computations after return). Indeed, for a 2000x1000 picture, there are 2M pixels (x3 or 4 channels), and getElems is putting them in a list word by word. Lists are quite inefficient for storing large amounts of data like pictures (at least 4 or 5 extra words per element, and the GC comes with an extra x2 factor).



        To reduce memory usage, we can write a custom loop to process the elements in the array as we read them, like below. To reduce that further, don't use lists to store large amounts of data. It still takes a while to read millions of elements from memory byte by byte; there may be a better way using a specialized data processing library but I don't know it.



        grayscaleFromRGB :: Image -> IO [Word8]
        grayscaleFromRGB img = do
        imgType <- img `get` imageStorageType
        if imgType == ImageEmpty
        then
        return (replicate 256 0)
        else do
        pixbuf <- imageGetPixbuf img
        pixels <- (pixbufGetPixels pixbuf :: IO (PixbufData Int Word8))
        nChannels <- pixbufGetNChannels pixbuf
        (_, bound) <- getBounds pixels
        loop pixels nChannels bound 0

        where
        -- the returned list will be reversed compared to the original code
        loop pixels n bound i acc | i + 2 > bound = return acc
        loop pixels n bound i acc = do
        let get i = fromIntegral <$> (readArray (pixels :: PixbufData Int Word8) i) :: IO Double
        a <- (0.3 *) <$> get i
        b <- (0.59 *) <$> get (i + 1)
        c <- (0.11 *) <$> get (i + 2)
        loop pixels n bound (i + n) $! (((:) $! round (a + b + c)) $! acc)






        share|improve this answer













        share|improve this answer



        share|improve this answer











        answered Mar 21 at 2:47









        Li-yao Xia

        22612




        22612






















             

            draft saved


            draft discarded


























             


            draft saved


            draft discarded














            StackExchange.ready(
            function ()
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f189971%2fperformance-concerns-when-converting-gtk-image-to-greyscale%23new-answer', 'question_page');

            );

            Post as a guest













































































            Popular posts from this blog

            Greedy Best First Search implementation in Rust

            Function to Return a JSON Like Objects Using VBA Collections and Arrays

            C++11 CLH Lock Implementation