-
-
Notifications
You must be signed in to change notification settings - Fork 32.3k
bpo-41486: Add _BlocksOutputBuffer for bz2/lzma/zlib modules #21740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
1a14b09
1. Add _BlocksOutputBuffer
wjssz 8b6dd9a
2. lzma module
wjssz 65f227b
3. bz2 module
wjssz db8d0f2
4. zlib: #include
wjssz de3cfb2
5. zlib: zlib_compress_impl()
wjssz dbb750d
6. zlib_decompress_impl
wjssz 262116f
7. zlib: zlib_Compress_compress_impl()
wjssz 06bac13
8. zlib: zlib_Compress_flush_impl()
wjssz 1d24d9b
9. zlib: zlib_Decompress_decompress_impl()
wjssz d73c830
10. zlib: zlib_Decompress_flush_impl()
wjssz e985647
11. zlib: remove old functions
wjssz 8ad64f0
12. add .readall() to DecompressReader
wjssz c05de06
Merge branch 'master' into blocks_output_buffer
animalize c8f0819
address review comments 1
wjssz 867da95
address review comments 2
wjssz 55705f6
split core code and wrappers
wjssz 45d7526
Merge branch 'master' into blocks_output_buffer
animalize 2964198
address review comments 3
wjssz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,317 @@ | ||
/* | ||
_BlocksOutputBuffer is used to maintain an output buffer | ||
that has unpredictable size. Suitable for compression/decompression | ||
API (bz2/lzma/zlib) that has stream->next_out and stream->avail_out: | ||
|
||
stream->next_out: point to the next output position. | ||
stream->avail_out: the number of available bytes left in the buffer. | ||
|
||
It maintains a list of bytes object, so there is no overhead of resizing | ||
the buffer. | ||
|
||
Usage: | ||
|
||
1, Initialize the struct instance like this: | ||
_BlocksOutputBuffer buffer = {.list = NULL}; | ||
Set .list to NULL for _BlocksOutputBuffer_OnError() | ||
|
||
2, Initialize the buffer use one of these functions: | ||
_BlocksOutputBuffer_InitAndGrow() | ||
_BlocksOutputBuffer_InitWithSize() | ||
|
||
3, If (avail_out == 0), grow the buffer: | ||
_BlocksOutputBuffer_Grow() | ||
|
||
4, Get the current outputted data size: | ||
_BlocksOutputBuffer_GetDataSize() | ||
|
||
5, Finish the buffer, and return a bytes object: | ||
_BlocksOutputBuffer_Finish() | ||
|
||
6, Clean up the buffer when an error occurred: | ||
_BlocksOutputBuffer_OnError() | ||
*/ | ||
|
||
#ifndef Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H | ||
#define Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H | ||
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
|
||
#include "Python.h" | ||
|
||
typedef struct { | ||
// List of bytes objects | ||
PyObject *list; | ||
// Number of whole allocated size | ||
Py_ssize_t allocated; | ||
// Max length of the buffer, negative number means unlimited length. | ||
Py_ssize_t max_length; | ||
} _BlocksOutputBuffer; | ||
|
||
static const char unable_allocate_msg[] = "Unable to allocate output buffer."; | ||
|
||
/* In 32-bit build, the max block size should <= INT32_MAX. */ | ||
#define OUTPUT_BUFFER_MAX_BLOCK_SIZE (256*1024*1024) | ||
|
||
/* Block size sequence */ | ||
#define KB (1024) | ||
#define MB (1024*1024) | ||
const Py_ssize_t BUFFER_BLOCK_SIZE[] = | ||
{ 32*KB, 64*KB, 256*KB, 1*MB, 4*MB, 8*MB, 16*MB, 16*MB, | ||
32*MB, 32*MB, 32*MB, 32*MB, 64*MB, 64*MB, 128*MB, 128*MB, | ||
OUTPUT_BUFFER_MAX_BLOCK_SIZE }; | ||
#undef KB | ||
#undef MB | ||
|
||
/* According to the block sizes defined by BUFFER_BLOCK_SIZE, the whole | ||
allocated size growth step is: | ||
1 32 KB +32 KB | ||
2 96 KB +64 KB | ||
3 352 KB +256 KB | ||
4 1.34 MB +1 MB | ||
5 5.34 MB +4 MB | ||
6 13.34 MB +8 MB | ||
7 29.34 MB +16 MB | ||
8 45.34 MB +16 MB | ||
9 77.34 MB +32 MB | ||
10 109.34 MB +32 MB | ||
11 141.34 MB +32 MB | ||
12 173.34 MB +32 MB | ||
13 237.34 MB +64 MB | ||
14 301.34 MB +64 MB | ||
15 429.34 MB +128 MB | ||
16 557.34 MB +128 MB | ||
17 813.34 MB +256 MB | ||
18 1069.34 MB +256 MB | ||
19 1325.34 MB +256 MB | ||
20 1581.34 MB +256 MB | ||
21 1837.34 MB +256 MB | ||
22 2093.34 MB +256 MB | ||
... | ||
*/ | ||
|
||
/* Initialize the buffer, and grow the buffer. | ||
|
||
max_length: Max length of the buffer, -1 for unlimited length. | ||
|
||
On success, return allocated size (>=0) | ||
On failure, return -1 | ||
*/ | ||
static inline Py_ssize_t | ||
_BlocksOutputBuffer_InitAndGrow(_BlocksOutputBuffer *buffer, | ||
const Py_ssize_t max_length, | ||
void **next_out) | ||
{ | ||
PyObject *b; | ||
Py_ssize_t block_size; | ||
|
||
// ensure .list was set to NULL | ||
assert(buffer->list == NULL); | ||
|
||
// get block size | ||
if (0 <= max_length && max_length < BUFFER_BLOCK_SIZE[0]) { | ||
block_size = max_length; | ||
} else { | ||
block_size = BUFFER_BLOCK_SIZE[0]; | ||
} | ||
|
||
// the first block | ||
b = PyBytes_FromStringAndSize(NULL, block_size); | ||
if (b == NULL) { | ||
return -1; | ||
} | ||
|
||
// create the list | ||
buffer->list = PyList_New(1); | ||
if (buffer->list == NULL) { | ||
Py_DECREF(b); | ||
return -1; | ||
} | ||
PyList_SET_ITEM(buffer->list, 0, b); | ||
|
||
// set variables | ||
buffer->allocated = block_size; | ||
buffer->max_length = max_length; | ||
|
||
*next_out = PyBytes_AS_STRING(b); | ||
return block_size; | ||
} | ||
|
||
/* Initialize the buffer, with an initial size. | ||
|
||
Check block size limit in the outer wrapper function. For example, some libs | ||
accept UINT32_MAX as the maximum block size, then init_size should <= it. | ||
|
||
On success, return allocated size (>=0) | ||
On failure, return -1 | ||
*/ | ||
static inline Py_ssize_t | ||
_BlocksOutputBuffer_InitWithSize(_BlocksOutputBuffer *buffer, | ||
const Py_ssize_t init_size, | ||
void **next_out) | ||
{ | ||
PyObject *b; | ||
|
||
// ensure .list was set to NULL | ||
assert(buffer->list == NULL); | ||
|
||
// the first block | ||
b = PyBytes_FromStringAndSize(NULL, init_size); | ||
if (b == NULL) { | ||
PyErr_SetString(PyExc_MemoryError, unable_allocate_msg); | ||
return -1; | ||
} | ||
|
||
// create the list | ||
buffer->list = PyList_New(1); | ||
if (buffer->list == NULL) { | ||
Py_DECREF(b); | ||
return -1; | ||
} | ||
PyList_SET_ITEM(buffer->list, 0, b); | ||
|
||
// set variables | ||
buffer->allocated = init_size; | ||
buffer->max_length = -1; | ||
|
||
*next_out = PyBytes_AS_STRING(b); | ||
return init_size; | ||
} | ||
|
||
/* Grow the buffer. The avail_out must be 0, please check it before calling. | ||
|
||
On success, return allocated size (>=0) | ||
On failure, return -1 | ||
*/ | ||
static inline Py_ssize_t | ||
_BlocksOutputBuffer_Grow(_BlocksOutputBuffer *buffer, | ||
void **next_out, | ||
const Py_ssize_t avail_out) | ||
{ | ||
PyObject *b; | ||
const Py_ssize_t list_len = Py_SIZE(buffer->list); | ||
Py_ssize_t block_size; | ||
|
||
// ensure no gaps in the data | ||
if (avail_out != 0) { | ||
PyErr_SetString(PyExc_SystemError, | ||
"avail_out is non-zero in _BlocksOutputBuffer_Grow()."); | ||
return -1; | ||
} | ||
|
||
// get block size | ||
if (list_len < (Py_ssize_t) Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE)) { | ||
block_size = BUFFER_BLOCK_SIZE[list_len]; | ||
} else { | ||
block_size = BUFFER_BLOCK_SIZE[Py_ARRAY_LENGTH(BUFFER_BLOCK_SIZE) - 1]; | ||
} | ||
|
||
// check max_length | ||
if (buffer->max_length >= 0) { | ||
// if (rest == 0), should not grow the buffer. | ||
Py_ssize_t rest = buffer->max_length - buffer->allocated; | ||
assert(rest > 0); | ||
|
||
// block_size of the last block | ||
if (block_size > rest) { | ||
block_size = rest; | ||
} | ||
} | ||
|
||
// check buffer->allocated overflow | ||
if (block_size > PY_SSIZE_T_MAX - buffer->allocated) { | ||
PyErr_SetString(PyExc_MemoryError, unable_allocate_msg); | ||
return -1; | ||
} | ||
|
||
// create the block | ||
b = PyBytes_FromStringAndSize(NULL, block_size); | ||
if (b == NULL) { | ||
PyErr_SetString(PyExc_MemoryError, unable_allocate_msg); | ||
return -1; | ||
} | ||
if (PyList_Append(buffer->list, b) < 0) { | ||
Py_DECREF(b); | ||
return -1; | ||
} | ||
Py_DECREF(b); | ||
|
||
// set variables | ||
buffer->allocated += block_size; | ||
|
||
*next_out = PyBytes_AS_STRING(b); | ||
return block_size; | ||
} | ||
|
||
/* Return the current outputted data size. */ | ||
static inline Py_ssize_t | ||
_BlocksOutputBuffer_GetDataSize(_BlocksOutputBuffer *buffer, | ||
const Py_ssize_t avail_out) | ||
{ | ||
return buffer->allocated - avail_out; | ||
} | ||
|
||
/* Finish the buffer. | ||
|
||
Return a bytes object on success | ||
Return NULL on failure | ||
*/ | ||
static inline PyObject * | ||
_BlocksOutputBuffer_Finish(_BlocksOutputBuffer *buffer, | ||
const Py_ssize_t avail_out) | ||
{ | ||
PyObject *result, *block; | ||
const Py_ssize_t list_len = Py_SIZE(buffer->list); | ||
|
||
// fast path for single block | ||
if ((list_len == 1 && avail_out == 0) || | ||
(list_len == 2 && Py_SIZE(PyList_GET_ITEM(buffer->list, 1)) == avail_out)) | ||
{ | ||
block = PyList_GET_ITEM(buffer->list, 0); | ||
Py_INCREF(block); | ||
|
||
Py_CLEAR(buffer->list); | ||
return block; | ||
} | ||
|
||
// final bytes object | ||
result = PyBytes_FromStringAndSize(NULL, buffer->allocated - avail_out); | ||
if (result == NULL) { | ||
PyErr_SetString(PyExc_MemoryError, unable_allocate_msg); | ||
return NULL; | ||
} | ||
|
||
// memory copy | ||
if (list_len > 0) { | ||
char *posi = PyBytes_AS_STRING(result); | ||
|
||
// blocks except the last one | ||
Py_ssize_t i = 0; | ||
for (; i < list_len-1; i++) { | ||
block = PyList_GET_ITEM(buffer->list, i); | ||
memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block)); | ||
posi += Py_SIZE(block); | ||
} | ||
// the last block | ||
block = PyList_GET_ITEM(buffer->list, i); | ||
memcpy(posi, PyBytes_AS_STRING(block), Py_SIZE(block) - avail_out); | ||
} else { | ||
assert(Py_SIZE(result) == 0); | ||
} | ||
|
||
Py_CLEAR(buffer->list); | ||
return result; | ||
} | ||
|
||
/* Clean up the buffer when an error occurred. */ | ||
static inline void | ||
_BlocksOutputBuffer_OnError(_BlocksOutputBuffer *buffer) | ||
{ | ||
Py_CLEAR(buffer->list); | ||
} | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif | ||
#endif /* Py_INTERNAL_BLOCKS_OUTPUT_BUFFER_H */ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
Misc/NEWS.d/next/Library/2020-10-16-15-34-30.bpo-41486.Mu9Iit.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Use a new output buffer management code for :mod:`bz2` / :mod:`lzma` / | ||
:mod:`zlib` modules, and add ``.readall()`` function to | ||
``_compression.DecompressReader`` class. These bring some performance | ||
improvements. Patch by Ma Lin. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.