first working test program

measures :
- compression ratio with / without dictionary
- create one dictionary per block
- memory budget for dictionaries
- decompression speed, using one different dictionary per block

current limitations :
- only one file
- 4K blocks only
- automatic dictionary built with 4K size

dictionary can be selected on command line, with -D
This commit is contained in:
Yann Collet 2018-08-28 15:47:07 -07:00
parent 0491037db9
commit 0c66a44d1b
5 changed files with 162 additions and 15 deletions

2
contrib/largeNbDicts/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# build artifacts
largeNbDicts

View File

@ -7,8 +7,10 @@
# in the COPYING file in the root directory of this source tree). # in the COPYING file in the root directory of this source tree).
# ################################################################ # ################################################################
PROGDIR = ../../programs
LIBDIR = ../../lib
CPPFLAGS+= -I../../lib -I../../lib/common -I../../lib/dictBuilder -I../../programs CPPFLAGS+= -I$(LIBDIR) -I$(LIBDIR)/common -I$(LIBDIR)/dictBuilder -I$(PROGDIR)
CFLAGS ?= -O3 CFLAGS ?= -O3
DEBUGFLAGS= -Wall -Wextra -Wcast-qual -Wcast-align -Wshadow \ DEBUGFLAGS= -Wall -Wextra -Wcast-qual -Wcast-align -Wshadow \
@ -24,9 +26,18 @@ default: largeNbDicts
all : largeNbDicts all : largeNbDicts
largeNbDicts: LDFLAGS += -lzstd largeNbDicts: LDFLAGS += -lzstd
largeNbDicts: largeNbDicts.c largeNbDicts: bench.o datagen.o xxhash.o largeNbDicts.c
$(CC) $(CPPFLAGS) $(CFLAGS) $^ $(LDFLAGS) -o $@ $(CC) $(CPPFLAGS) $(CFLAGS) $^ $(LDFLAGS) -o $@
bench.o : $(PROGDIR)/bench.c
$(CC) $(CPPFLAGS) $(CFLAGS) $^ -c
datagen.o: $(PROGDIR)/datagen.c
$(CC) $(CPPFLAGS) $(CFLAGS) $^ -c
xxhash.o : $(LIBDIR)/common/xxhash.c
$(CC) $(CPPFLAGS) $(CFLAGS) $^ -c
clean: clean:
$(RM) *.o
$(RM) largeNbDicts $(RM) largeNbDicts

Binary file not shown.

View File

