Skip to content

Commit

Permalink
libkmod: use bufferless zstd decompression
Browse files Browse the repository at this point in the history
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 <[email protected]>
Link: #142
Signed-off-by: Lucas De Marchi <[email protected]>
  • Loading branch information
evelikov authored and lucasdemarchi committed Sep 23, 2024
1 parent 4f8a6a8 commit 72c0aa3
Showing 1 changed file with 45 additions and 106 deletions.
151 changes: 45 additions & 106 deletions libkmod/libkmod-file-zstd.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
Expand All @@ -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;
}

0 comments on commit 72c0aa3

Please sign in to comment.