Skip to content

Commit e2abbf2

Browse files
authored
Merge pull request #135 from pycompression/allow_threads
Add GIL escape to checksums and compress/decompress
2 parents 83ef50e + 793809b commit e2abbf2

File tree

4 files changed

+90
-8
lines changed

4 files changed

+90
-8
lines changed

CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ Changelog
77
.. This document is user facing. Please word the changes in such a way
88
.. that users understand how the changes affect the new version.
99
10+
version 1.2.0-dev
11+
-----------------
12+
+ Escape GIL when calling inflate, deflate, crc32 and adler32 functions to
13+
just like in CPython. This allows for utilising more CPU cores in combination
14+
with the threading module. This comes with a very slight cost in efficiency
15+
for strict single-threaded applications.
16+
1017
version 1.1.0
1118
-----------------
1219
+ Added tests and support for Python 3.11.

src/isal/igzip_libmodule.c

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ Changes compared to CPython:
2323

2424
typedef struct {
2525
PyObject_HEAD
26-
PyObject *unused_data;
26+
PyObject *unused_data;
27+
PyThread_type_lock lock;
2728
PyObject *zdict;
2829
uint8_t *input_buffer;
2930
Py_ssize_t input_buffer_size;
@@ -41,6 +42,7 @@ typedef struct {
4142
static void
4243
IgzipDecompressor_dealloc(IgzipDecompressor *self)
4344
{
45+
PyThread_free_lock(self->lock);
4446
PyMem_Free(self->input_buffer);
4547
Py_CLEAR(self->unused_data);
4648
Py_CLEAR(self->zdict);
@@ -99,7 +101,10 @@ decompress_buf(IgzipDecompressor *self, Py_ssize_t max_length)
99101
else if (obuflen == -2)
100102
break;
101103

104+
Py_BEGIN_ALLOW_THREADS;
102105
err = isal_inflate(&(self->state));
106+
Py_END_ALLOW_THREADS;
107+
103108
if (err != ISAL_DECOMP_OK){
104109
isal_inflate_error(err);
105110
goto error;
@@ -357,18 +362,23 @@ igzip_lib_IgzipDecompressor_decompress(IgzipDecompressor *self,
357362
{
358363
static char *keywords[] = {"", "max_length", NULL};
359364
static char *format = "y*|n:decompress";
365+
366+
PyObject *result = NULL;
360367
Py_buffer data = {NULL, NULL};
361368
Py_ssize_t max_length = -1;
362369

363370
if (!PyArg_ParseTupleAndKeywords(
364371
args, kwargs, format, keywords, &data, &max_length)) {
365372
return NULL;
366373
}
367-
PyObject *result = NULL;
368-
if (self->eof)
374+
ENTER_ZLIB(self);
375+
if (self->eof) {
369376
PyErr_SetString(PyExc_EOFError, "End of stream already reached");
370-
else
377+
}
378+
else {
371379
result = decompress(self, data.buf, data.len, max_length);
380+
}
381+
LEAVE_ZLIB(self);
372382
PyBuffer_Release(&data);
373383
return result;
374384
}
@@ -416,6 +426,12 @@ igzip_lib_IgzipDecompressor__new__(PyTypeObject *type,
416426
Py_CLEAR(self);
417427
return NULL;
418428
}
429+
self->lock = PyThread_allocate_lock();
430+
if (self->lock == NULL) {
431+
Py_DECREF(self);
432+
PyErr_SetString(PyExc_MemoryError, "Unable to allocate lock");
433+
return NULL;
434+
}
419435
isal_inflate_init(&(self->state));
420436
self->state.hist_bits = hist_bits;
421437
self->state.crc_flag = flag;

src/isal/isal_shared.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ Changes compared to CPython:
2828

2929
static PyObject *IsalError;
3030

31+
#define ENTER_ZLIB(obj) do { \
32+
if (!PyThread_acquire_lock((obj)->lock, 0)) { \
33+
Py_BEGIN_ALLOW_THREADS \
34+
PyThread_acquire_lock((obj)->lock, 1); \
35+
Py_END_ALLOW_THREADS \
36+
} } while (0)
37+
#define LEAVE_ZLIB(obj) PyThread_release_lock((obj)->lock);
38+
3139
/* Initial buffer size. */
3240
#define DEF_BUF_SIZE (16*1024)
3341
#define DEF_MAX_INITIAL_BUF_SIZE (16 * 1024 * 1024)
@@ -317,7 +325,10 @@ igzip_lib_compress_impl(Py_buffer *data,
317325
"Unsufficient memory for buffer allocation");
318326
goto error;
319327
}
328+
329+
Py_BEGIN_ALLOW_THREADS
320330
err = isal_deflate(&zst);
331+
Py_END_ALLOW_THREADS
321332

322333
if (err != COMP_OK) {
323334
isal_deflate_error(err);
@@ -376,7 +387,10 @@ igzip_lib_decompress_impl(Py_buffer *data, int flag,
376387
goto error;
377388
}
378389

390+
Py_BEGIN_ALLOW_THREADS
379391
err = isal_inflate(&zst);
392+
Py_END_ALLOW_THREADS
393+
380394
if (err != ISAL_DECOMP_OK) {
381395
isal_inflate_error(err);
382396
goto error;

src/isal/isal_zlibmodule.c

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,9 @@ isal_zlib_adler32(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
174174
return NULL;
175175
}
176176
}
177+
Py_BEGIN_ALLOW_THREADS
177178
value = isal_adler32(value, data.buf, (uint64_t)data.len);
179+
Py_END_ALLOW_THREADS
178180
return_value = PyLong_FromUnsignedLong(value & 0xffffffffU);
179181
PyBuffer_Release(&data);
180182
return return_value;
@@ -219,7 +221,9 @@ isal_zlib_crc32(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
219221
return NULL;
220222
}
221223
}
224+
Py_BEGIN_ALLOW_THREADS
222225
value = crc32_gzip_refl(value, data.buf, (uint64_t)data.len);
226+
Py_END_ALLOW_THREADS
223227
return_value = PyLong_FromUnsignedLong(value & 0xffffffffU);
224228
PyBuffer_Release(&data);
225229
return return_value;
@@ -325,6 +329,7 @@ typedef struct
325329
uint8_t *level_buf;
326330
PyObject *zdict;
327331
int is_initialised;
332+
PyThread_type_lock lock;
328333
// isal_zstream should be at the bottom as it contains buffers inside the struct.
329334
struct isal_zstream zst;
330335
} compobject;
@@ -334,6 +339,7 @@ Comp_dealloc(compobject *self)
334339
{
335340
if (self->is_initialised && self->level_buf != NULL)
336341
PyMem_Free(self->level_buf);
342+
PyThread_free_lock(self->lock);
337343
Py_XDECREF(self->zdict);
338344
Py_TYPE(self)->tp_free((PyObject *)self);
339345
}
@@ -348,6 +354,12 @@ newcompobject()
348354
self->is_initialised = 0;
349355
self->zdict = NULL;
350356
self->level_buf = NULL;
357+
self->lock = PyThread_allocate_lock();
358+
if (self->lock == NULL) {
359+
Py_DECREF(self);
360+
PyErr_SetString(PyExc_MemoryError, "Unable to allocate lock");
361+
return NULL;
362+
}
351363
return self;
352364
}
353365

@@ -361,13 +373,15 @@ typedef struct
361373
int is_initialised;
362374
int method_set;
363375
char eof;
376+
PyThread_type_lock lock;
364377
// inflate_state should be at the bottom as it contains buffers inside the struct.
365378
struct inflate_state zst;
366379
} decompobject;
367380

