From 72c0aa37fe0104f347242a652f3ab3683967bbf0 Mon Sep 17 00:00:00 2001 From: Emil Velikov Date: Thu, 19 Sep 2024 13:14:58 +0100 Subject: [PATCH] libkmod: use bufferless zstd decompression Unlike the other two decompressions, zstd supports streaming bufferless mode. Meaning we don't need to read and realloc in a loop. Some strace numbers: $ strace -e read ./before/depmod -o /tmp/throaway | wc -l 35265 $ strace -e fstat ./before/depmod -o /tmp/throaway | wc -l 1110 $ strace -e read ./after/depmod -o /tmp/throaway | wc -l 5677 $ strace -e fstat ./after/depmod -o /tmp/throaway | wc -l 6642 .. and valgrind total heap usage statistics: before: 1,039,426 allocs, 1,039,426 frees, 3,679,232,922 bytes allocated after: 1,020,643 allocs, 1,020,643 frees, 1,157,922,357 bytes allocated The actual runtime is within the error margin. v2: - use ZSTD_decompress(), which allocates ZSTD_DCtx internally Signed-off-by: Emil Velikov Link: https://github.com/kmod-project/kmod/pull/142 Signed-off-by: Lucas De Marchi --- libkmod/libkmod-file-zstd.c | 151 +++++++++++------------------------- 1 file changed, 45 insertions(+), 106 deletions(-) diff --git a/libkmod/libkmod-file-zstd.c b/libkmod/libkmod-file-zstd.c index 1aec6381..098d1074 100644 --- a/libkmod/libkmod-file-zstd.c +++ b/libkmod/libkmod-file-zstd.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -18,130 +19,68 @@ #include "libkmod-internal.h" #include "libkmod-internal-file.h" -static int zstd_read_block(struct kmod_file *file, size_t block_size, - ZSTD_inBuffer *input, size_t *input_capacity) +int kmod_file_load_zstd(struct kmod_file *file) { - ssize_t rdret; - int ret; - - if (*input_capacity < block_size) { - free((void *)input->src); - input->src = malloc(block_size); - if (input->src == NULL) { - ret = -errno; - ERR(file->ctx, "zstd: %m\n"); - return ret; - } - *input_capacity = block_size; - } + void *src_buf = MAP_FAILED, *dst_buf = NULL; + size_t src_size, dst_size, ret; + unsigned long long frame_size; + struct stat st; - rdret = read(file->fd, (void *)input->src, block_size); - if (rdret < 0) { + if (fstat(file->fd, &st) < 0) { ret = -errno; ERR(file->ctx, "zstd: %m\n"); - return ret; + goto out; } - input->pos = 0; - input->size = rdret; - return 0; -} - -static int zstd_ensure_outbuffer_space(ZSTD_outBuffer *buffer, size_t min_free) -{ - uint8_t *old_buffer = buffer->dst; - int ret = 0; - - if (buffer->size - buffer->pos >= min_free) - return 0; - - if (buffer->size < min_free) - buffer->size = min_free; - else - buffer->size *= 2; + if ((uintmax_t)st.st_size > SIZE_MAX) { + ret = -ENOMEM; + goto out; + } - buffer->size += min_free; - buffer->dst = realloc(buffer->dst, buffer->size); - if (buffer->dst == NULL) { + src_size = st.st_size; + src_buf = mmap(NULL, src_size, PROT_READ, MAP_PRIVATE, file->fd, 0); + if (src_buf == MAP_FAILED) { ret = -errno; - free(old_buffer); + goto out; } - return ret; -} - -static int zstd_decompress_block(struct kmod_file *file, ZSTD_DStream *dstr, - ZSTD_inBuffer *input, ZSTD_outBuffer *output, - size_t *next_block_size) -{ - size_t out_buf_min_size = ZSTD_DStreamOutSize(); - int ret = 0; - - do { - ssize_t dsret; - - ret = zstd_ensure_outbuffer_space(output, out_buf_min_size); - if (ret) { - ERR(file->ctx, "zstd: %s\n", strerror(-ret)); - break; - } - - dsret = ZSTD_decompressStream(dstr, output, input); - if (ZSTD_isError(dsret)) { - ret = -EINVAL; - ERR(file->ctx, "zstd: %s\n", ZSTD_getErrorName(dsret)); - break; - } - if (dsret > 0) - *next_block_size = (size_t)dsret; - } while (input->pos < input->size - || output->pos > output->size - || output->size - output->pos < out_buf_min_size); - - return ret; -} - -int kmod_file_load_zstd(struct kmod_file *file) -{ - ZSTD_DStream *dstr; - size_t next_block_size; - size_t zst_inb_capacity = 0; - ZSTD_inBuffer zst_inb = { 0 }; - ZSTD_outBuffer zst_outb = { 0 }; - int ret; - - dstr = ZSTD_createDStream(); - if (dstr == NULL) { + frame_size = ZSTD_getFrameContentSize(src_buf, src_size); + if (frame_size == 0 || frame_size == ZSTD_CONTENTSIZE_UNKNOWN || + frame_size == ZSTD_CONTENTSIZE_ERROR) { ret = -EINVAL; - ERR(file->ctx, "zstd: Failed to create decompression stream\n"); + ERR(file->ctx, "zstd: Failed to determine decompression size\n"); goto out; } - next_block_size = ZSTD_initDStream(dstr); + if (frame_size > SIZE_MAX) { + ret = -ENOMEM; + goto out; + } - while (true) { - ret = zstd_read_block(file, next_block_size, &zst_inb, - &zst_inb_capacity); - if (ret != 0) - goto out; - if (zst_inb.size == 0) /* EOF */ - break; + dst_size = frame_size; + dst_buf = malloc(dst_size); + if (dst_buf == NULL) { + ret = -errno; + goto out; + } - ret = zstd_decompress_block(file, dstr, &zst_inb, &zst_outb, - &next_block_size); - if (ret != 0) - goto out; + ret = ZSTD_decompress(dst_buf, dst_size, src_buf, src_size); + if (ZSTD_isError(ret)) { + ERR(file->ctx, "zstd: %s\n", ZSTD_getErrorName(ret)); + goto out; } - ZSTD_freeDStream(dstr); - free((void *)zst_inb.src); - file->memory = zst_outb.dst; - file->size = zst_outb.pos; - return 0; + file->memory = dst_buf; + file->size = dst_size; + + ret = 0; + dst_buf = NULL; + out: - if (dstr != NULL) - ZSTD_freeDStream(dstr); - free((void *)zst_inb.src); - free((void *)zst_outb.dst); + free(dst_buf); + + if (src_buf != MAP_FAILED) + munmap(src_buf, src_size); + return ret; }