Почему Cython только на 20% быстрее на черно-белом изображении RunLengthEncode - PullRequest
0 голосов
/ 03 ноября 2018

Мне нужно сжать много (2000) изображений (2560x1440x1) в 1-битном формате кодированной длины пробега. (Формат не мой выбор.) Максимально допустимая длина прогона составляет 125 повторений.

Я использую Python и пробовал использовать как numpy, так и нативный Python. Скорость с использованием NumPy быстрее. После перехода на Cython скорость увеличилась на 200%, однако скорость нативного кода стала (в 2 раза) выше, чем при использовании numpy.

Увеличение скорости на 200% с Cython кажется слишком маленьким. Я что-то не так делаю?

Основной вызов программы для нативного Python / Cython:

rlestack.append(rleEncode.encodedBitmap_Bytes_nonumpy(imgarr8.flatten(0).tolist()))

или используя Numpy / Cython:

rlestack.append(rleEncode.encodedBitmap_Bytes_withnumpy(imgarr8.flatten(0)))

(imgarr8 - это массив numpy.uint8 размером 2560x1440)

Методы кодирования Cython RLE находятся в rleEncode.pyx:

import numpy
cimport numpy

#!python
@cython.wraparound (False) #turn off negative indexing
@cython.boundscheck(False) # turn off bounds-checking
@cython.nonecheck(False)
def encodedBitmap_Bytes_nonumpy1D(list surfOrFile not None):
    """ Converts image data from file on disk to RLE encoded byte string.
        Encoding scheme:
            Highest bit of each byte is color (black or white)
            Lowest 7 bits of each byte is repetition of that color, with max of 125 / 0x7D
    """
    #(width, height) = (1440, 2560)
    cdef unsigned int nrPixels = 3686400
    cdef unsigned int lastPixel = nrPixels - 1

    # Count number of pixels with same color up until 0x7D/125 repetitions
    rleData = bytearray() # convert bytearray to cdef array has no speed benefit
    cdef unsigned char color = 0
    cdef unsigned char prevColor = 0
    cdef unsigned char black = 0
    cdef unsigned char white = 1
    cdef unsigned char nocolor=3
    cdef unsigned char r
    cdef unsigned char nrOfColor = 0
    cdef unsigned char encValue = 0
    cdef unsigned int pixelNr
    cdef unsigned int isLastPixel = False
    prevColor = nocolor

    for pixelNr in range(nrPixels):
        r = surfOrFile[pixelNr]
        if (r and 0b10000000): #if (r<128)
            color = white
        else:
            color = black
        if prevColor == nocolor: prevColor = color
        isLastPixel = (pixelNr == lastPixel)
        if color == prevColor and nrOfColor < 0x7D and not isLastPixel:
            nrOfColor = nrOfColor + 1
        else:
            # print (color,nrOfColor,nrOfColor<<1)
            encValue = (prevColor << 7) | nrOfColor  # push color (B/W) to highest bit and repetitions to lowest 7 bits.
            rleData.append(encValue)
            prevColor = color
            nrOfColor = 1

    return bytes(rleData)


#!python
@cython.boundscheck(False) # turn off bounds-checking
@cython.wraparound(False) # turn off bounds-checking
def encodedBitmap_Bytes_withnumpy(numpy.ndarray[numpy.npy_uint8,ndim=1] x):

    # Encoding magic
    cdef unsigned int n=0
    cdef numpy.ndarray[numpy.npy_int64, ndim = 1] starts # npy_int64
    cdef numpy.ndarray[numpy.npy_int64, ndim = 1] lengths# npy_int64
    cdef numpy.ndarray[numpy.npy_uint8, ndim = 1] values # npy_uint8

    where = numpy.flatnonzero
    n = len(x)
    starts = numpy.r_[0, where(~numpy.isclose(x[1:], x[:-1], equal_nan=True)) + 1]
    lengths = numpy.diff(numpy.r_[starts, n])
    values = x[starts]

    # Reduce repetitions of color to max 0x7D/125 and store in bytearray
    rleData = bytearray()
    cdef unsigned int nr=0
    cdef unsigned int col=0
    cdef unsigned char color=0
    cdef unsigned char encValue = 0

    cdef unsigned int l=len(lengths)
    cdef unsigned int i=0
    for i in range (0,l):
        nr=lengths[i]
        col=values[i]
        # color = (abs(col)>1) # slow
        color = 1 if col else 0  # fast
        while nr > 0x7D:
            encValue = (color << 7) | 0x7D
            rleData.append(encValue)
            nr = nr - 0x7D
        encValue = (color << 7) | nr
        rleData.append(encValue)

    # Needed is an byte string, so convert
    return bytes(rleData)