368381
static void
369382
Decomp_dealloc(decompobject *self)
370383
{
384+
PyThread_free_lock(self->lock);
371385
Py_XDECREF(self->unused_data);
372386
Py_XDECREF(self->unconsumed_tail);
373387
Py_XDECREF(self->zdict);
@@ -419,6 +433,12 @@ newdecompobject()
419433
if (self->unconsumed_tail == NULL) {
420434
Py_DECREF(self);
421435
return NULL;
436+
}
437+
self->lock = PyThread_allocate_lock();
438+
if (self->lock == NULL) {
439+
Py_DECREF(self);
440+
PyErr_SetString(PyExc_MemoryError, "Unable to allocate lock");
441+
return NULL;
422442
}
423443
return self;
424444
}
@@ -562,6 +582,8 @@ isal_zlib_Compress_compress_impl(compobject *self, Py_buffer *data)
562582
Py_ssize_t ibuflen, obuflen = DEF_BUF_SIZE;
563583
int err;
564584

585+
ENTER_ZLIB(self);
586+
565587
self->zst.next_in = data->buf;
566588
ibuflen = data->len;
567589

@@ -573,7 +595,9 @@ isal_zlib_Compress_compress_impl(compobject *self, Py_buffer *data)
573595
if (obuflen < 0)
574596
goto error;
575597

