Search code examples
pythonmultithreadingcprofilepstats

Python script long execuation function


Hello i got a problem that a function in my script takes long to execute. So i am trying to optimize the code and i am currently using cProfile and Pstats for that.

When i execute a function it takes around 0.7 seconds to 1+ seconds to finish. The item that always gives the highest duration on Windows is:

    Sun Mar 10 13:55:02 2019    profiles/getColors.profile

         657 function calls in 0.535 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
          1    0.714    0.714    0.714    0.714 {built-in method _winapi.WaitForMultipleObjects}_winapi.WaitForMultipleObjects}

And on Linux:

2    1.013    0.506    1.013    0.506 {built-in method posix.read}

So i am going to assume it has to do something with threading but i never create any threads and my other function takes barely no time at all to complete like ~0.1 sec, So my question is why does it take so long the time to execute this code:

def getColors(image, rows, columns, sectionedPixel):
# Flooring so we do not get decimal numbers
sectionColumns = math.floor(width / columns)
sectionRows = math.floor(height / rows)
colorValues = [0, 0, 0, 0]
leftRGBVal = [0, 0, 0]
rightRGBVal = [0, 0, 0]
topRGBVal = [0, 0, 0]
botRGBVal = [0, 0, 0]

# LEFT SIDE
getRiLeSideColor(image, 0, 10, leftRGBVal)
# RIGHT SIDE
getRiLeSideColor(image, width - 10, width, rightRGBVal)

# TOP SIDE
getToBoSideColor(image, 0, 10, topRGBVal)
# BOTTOM SIDE
getToBoSideColor(image, height - 10, height, botRGBVal)

colorValues[0] = leftRGBVal
colorValues[1] = rightRGBVal
colorValues[2] = topRGBVal
colorValues[3] = botRGBVal

return colorValues

Full log of CProfile: https://pastebin.com/jAA5FkPZ

Full code: https://gist.github.com/Patrick265/592a7dccba4660a4e4210ddd5e9974eb


Solution

  • If I just run your script and check for the timings of the two major calls retrieveScreen(...) and getColors(...) I see the average timings:

    retrieveScreen: 1.70369
    getColors:      0.07770
    

    I guess your screencap relies on the operating system and just takes time.

    From a couple of quick tests, I think it should be faster to get your screen's PixelAccess like so, just using bare PIL:

    import PIL.ImageGrab
    
    pxlaccess = PIL.ImageGrab.grab().load()
    

    Edit: Full Example

    This (python3) code only uses PIL. To be honest, I am uncertain about the actual method employed by pillow to access the screen, but it's slightly faster than your previous approach. I think you are trying to achieve something like backlight for a screen, which would require to be updated quite quick; don't know whether this is sufficient for your purpose or not. However, feel free to use the code as required:

    #!/usr/bin/python3
    
    import time
    import PIL.ImageGrab
    
    
    def get_screen_and_dimensions():
        cap = PIL.ImageGrab.grab()
        return cap.load(), cap.size
    
    
    def average_color_from_rect(screen, x0, x1, y0, y1):
        color = [0, 0, 0]
        for x in range(x0, x1):
            for y in range(y0, y1):
                source = screen[x, y]
                for i in range(3):
                    color[i] += source[i]
        count = (x1 - x0) * (y1 - y0)
        return [round(color[i] / count) for i in range(3)]
    
    
    def main():
        t0 = time.time()
        screen, size = get_screen_and_dimensions()
        t1 = time.time()
        print(f'Grab screen: {t1 - t0:.4f}s')
    
        bandwidth = 10
        borders = {
            'top': (0, size[0], 0, bandwidth),
            'right': (size[0] - bandwidth, size[0], 0, size[1]),
            'bottom': (0, size[0], size[1] - bandwidth, size[1]),
            'left': (0, bandwidth, 0, size[1]),
        }
    
        t0 = time.time()
        for border, args in borders.items():
            color = average_color_from_rect(screen, *args)
            print(f'{border}: {color}')
        t1 = time.time()
        print(f'Color calculation: {t1 - t0:.4f}s')
    
    
    if __name__ == "__main__":
        main()
    

    Exemplary output, just for demonstration purpose:

    $ python3 fiddle-colors.py 
    Grab screen: 0.3974s
    top: [35, 35, 35]
    right: [126, 126, 125]
    bottom: [134, 137, 139]
    left: [50, 50, 50]
    Color calculation: 0.0905s