Я компилирую rleEncode.pyx с python setup.py build_ext --inplace, используя следующий файл setup.py.

from distutils.core import setup
from Cython.Build import cythonize
import numpy

setup(
    ext_modules=cythonize("rleEncode.pyx"),
    include_dirs=[numpy.get_include()]
)

(я использовал команду sudo apt-get install python3-dev для установки необходимого заголовка python.h.)

1 Ответ

0 голосов
/ 05 ноября 2018

Используя ваши комментарии и https://kogs -www.informatik.uni-hamburg.de / ~ seppke / content / training / wise1314 / 20131128_letsch-gries-boomgarten-cython.pdf , я получил 10 раз быстрее!

Полученный код:

cimport cython
import numpy as numpy
cimport numpy as numpy
DTYPE = numpy.uint8
ctypedef numpy.uint8_t DTYPE_t

#!python
@cython.wraparound (False) # turn off negative indexing
@cython.boundscheck(False) # turn off bounds-checking
@cython.nonecheck(False)
def encodedBitmap_Bytes_numpy1DBlock(numpy.ndarray[DTYPE_t,ndim=1] surfOrFile):
    """ Converts image data from file on disk to RLE encoded byte string.
        Encoding scheme:
            Highest bit of each byte is color (black or white)
            Lowest 7 bits of each byte is repetition of that color, with max of 125 / 0x7D
        Credits for speed to:
            https://kogs-www.informatik.uni-hamburg.de/~seppke/content/teaching/wise1314/20131128_letsch-gries-boomgarten-cython.pdf
            /10329810/pochemu-cython-tolko-na-20-bystree-na-cherno-belom-izobrazhenii-runlengthencode
    """
    # Make room for rleData
    cdef numpy.ndarray [DTYPE_t,ndim=1] rleData =    numpy.zeros((3686400),dtype=DTYPE)

    # Some constants for nr of pixels and last pixelnr
    cdef unsigned int nrPixels = 3686400 #(width, height) = (1440, 2560)
    cdef unsigned int lastPixel = nrPixels - 1

    # Count number of pixels with same color up until 0x7D/125 repetitions
    cdef unsigned char color = 0
    cdef unsigned char prevColor = 0
    cdef unsigned char r
    cdef unsigned char nrOfColor = 0
    cdef unsigned char encValue = 0
    cdef unsigned int pixelNr
    cdef unsigned int nrBytes=0
    prevColor = surfOrFile[0] >> 7 #prevColor = nocolor
    for pixelNr in range(nrPixels):
        r = surfOrFile[pixelNr]
        color = r >> 7 #if (r<128) color = 1 else: color = 0
        if color == prevColor and nrOfColor < 0x7D:# and not isLastPixel:
            nrOfColor = nrOfColor + 1
        else:
            encValue = (prevColor << 7) | nrOfColor  # push color (B/W) to highest bit and repetitions to lowest 7 bits.
            rleData[nrBytes]=encValue
            nrBytes = nrBytes+1
            prevColor = color
            nrOfColor = 1
    # Handle lastpixel, we did nrOfColor++ once too many
    nrOfColor=nrOfColor-1
    encValue = (prevColor << 7) | nrOfColor  # push color (B/W) to highest bit and repetitions to lowest 7 bits.
    rleData[nrBytes] = encValue
    nrBytes = nrBytes + 1

    # Remove excess bytes and return rleData
    rleData=rleData[:nrBytes]
    return bytes(rleData)
...