Skip to content

Commit b69c692

Browse files
authored
speedups
* add more static typing to cython to speedup * release gil * add multithread example
1 parent c4cdc97 commit b69c692

3 files changed

Lines changed: 39 additions & 10 deletions

File tree

src/qoi/qoi.pxd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cdef extern from "phoboslab_qoi/qoi.h":
1+
cdef extern from "phoboslab_qoi/qoi.h" nogil:
22
ctypedef struct qoi_desc:
33
unsigned int width
44
unsigned int height

src/qoi/qoi.pyx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ from pathlib import Path
88

99
np.import_array()
1010

11+
ctypedef np.uint8_t DTYPE_t
1112

1213
class QOIColorSpace(enum.Enum):
1314
SRGB = qoi.QOI_SRGB
@@ -16,9 +17,9 @@ class QOIColorSpace(enum.Enum):
1617
cdef class PixelWrapper:
1718
cdef void* pixels
1819

19-
cdef np.ndarray as_ndarray(self, int height, int width, int channels, char * pixels):
20+
cdef np.ndarray[DTYPE_t, ndim=3] as_ndarray(self, int height, int width, int channels, char * pixels):
2021
cdef np.npy_intp shape[3]
21-
cdef np.ndarray ndarray
22+
cdef np.ndarray[DTYPE_t, ndim=3] ndarray
2223
self.pixels = pixels
2324
shape[:] = (height, width, channels)
2425
ndarray = np.PyArray_SimpleNewFromData(3, shape, np.NPY_UINT8, self.pixels)
@@ -52,12 +53,14 @@ cpdef int write(filename, unsigned char[:,:,::1] rgb, colorspace: QOIColorSpace
5253
# Makes a contiguous copy of the numpy array so we can process bytes directly:
5354
# rgb = np.ascontiguousarray(rgb)
5455

55-
cdef int bytes_written = qoi.qoi_write(_filename, &rgb[0][0][0], &desc)
56+
cdef int bytes_written
57+
with nogil:
58+
bytes_written = qoi.qoi_write(_filename, &rgb[0][0][0], &desc)
5659
if bytes_written == 0:
5760
raise RuntimeError("Failed to write!")
5861
return bytes_written
5962

60-
cpdef np.ndarray read(filename, int channels = 0, unsigned char[::1] colorspace = bytearray(1)):
63+
cpdef np.ndarray[DTYPE_t, ndim=3] read(filename, int channels = 0, unsigned char[::1] colorspace = bytearray(1)):
6164
# TODO: how to return desc.colorspace? A: How about return a tuple of ndarray and a wrapper around struct qoi_desc? or we can add another param like char[:] to simulate pointer
6265
cdef bytes filename_bytes
6366
cdef char* _filename
@@ -71,8 +74,8 @@ cpdef np.ndarray read(filename, int channels = 0, unsigned char[::1] colorspace
7174
filename_bytes = str(filename).encode('utf8')
7275
_filename = filename_bytes
7376

74-
75-
pixels = <char *>qoi.qoi_read(_filename, &desc, channels)
77+
with nogil:
78+
pixels = <char *>qoi.qoi_read(_filename, &desc, channels)
7679
if pixels is NULL:
7780
raise RuntimeError("Failed to read!")
7881
try:
@@ -97,7 +100,8 @@ cpdef bytes encode(unsigned char[:,:,::1] rgb, colorspace: QOIColorSpace = QOICo
97100

98101
# if not rgb.flags['C_CONTIGUOUS']:
99102
# rgb = np.ascontiguousarray(rgb) # makes a contiguous copy of the numpy array so we can read memory directly
100-
encoded = <char *>qoi.qoi_encode(&rgb[0][0][0], &desc, &size)
103+
with nogil:
104+
encoded = <char *>qoi.qoi_encode(&rgb[0][0][0], &desc, &size)
101105
if encoded is NULL or size <= 0:
102106
raise RuntimeError("Failed to encode!")
103107
try:
@@ -106,12 +110,13 @@ cpdef bytes encode(unsigned char[:,:,::1] rgb, colorspace: QOIColorSpace = QOICo
106110
finally:
107111
PyMem_Free(encoded)
108112

109-
cpdef np.ndarray decode(const unsigned char[::1] data, int channels = 0, unsigned char[::1] colorspace = bytearray(1)):
113+
cpdef np.ndarray[DTYPE_t, ndim=3] decode(const unsigned char[::1] data, int channels = 0, unsigned char[::1] colorspace = bytearray(1)):
110114
# TODO: what to do about desc.colorspace? A: How about return a tuple of ndarray and a wrapper around struct qoi_desc? or we can add another param like char[:] to simulate pointer
111115
cdef qoi.qoi_desc desc
112116
cdef int ret
113117
cdef char * pixels
114-
pixels = <char *>qoi.qoi_decode(&data[0], <int>data.shape[0], &desc, channels)
118+
with nogil:
119+
pixels = <char *>qoi.qoi_decode(&data[0], <int>data.shape[0], &desc, channels)
115120
if pixels is NULL:
116121
raise RuntimeError("Failed to decode!")
117122
try:

tests/test_multithread.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from concurrent.futures import ThreadPoolExecutor, wait
2+
3+
import numpy as np
4+
5+
import qoi
6+
7+
RGB = np.random.randint(low=0, high=255, size=(224, 244, 3)).astype(np.uint8)
8+
RGBA = np.random.randint(low=0, high=255, size=(224, 244, 4)).astype(np.uint8)
9+
10+
11+
def worker():
12+
bites = bytearray(qoi.encode(RGB))
13+
img_decoded = qoi.decode(bites)
14+
# print("done")
15+
16+
17+
def main():
18+
with ThreadPoolExecutor(8) as pool:
19+
futures = [pool.submit(worker) for _ in range(10000)]
20+
wait(futures)
21+
print("Did you see that in top? Congratulations you have bypass the gil")
22+
23+
if __name__ == "__main__":
24+
main()

0 commit comments

Comments
 (0)