mirror of
https://github.com/facebook/zstd.git
synced 2025-07-05 15:09:03 +02:00
As reported by @P-E-Meunier in https://github.com/facebook/zstd/issues/2662#issuecomment-1443836186, seekable format ingestion speed can be particularly slow when selected `FRAME_SIZE` is very small, especially in combination with the recent row_hash compression mode. The specific scenario mentioned was `pijul`, using frame sizes of 256 bytes and level 10. This is improved in this PR, by providing approximate parameter adaptation to the compression process. Tested locally on a M1 laptop, ingestion of `enwik8` using `pijul` parameters went from 35sec. (before this PR) to 2.5sec (with this PR). For the specific corner case of a file full of zeroes, this is even more pronounced, going from 45sec. to 0.5sec. These benefits are unrelated to (and come on top of) other improvement efforts currently being made by @yoniko for the row_hash compression method specifically. The `seekable_compress` test program has been updated to allows setting compression level, in order to produce these performance results.
137 lines
4.8 KiB
C
137 lines
4.8 KiB
C
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under both the BSD-style license (found in the
|
|
* LICENSE file in the root directory of this source tree) and the GPLv2 (found
|
|
* in the COPYING file in the root directory of this source tree).
|
|
*/
|
|
|
|
#include <stdlib.h> // malloc, free, exit, atoi
|
|
#include <stdio.h> // fprintf, perror, feof, fopen, etc.
|
|
#include <string.h> // strlen, memset, strcat
|
|
#define ZSTD_STATIC_LINKING_ONLY
|
|
#include <zstd.h> // presumes zstd library is installed
|
|
|
|
#include "../zstd_seekable.h"
|
|
|
|
static void* malloc_orDie(size_t size)
|
|
{
|
|
void* const buff = malloc(size);
|
|
if (buff) return buff;
|
|
/* error */
|
|
perror("malloc:");
|
|
exit(1);
|
|
}
|
|
|
|
static FILE* fopen_orDie(const char *filename, const char *instruction)
|
|
{
|
|
FILE* const inFile = fopen(filename, instruction);
|
|
if (inFile) return inFile;
|
|
/* error */
|
|
perror(filename);
|
|
exit(3);
|
|
}
|
|
|
|
static size_t fread_orDie(void* buffer, size_t sizeToRead, FILE* file)
|
|
{
|
|
size_t const readSize = fread(buffer, 1, sizeToRead, file);
|
|
if (readSize == sizeToRead) return readSize; /* good */
|
|
if (feof(file)) return readSize; /* good, reached end of file */
|
|
/* error */
|
|
perror("fread");
|
|
exit(4);
|
|
}
|
|
|
|
static size_t fwrite_orDie(const void* buffer, size_t sizeToWrite, FILE* file)
|
|
{
|
|
size_t const writtenSize = fwrite(buffer, 1, sizeToWrite, file);
|
|
if (writtenSize == sizeToWrite) return sizeToWrite; /* good */
|
|
/* error */
|
|
perror("fwrite");
|
|
exit(5);
|
|
}
|
|
|
|
static size_t fclose_orDie(FILE* file)
|
|
{
|
|
if (!fclose(file)) return 0;
|
|
/* error */
|
|
perror("fclose");
|
|
exit(6);
|
|
}
|
|
|
|
static void compressFile_orDie(const char* fname, const char* outName, int cLevel, unsigned frameSize)
|
|
{
|
|
FILE* const fin = fopen_orDie(fname, "rb");
|
|
FILE* const fout = fopen_orDie(outName, "wb");
|
|
size_t const buffInSize = ZSTD_CStreamInSize(); /* can always read one full block */
|
|
void* const buffIn = malloc_orDie(buffInSize);
|
|
size_t const buffOutSize = ZSTD_CStreamOutSize(); /* can always flush a full block */
|
|
void* const buffOut = malloc_orDie(buffOutSize);
|
|
|
|
ZSTD_seekable_CStream* const cstream = ZSTD_seekable_createCStream();
|
|
if (cstream==NULL) { fprintf(stderr, "ZSTD_seekable_createCStream() error \n"); exit(10); }
|
|
size_t const initResult = ZSTD_seekable_initCStream(cstream, cLevel, 1, frameSize);
|
|
if (ZSTD_isError(initResult)) { fprintf(stderr, "ZSTD_seekable_initCStream() error : %s \n", ZSTD_getErrorName(initResult)); exit(11); }
|
|
|
|
size_t read, toRead = buffInSize;
|
|
while( (read = fread_orDie(buffIn, toRead, fin)) ) {
|
|
ZSTD_inBuffer input = { buffIn, read, 0 };
|
|
while (input.pos < input.size) {
|
|
ZSTD_outBuffer output = { buffOut, buffOutSize, 0 };
|
|
toRead = ZSTD_seekable_compressStream(cstream, &output , &input); /* toRead is guaranteed to be <= ZSTD_CStreamInSize() */
|
|
if (ZSTD_isError(toRead)) { fprintf(stderr, "ZSTD_seekable_compressStream() error : %s \n", ZSTD_getErrorName(toRead)); exit(12); }
|
|
if (toRead > buffInSize) toRead = buffInSize; /* Safely handle case when `buffInSize` is manually changed to a value < ZSTD_CStreamInSize()*/
|
|
fwrite_orDie(buffOut, output.pos, fout);
|
|
}
|
|
}
|
|
|
|
while (1) {
|
|
ZSTD_outBuffer output = { buffOut, buffOutSize, 0 };
|
|
size_t const remainingToFlush = ZSTD_seekable_endStream(cstream, &output); /* close stream */
|
|
if (ZSTD_isError(remainingToFlush)) { fprintf(stderr, "ZSTD_seekable_endStream() error : %s \n", ZSTD_getErrorName(remainingToFlush)); exit(13); }
|
|
fwrite_orDie(buffOut, output.pos, fout);
|
|
if (!remainingToFlush) break;
|
|
}
|
|
|
|
ZSTD_seekable_freeCStream(cstream);
|
|
fclose_orDie(fout);
|
|
fclose_orDie(fin);
|
|
free(buffIn);
|
|
free(buffOut);
|
|
}
|
|
|
|
static char* createOutFilename_orDie(const char* filename)
|
|
{
|
|
size_t const inL = strlen(filename);
|
|
size_t const outL = inL + 5;
|
|
void* outSpace = malloc_orDie(outL);
|
|
memset(outSpace, 0, outL);
|
|
strcat(outSpace, filename);
|
|
strcat(outSpace, ".zst");
|
|
return (char*)outSpace;
|
|
}
|
|
|
|
#define CLEVEL_DEFAULT 5
|
|
int main(int argc, const char** argv)
|
|
{
|
|
const char* const exeName = argv[0];
|
|
if (argc<3 || argc>4) {
|
|
printf("wrong arguments \n");
|
|
printf("usage: \n");
|
|
printf("%s FILE FRAME_SIZE [LEVEL] \n", exeName);
|
|
return 1;
|
|
}
|
|
|
|
{ const char* const inFileName = argv[1];
|
|
unsigned const frameSize = (unsigned)atoi(argv[2]);
|
|
int const cLevel = (argc==4) ? atoi(argv[3]) : CLEVEL_DEFAULT;
|
|
|
|
char* const outFileName = createOutFilename_orDie(inFileName);
|
|
compressFile_orDie(inFileName, outFileName, cLevel, frameSize);
|
|
free(outFileName);
|
|
}
|
|
|
|
return 0;
|
|
}
|