@ -12,7 +12,7 @@
* This is a benchmark test tool * This is a benchmark test tool
* dedicated to the specific case of dictionary decompression * dedicated to the specific case of dictionary decompression
* using a very large nb of dictionaries * using a very large nb of dictionaries
* thus generating many cache-misses. * thus suffering latency from lots of cache misses.
* It's created in a bid to investigate performance and find optimizations. */ * It's created in a bid to investigate performance and find optimizations. */
@ -24,6 +24,7 @@
#include <assert.h> /* assert */ #include <assert.h> /* assert */
#include "util.h" #include "util.h"
#include "bench.h"
#define ZSTD_STATIC_LINKING_ONLY #define ZSTD_STATIC_LINKING_ONLY
#include "zstd.h" #include "zstd.h"
#include "zdict.h" #include "zdict.h"
@ -158,6 +159,17 @@ buffer_collection_t splitBuffer(buffer_t srcBuffer, size_t blockSize)
return result; return result;
} }
/* shrinkSizes() :
* update sizes in buffer collection */
void shrinkSizes(buffer_collection_t collection,
const size_t* sizes) /* presumed same size as collection */
{
size_t const nbBlocks = collection.nbBuffers;
for (size_t blockNb = 0; blockNb < nbBlocks; blockNb++) {
assert(sizes[blockNb] <= collection.capacities[blockNb]);
collection.capacities[blockNb] = sizes[blockNb];
}
}
/*--- dictionary creation ---*/ /*--- dictionary creation ---*/
@ -221,13 +233,30 @@ static ddict_collection_t createDDictCollection(const void* dictBuffer, size_t d
} }
/* mess with adresses, so that linear scanning dictionaries != linear address scanning */
void shuffleDictionaries(ddict_collection_t dicts)
{
size_t const nbDicts = dicts.nbDDict;
for (size_t r=0; r<nbDicts; r++) {
size_t const d1 = rand() % nbDicts;
size_t const d2 = rand() % nbDicts;
ZSTD_DDict* tmpd = dicts.ddicts[d1];
dicts.ddicts[d1] = dicts.ddicts[d2];
dicts.ddicts[d2] = tmpd;
}
}
/* --- Compression --- */ /* --- Compression --- */
/* compressBlocks() : /* compressBlocks() :
* @return : total compressed size of all blocks, * @return : total compressed size of all blocks,
* or 0 if error. * or 0 if error.
*/ */
static size_t compressBlocks(buffer_collection_t dstBlockBuffers, buffer_collection_t srcBlockBuffers, ZSTD_CDict* cdict, int cLevel) static size_t compressBlocks(size_t* cSizes, /* optional (can be NULL). If present, must contain at least nbBlocks fields */
buffer_collection_t dstBlockBuffers,
buffer_collection_t srcBlockBuffers,
ZSTD_CDict* cdict, int cLevel)
{ {
size_t const nbBlocks = srcBlockBuffers.nbBuffers; size_t const nbBlocks = srcBlockBuffers.nbBuffers;
assert(dstBlockBuffers.nbBuffers == srcBlockBuffers.nbBuffers); assert(dstBlockBuffers.nbBuffers == srcBlockBuffers.nbBuffers);
@ -243,14 +272,14 @@ static size_t compressBlocks(buffer_collection_t dstBlockBuffers, buffer_collect
dstBlockBuffers.buffers[blockNb], dstBlockBuffers.capacities[blockNb], dstBlockBuffers.buffers[blockNb], dstBlockBuffers.capacities[blockNb],
srcBlockBuffers.buffers[blockNb], srcBlockBuffers.capacities[blockNb], srcBlockBuffers.buffers[blockNb], srcBlockBuffers.capacities[blockNb],
cLevel); cLevel);
assert(!ZSTD_isError(cBlockSize));
} else { } else {
cBlockSize = ZSTD_compress_usingCDict(cctx, cBlockSize = ZSTD_compress_usingCDict(cctx,
dstBlockBuffers.buffers[blockNb], dstBlockBuffers.capacities[blockNb], dstBlockBuffers.buffers[blockNb], dstBlockBuffers.capacities[blockNb],
srcBlockBuffers.buffers[blockNb], srcBlockBuffers.capacities[blockNb], srcBlockBuffers.buffers[blockNb], srcBlockBuffers.capacities[blockNb],
cdict); cdict);
assert(!ZSTD_isError(cBlockSize));
} }
assert(!ZSTD_isError(cBlockSize));
if (cSizes) cSizes[blockNb] = cBlockSize;
totalCSize += cBlockSize; totalCSize += cBlockSize;
} }
return totalCSize; return totalCSize;
@ -259,6 +288,92 @@ static size_t compressBlocks(buffer_collection_t dstBlockBuffers, buffer_collect
/* --- Benchmark --- */ /* --- Benchmark --- */
typedef size_t (*BMK_benchFn_t)(const void* src, size_t srcSize, void* dst, size_t dstCapacity, void* customPayload);
typedef size_t (*BMK_initFn_t)(void* initPayload);
typedef struct {
ZSTD_DCtx* dctx;
size_t nbBlocks;
size_t blockNb;
ddict_collection_t dictionaries;
} decompressInstructions;
decompressInstructions createDecompressInstructions(ddict_collection_t dictionaries)
{
decompressInstructions di;
di.dctx = ZSTD_createDCtx();
assert(di.dctx != NULL);
di.nbBlocks = dictionaries.nbDDict;
di.blockNb = 0;
di.dictionaries = dictionaries;
return di;
}
void freeDecompressInstructions(decompressInstructions di)
{
ZSTD_freeDCtx(di.dctx);
}
/* benched function */
size_t decompress(const void* src, size_t srcSize, void* dst, size_t dstCapacity, void* payload)
{
decompressInstructions* const di = (decompressInstructions*) payload;
size_t const result = ZSTD_decompress_usingDDict(di->dctx,
dst, dstCapacity,
src, srcSize,
di->dictionaries.ddicts[di->blockNb]);
di->blockNb = di->blockNb + 1;
if (di->blockNb >= di->nbBlocks) di->blockNb = 0;
return result;
}
#define BENCH_TIME_DEFAULT_MS 6000
#define RUN_TIME_DEFAULT_MS 1000
static int benchMem(buffer_collection_t dstBlocks,
buffer_collection_t srcBlocks,
ddict_collection_t dictionaries)
{
assert(dstBlocks.nbBuffers == srcBlocks.nbBuffers);
assert(dstBlocks.nbBuffers == dictionaries.nbDDict);
double bestSpeed = 0.;
BMK_timedFnState_t* const benchState =
BMK_createTimedFnState(BENCH_TIME_DEFAULT_MS, RUN_TIME_DEFAULT_MS);
decompressInstructions di = createDecompressInstructions(dictionaries);
for (;;) {
BMK_runOutcome_t const outcome = BMK_benchTimedFn(benchState,
decompress, &di,
NULL, NULL,
dstBlocks.nbBuffers,
(const void* const *)srcBlocks.buffers, srcBlocks.capacities,
dstBlocks.buffers, dstBlocks.capacities,
NULL);
assert(BMK_isSuccessful_runOutcome(outcome));
BMK_runTime_t const result = BMK_extract_runTime(outcome);
U64 const dTime_ns = result.nanoSecPerRun;
double const dTime_sec = (double)dTime_ns / 1000000000;
size_t const srcSize = result.sumOfReturn;
double const dSpeed_MBps = (double)srcSize / dTime_sec / (1 MB);
if (dSpeed_MBps > bestSpeed) bestSpeed = dSpeed_MBps;
DISPLAY("Decompression Speed : %.1f MB/s \r", bestSpeed);
if (BMK_isCompleted_TimedFn(benchState)) break;
}
DISPLAY("\n");
freeDecompressInstructions(di);
BMK_freeTimedFnState(benchState);
return 0; /* success */
}
/* bench() : /* bench() :
* fileName : file to load for benchmarking purpose * fileName : file to load for benchmarking purpose
@ -272,8 +387,9 @@ int bench(const char* fileName, const char* dictionary)
DISPLAYLEVEL(3, "loading %s... \n", fileName); DISPLAYLEVEL(3, "loading %s... \n", fileName);
buffer_t const srcBuffer = createBuffer_fromFile(fileName); buffer_t const srcBuffer = createBuffer_fromFile(fileName);
assert(srcBuffer.ptr != NULL); assert(srcBuffer.ptr != NULL);
size_t const srcSize = srcBuffer.size;
DISPLAYLEVEL(3, "created src buffer of size %.1f MB \n", DISPLAYLEVEL(3, "created src buffer of size %.1f MB \n",
(double)(srcBuffer.size) / (1 MB)); (double)srcSize / (1 MB));
buffer_collection_t const srcBlockBuffers = splitBuffer(srcBuffer, BLOCKSIZE); buffer_collection_t const srcBlockBuffers = splitBuffer(srcBuffer, BLOCKSIZE);
assert(srcBlockBuffers.buffers != NULL); assert(srcBlockBuffers.buffers != NULL);
@ -302,17 +418,20 @@ int bench(const char* fileName, const char* dictionary)
ZSTD_CDict* const cdict = ZSTD_createCDict(dictBuffer.ptr, dictBuffer.size, COMP_LEVEL); ZSTD_CDict* const cdict = ZSTD_createCDict(dictBuffer.ptr, dictBuffer.size, COMP_LEVEL);
assert(cdict != NULL); assert(cdict != NULL);
size_t const cTotalSizeNoDict = compressBlocks(dstBlockBuffers, srcBlockBuffers, NULL, COMP_LEVEL); size_t const cTotalSizeNoDict = compressBlocks(NULL, dstBlockBuffers, srcBlockBuffers, NULL, COMP_LEVEL);
assert(cTotalSizeNoDict != 0); assert(cTotalSizeNoDict != 0);
DISPLAYLEVEL(3, "compressing at level %u without dictionary : Ratio=%.2f (%u bytes) \n", DISPLAYLEVEL(3, "compressing at level %u without dictionary : Ratio=%.2f (%u bytes) \n",
COMP_LEVEL, COMP_LEVEL,
(double)srcBuffer.size / cTotalSizeNoDict, (unsigned)cTotalSizeNoDict); (double)srcSize / cTotalSizeNoDict, (unsigned)cTotalSizeNoDict);
size_t const cTotalSize = compressBlocks(dstBlockBuffers, srcBlockBuffers, cdict, COMP_LEVEL); size_t* const cSizes = malloc(nbBlocks * sizeof(size_t));
assert(cSizes != NULL);
size_t const cTotalSize = compressBlocks(cSizes, dstBlockBuffers, srcBlockBuffers, cdict, COMP_LEVEL);
assert(cTotalSize != 0); assert(cTotalSize != 0);
DISPLAYLEVEL(3, "compressed using a %u bytes dictionary : Ratio=%.2f (%u bytes) \n", DISPLAYLEVEL(3, "compressed using a %u bytes dictionary : Ratio=%.2f (%u bytes) \n",
(unsigned)dictBuffer.size, (unsigned)dictBuffer.size,
(double)srcBuffer.size / cTotalSize, (unsigned)cTotalSize); (double)srcSize / cTotalSize, (unsigned)cTotalSize);
size_t const dictMem = ZSTD_estimateDDictSize(dictBuffer.size, ZSTD_dlm_byCopy); size_t const dictMem = ZSTD_estimateDDictSize(dictBuffer.size, ZSTD_dlm_byCopy);
size_t const allDictMem = dictMem * nbBlocks; size_t const allDictMem = dictMem * nbBlocks;
@ -322,13 +441,28 @@ int bench(const char* fileName, const char* dictionary)
ddict_collection_t const dictionaries = createDDictCollection(dictBuffer.ptr, dictBuffer.size, nbBlocks); ddict_collection_t const dictionaries = createDDictCollection(dictBuffer.ptr, dictBuffer.size, nbBlocks);
assert(dictionaries.ddicts != NULL); assert(dictionaries.ddicts != NULL);
shuffleDictionaries(dictionaries);
// for (size_t u = 0; u < dictionaries.nbDDict; u++) DISPLAY("dict address : %p \n", dictionaries.ddicts[u]); /* check dictionary addresses */
void* const resultPtr = malloc(srcSize);
assert(resultPtr != NULL);
buffer_t resultBuffer;
resultBuffer.ptr = resultPtr;
resultBuffer.capacity = srcSize;
resultBuffer.size = srcSize;
//result = benchMem(srcBlockBuffers, dstBlockBuffers, dictionaries);; buffer_collection_t const resultBlockBuffers = splitBuffer(resultBuffer, BLOCKSIZE);
assert(resultBlockBuffers.buffers != NULL);
shrinkSizes(dstBlockBuffers, cSizes);
result = benchMem(resultBlockBuffers, dstBlockBuffers, dictionaries);
/* free all heap objects in reverse order */
freeCollection(resultBlockBuffers);
free(resultPtr);
freeDDictCollection(dictionaries); freeDDictCollection(dictionaries);
free(cSizes);
ZSTD_freeCDict(cdict); ZSTD_freeCDict(cdict);
freeBuffer(dictBuffer); freeBuffer(dictBuffer);
freeCollection(dstBlockBuffers); freeCollection(dstBlockBuffers);
@ -342,7 +476,7 @@ int bench(const char* fileName, const char* dictionary)
/*--- Command Line ---*/ /* --- Command Line --- */
int bad_usage(const char* exeName) int bad_usage(const char* exeName)
{ {

View File

@ -253,7 +253,7 @@ static size_t local_defaultCompress(
/* `addArgs` is the context */ /* `addArgs` is the context */
static size_t local_defaultDecompress( static size_t local_defaultDecompress(
const void* srcBuffer, size_t srcSize, const void* srcBuffer, size_t srcSize,
void* dstBuffer, size_t dstSize, void* dstBuffer, size_t dstCapacity,
void* addArgs) void* addArgs)
{ {
size_t moreToFlush = 1; size_t moreToFlush = 1;
@ -261,7 +261,7 @@ static size_t local_defaultDecompress(
ZSTD_inBuffer in; ZSTD_inBuffer in;
ZSTD_outBuffer out; ZSTD_outBuffer out;
in.src = srcBuffer; in.size = srcSize; in.pos = 0; in.src = srcBuffer; in.size = srcSize; in.pos = 0;
out.dst = dstBuffer; out.size = dstSize; out.pos = 0; out.dst = dstBuffer; out.size = dstCapacity; out.pos = 0;
while (moreToFlush) { while (moreToFlush) {
if(out.pos == out.size) { if(out.pos == out.size) {
return (size_t)-ZSTD_error_dstSize_tooSmall; return (size_t)-ZSTD_error_dstSize_tooSmall;