598+
Py_BEGIN_ALLOW_THREADS
576599
err = isal_deflate(&self->zst);
600+
Py_END_ALLOW_THREADS
577601

578602
if (err != COMP_OK) {
579603
isal_deflate_error(err);
@@ -591,6 +615,7 @@ isal_zlib_Compress_compress_impl(compobject *self, Py_buffer *data)
591615
error:
592616
Py_CLEAR(RetVal);
593617
success:
618+
LEAVE_ZLIB(self);
594619
return RetVal;
595620
}
596621

@@ -668,13 +693,16 @@ isal_zlib_Decompress_decompress_impl(decompobject *self, Py_buffer *data,
668693
}
669694
self->method_set = 1;
670695
}
671-
self->zst.next_in = data->buf;
672-
ibuflen = data->len;
673696

674697
/* limit amount of data allocated to max_length */
675698
if (max_length && obuflen > max_length)
676699
obuflen = max_length;
677700

701+
ENTER_ZLIB(self);
702+
703+
self->zst.next_in = data->buf;
704+
ibuflen = data->len;
705+
678706
do {
679707
arrange_input_buffer(&(self->zst.avail_in), &ibuflen);
680708

@@ -693,7 +721,10 @@ isal_zlib_Decompress_decompress_impl(decompobject *self, Py_buffer *data,
693721
goto abort;
694722
}
695723

724+
Py_BEGIN_ALLOW_THREADS
696725
err = isal_inflate(&self->zst);
726+
Py_END_ALLOW_THREADS
727+
697728
if (err != ISAL_DECOMP_OK){
698729
isal_inflate_error(err);
699730
goto abort;
@@ -719,6 +750,7 @@ isal_zlib_Decompress_decompress_impl(decompobject *self, Py_buffer *data,
719750
abort:
720751
Py_CLEAR(RetVal);
721752
success:
753+
LEAVE_ZLIB(self);
722754
return RetVal;
723755
}
724756

@@ -732,8 +764,12 @@ isal_zlib_Compress_flush_impl(compobject *self, int mode)
732764
/* Flushing with Z_NO_FLUSH is a no-op, so there's no point in
733765
doing any work at all; just return an empty string. */
734766
if (mode == Z_NO_FLUSH) {
735-
return PyBytes_FromStringAndSize(NULL, 0);
736-
} else if (mode == Z_FINISH) {
767+
return PyBytes_FromStringAndSize(NULL, 0);
768+
}
769+
770+
ENTER_ZLIB(self);
771+
772+
if (mode == Z_FINISH) {
737773
self->zst.flush = FULL_FLUSH;
738774
self->zst.end_of_stream = 1;
739775
} else if (mode == Z_FULL_FLUSH){
@@ -756,7 +792,9 @@ isal_zlib_Compress_flush_impl(compobject *self, int mode)
756792
goto error;
757793
}
758794

795+
Py_BEGIN_ALLOW_THREADS
759796
err = isal_deflate(&self->zst);
797+
Py_END_ALLOW_THREADS
760798

761799
if (err != COMP_OK) {
762800
isal_deflate_error(err);
@@ -784,6 +822,7 @@ isal_zlib_Compress_flush_impl(compobject *self, int mode)
784822
Py_CLEAR(RetVal);
785823

786824
error:
825+
LEAVE_ZLIB(self);
787826
return RetVal;
788827
}
789828

@@ -800,7 +839,10 @@ isal_zlib_Decompress_flush_impl(decompobject *self, Py_ssize_t length)
800839
return NULL;
801840
}
802841

842+
ENTER_ZLIB(self);
843+
803844
if (PyObject_GetBuffer(self->unconsumed_tail, &data, PyBUF_SIMPLE) == -1) {
845+
LEAVE_ZLIB(self);
804846
return NULL;
805847
}
806848

@@ -817,7 +859,9 @@ isal_zlib_Decompress_flush_impl(decompobject *self, Py_ssize_t length)
817859
if (length < 0)
818860
goto abort;
819861

862+
Py_BEGIN_ALLOW_THREADS
820863
err = isal_inflate(&self->zst);
864+
Py_END_ALLOW_THREADS
821865

822866
if (err != ISAL_DECOMP_OK) {
823867
isal_inflate_error(err);
@@ -846,6 +890,7 @@ isal_zlib_Decompress_flush_impl(decompobject *self, Py_ssize_t length)
846890
Py_CLEAR(RetVal);
847891
success:
848892
PyBuffer_Release(&data);
893+
LEAVE_ZLIB(self);
849894
return RetVal;
850895
}
851896

0 commit comments

Comments
 (0)