1 /* 2 * Copyright (c) 2016-present, Przemyslaw Skibinski, Yann Collet, Facebook, Inc. 3 * All rights reserved. 4 * 5 * This source code is licensed under both the BSD-style license (found in the 6 * LICENSE file in the root directory of this source tree) and the GPLv2 (found 7 * in the COPYING file in the root directory of this source tree). 8 */ 9 10 11 /* ************************************* 12 * Includes 13 ***************************************/ 14 #include "util.h" /* Compiler options, UTIL_GetFileSize, UTIL_sleep */ 15 #include <stdlib.h> /* malloc, free */ 16 #include <string.h> /* memset */ 17 #include <stdio.h> /* fprintf, fopen, ftello64 */ 18 #include <time.h> /* clock_t, clock, CLOCKS_PER_SEC */ 19 #include <ctype.h> /* toupper */ 20 21 #include "mem.h" 22 #define ZSTD_STATIC_LINKING_ONLY 23 #include "zstd.h" 24 #include "datagen.h" /* RDG_genBuffer */ 25 #include "xxhash.h" 26 27 #include "zstd_zlibwrapper.h" 28 29 30 31 /*-************************************ 32 * Tuning parameters 33 **************************************/ 34 #ifndef ZSTDCLI_CLEVEL_DEFAULT 35 # define ZSTDCLI_CLEVEL_DEFAULT 3 36 #endif 37 38 39 /*-************************************ 40 * Constants 41 **************************************/ 42 #define COMPRESSOR_NAME "Zstandard wrapper for zlib command line interface" 43 #ifndef ZSTD_VERSION 44 # define ZSTD_VERSION "v" ZSTD_VERSION_STRING 45 #endif 46 #define AUTHOR "Yann Collet" 47 #define WELCOME_MESSAGE "*** %s %i-bits %s, by %s ***\n", COMPRESSOR_NAME, (int)(sizeof(size_t)*8), ZSTD_VERSION, AUTHOR 48 49 #ifndef ZSTD_GIT_COMMIT 50 # define ZSTD_GIT_COMMIT_STRING "" 51 #else 52 # define ZSTD_GIT_COMMIT_STRING ZSTD_EXPAND_AND_QUOTE(ZSTD_GIT_COMMIT) 53 #endif 54 55 #define NBLOOPS 3 56 #define TIMELOOP_MICROSEC 1*1000000ULL /* 1 second */ 57 #define ACTIVEPERIOD_MICROSEC 70*1000000ULL /* 70 seconds */ 58 #define COOLPERIOD_SEC 10 59 60 #define KB *(1 <<10) 61 #define MB *(1 <<20) 62 #define GB *(1U<<30) 63 64 static const size_t maxMemory = (sizeof(size_t)==4) ? (2 GB - 64 MB) : (size_t)(1ULL << ((sizeof(size_t)*8)-31)); 65 66 static U32 g_compressibilityDefault = 50; 67 68 69 /* ************************************* 70 * console display 71 ***************************************/ 72 #define DEFAULT_DISPLAY_LEVEL 2 73 #define DISPLAY(...) fprintf(displayOut, __VA_ARGS__) 74 #define DISPLAYLEVEL(l, ...) if (g_displayLevel>=l) { DISPLAY(__VA_ARGS__); } 75 static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; /* 0 : no display; 1: errors; 2 : + result + interaction + warnings; 3 : + progression; 4 : + information */ 76 static FILE* displayOut; 77 78 #define DISPLAYUPDATE(l, ...) if (g_displayLevel>=l) { \ 79 if ((clock() - g_time > refreshRate) || (g_displayLevel>=4)) \ 80 { g_time = clock(); DISPLAY(__VA_ARGS__); \ 81 if (g_displayLevel>=4) fflush(displayOut); } } 82 static const clock_t refreshRate = CLOCKS_PER_SEC * 15 / 100; 83 static clock_t g_time = 0; 84 85 86 /* ************************************* 87 * Exceptions 88 ***************************************/ 89 #ifndef DEBUG 90 # define DEBUG 0 91 #endif 92 #define DEBUGOUTPUT(...) { if (DEBUG) DISPLAY(__VA_ARGS__); } 93 #define EXM_THROW(error, ...) \ 94 { \ 95 DEBUGOUTPUT("Error defined at %s, line %i : \n", __FILE__, __LINE__); \ 96 DISPLAYLEVEL(1, "Error %i : ", error); \ 97 DISPLAYLEVEL(1, __VA_ARGS__); \ 98 DISPLAYLEVEL(1, "\n"); \ 99 exit(error); \ 100 } 101 102 103 /* ************************************* 104 * Benchmark Parameters 105 ***************************************/ 106 static U32 g_nbIterations = NBLOOPS; 107 static size_t g_blockSize = 0; 108 int g_additionalParam = 0; 109 110 void BMK_setNotificationLevel(unsigned level) { g_displayLevel=level; } 111 112 void BMK_setAdditionalParam(int additionalParam) { g_additionalParam=additionalParam; } 113 114 void BMK_SetNbIterations(unsigned nbLoops) 115 { 116 g_nbIterations = nbLoops; 117 DISPLAYLEVEL(3, "- test >= %u seconds per compression / decompression -\n", g_nbIterations); 118 } 119 120 void BMK_SetBlockSize(size_t blockSize) 121 { 122 g_blockSize = blockSize; 123 DISPLAYLEVEL(2, "using blocks of size %u KB \n", (U32)(blockSize>>10)); 124 } 125 126 127 /* ******************************************************** 128 * Bench functions 129 **********************************************************/ 130 #undef MIN 131 #undef MAX 132 #define MIN(a,b) ((a)<(b) ? (a) : (b)) 133 #define MAX(a,b) ((a)>(b) ? (a) : (b)) 134 135 typedef struct 136 { 137 z_const char* srcPtr; 138 size_t srcSize; 139 char* cPtr; 140 size_t cRoom; 141 size_t cSize; 142 char* resPtr; 143 size_t resSize; 144 } blockParam_t; 145 146 typedef enum { BMK_ZSTD, BMK_ZSTD_STREAM, BMK_ZLIB, BMK_ZWRAP_ZLIB, BMK_ZWRAP_ZSTD, BMK_ZLIB_REUSE, BMK_ZWRAP_ZLIB_REUSE, BMK_ZWRAP_ZSTD_REUSE } BMK_compressor; 147 148 149 static int BMK_benchMem(z_const void* srcBuffer, size_t srcSize, 150 const char* displayName, int cLevel, 151 const size_t* fileSizes, U32 nbFiles, 152 const void* dictBuffer, size_t dictBufferSize, BMK_compressor compressor) 153 { 154 size_t const blockSize = (g_blockSize>=32 ? g_blockSize : srcSize) + (!srcSize) /* avoid div by 0 */ ; 155 size_t const avgSize = MIN(g_blockSize, (srcSize / nbFiles)); 156 U32 const maxNbBlocks = (U32) ((srcSize + (blockSize-1)) / blockSize) + nbFiles; 157 blockParam_t* const blockTable = (blockParam_t*) malloc(maxNbBlocks * sizeof(blockParam_t)); 158 size_t const maxCompressedSize = ZSTD_compressBound(srcSize) + (maxNbBlocks * 1024); /* add some room for safety */ 159 void* const compressedBuffer = malloc(maxCompressedSize); 160 void* const resultBuffer = malloc(srcSize); 161 ZSTD_CCtx* const ctx = ZSTD_createCCtx(); 162 ZSTD_DCtx* const dctx = ZSTD_createDCtx(); 163 U32 nbBlocks; 164 165 /* checks */ 166 if (!compressedBuffer || !resultBuffer || !blockTable || !ctx || !dctx) 167 EXM_THROW(31, "allocation error : not enough memory"); 168 169 /* init */ 170 if (strlen(displayName)>17) displayName += strlen(displayName)-17; /* can only display 17 characters */ 171 172 /* Init blockTable data */ 173 { z_const char* srcPtr = (z_const char*)srcBuffer; 174 char* cPtr = (char*)compressedBuffer; 175 char* resPtr = (char*)resultBuffer; 176 U32 fileNb; 177 for (nbBlocks=0, fileNb=0; fileNb<nbFiles; fileNb++) { 178 size_t remaining = fileSizes[fileNb]; 179 U32 const nbBlocksforThisFile = (U32)((remaining + (blockSize-1)) / blockSize); 180 U32 const blockEnd = nbBlocks + nbBlocksforThisFile; 181 for ( ; nbBlocks<blockEnd; nbBlocks++) { 182 size_t const thisBlockSize = MIN(remaining, blockSize); 183 blockTable[nbBlocks].srcPtr = srcPtr; 184 blockTable[nbBlocks].cPtr = cPtr; 185 blockTable[nbBlocks].resPtr = resPtr; 186 blockTable[nbBlocks].srcSize = thisBlockSize; 187 blockTable[nbBlocks].cRoom = ZSTD_compressBound(thisBlockSize); 188 srcPtr += thisBlockSize; 189 cPtr += blockTable[nbBlocks].cRoom; 190 resPtr += thisBlockSize; 191 remaining -= thisBlockSize; 192 } } } 193 194 /* warmimg up memory */ 195 RDG_genBuffer(compressedBuffer, maxCompressedSize, 0.10, 0.50, 1); 196 197 /* Bench */ 198 { U64 fastestC = (U64)(-1LL), fastestD = (U64)(-1LL); 199 U64 const crcOrig = XXH64(srcBuffer, srcSize, 0); 200 UTIL_time_t coolTime; 201 U64 const maxTime = (g_nbIterations * TIMELOOP_MICROSEC) + 100; 202 U64 totalCTime=0, totalDTime=0; 203 U32 cCompleted=0, dCompleted=0; 204 # define NB_MARKS 4 205 const char* const marks[NB_MARKS] = { " |", " /", " =", "\\" }; 206 U32 markNb = 0; 207 size_t cSize = 0; 208 double ratio = 0.; 209 210 coolTime = UTIL_getTime(); 211 DISPLAYLEVEL(2, "\r%79s\r", ""); 212 while (!cCompleted | !dCompleted) { 213 UTIL_time_t clockStart; 214 U64 clockLoop = g_nbIterations ? TIMELOOP_MICROSEC : 1; 215 216 /* overheat protection */ 217 if (UTIL_clockSpanMicro(coolTime) > ACTIVEPERIOD_MICROSEC) { 218 DISPLAYLEVEL(2, "\rcooling down ... \r"); 219 UTIL_sleep(COOLPERIOD_SEC); 220 coolTime = UTIL_getTime(); 221 } 222 223 /* Compression */ 224 DISPLAYLEVEL(2, "%2s-%-17.17s :%10u ->\r", marks[markNb], displayName, (U32)srcSize); 225 if (!cCompleted) memset(compressedBuffer, 0xE5, maxCompressedSize); /* warm up and erase result buffer */ 226 227 UTIL_sleepMilli(1); /* give processor time to other processes */ 228 UTIL_waitForNextTick(); 229 clockStart = UTIL_getTime(); 230 231 if (!cCompleted) { /* still some time to do compression tests */ 232 U32 nbLoops = 0; 233 if (compressor == BMK_ZSTD) { 234 ZSTD_parameters const zparams = ZSTD_getParams(cLevel, avgSize, dictBufferSize); 235 ZSTD_customMem const cmem = { NULL, NULL, NULL }; 236 ZSTD_CDict* const cdict = ZSTD_createCDict_advanced(dictBuffer, dictBufferSize, ZSTD_dlm_byRef, ZSTD_dct_auto, zparams.cParams, cmem); 237 if (cdict==NULL) EXM_THROW(1, "ZSTD_createCDict_advanced() allocation failure"); 238 239 do { 240 U32 blockNb; 241 size_t rSize; 242 for (blockNb=0; blockNb<nbBlocks; blockNb++) { 243 if (dictBufferSize) { 244 rSize = ZSTD_compress_usingCDict(ctx, 245 blockTable[blockNb].cPtr, blockTable[blockNb].cRoom, 246 blockTable[blockNb].srcPtr,blockTable[blockNb].srcSize, 247 cdict); 248 } else { 249 rSize = ZSTD_compressCCtx (ctx, 250 blockTable[blockNb].cPtr, blockTable[blockNb].cRoom, 251 blockTable[blockNb].srcPtr,blockTable[blockNb].srcSize, cLevel); 252 } 253 if (ZSTD_isError(rSize)) EXM_THROW(1, "ZSTD_compress_usingCDict() failed : %s", ZSTD_getErrorName(rSize)); 254 blockTable[blockNb].cSize = rSize; 255 } 256 nbLoops++; 257 } while (UTIL_clockSpanMicro(clockStart) < clockLoop); 258 ZSTD_freeCDict(cdict); 259 } else if (compressor == BMK_ZSTD_STREAM) { 260 ZSTD_parameters const zparams = ZSTD_getParams(cLevel, avgSize, dictBufferSize); 261 ZSTD_inBuffer inBuffer; 262 ZSTD_outBuffer outBuffer; 263 ZSTD_CStream* zbc = ZSTD_createCStream(); 264 size_t rSize; 265 if (zbc == NULL) EXM_THROW(1, "ZSTD_createCStream() allocation failure"); 266 rSize = ZSTD_initCStream_advanced(zbc, dictBuffer, dictBufferSize, zparams, avgSize); 267 if (ZSTD_isError(rSize)) EXM_THROW(1, "ZSTD_initCStream_advanced() failed : %s", ZSTD_getErrorName(rSize)); 268 do { 269 U32 blockNb; 270 for (blockNb=0; blockNb<nbBlocks; blockNb++) { 271 rSize = ZSTD_resetCStream(zbc, blockTable[blockNb].srcSize); 272 if (ZSTD_isError(rSize)) EXM_THROW(1, "ZSTD_resetCStream() failed : %s", ZSTD_getErrorName(rSize)); 273 inBuffer.src = blockTable[blockNb].srcPtr; 274 inBuffer.size = blockTable[blockNb].srcSize; 275 inBuffer.pos = 0; 276 outBuffer.dst = blockTable[blockNb].cPtr; 277 outBuffer.size = blockTable[blockNb].cRoom; 278 outBuffer.pos = 0; 279 rSize = ZSTD_compressStream(zbc, &outBuffer, &inBuffer); 280 if (ZSTD_isError(rSize)) EXM_THROW(1, "ZSTD_compressStream() failed : %s", ZSTD_getErrorName(rSize)); 281 rSize = ZSTD_endStream(zbc, &outBuffer); 282 if (ZSTD_isError(rSize)) EXM_THROW(1, "ZSTD_endStream() failed : %s", ZSTD_getErrorName(rSize)); 283 blockTable[blockNb].cSize = outBuffer.pos; 284 } 285 nbLoops++; 286 } while (UTIL_clockSpanMicro(clockStart) < clockLoop); 287 ZSTD_freeCStream(zbc); 288 } else if (compressor == BMK_ZWRAP_ZLIB_REUSE || compressor == BMK_ZWRAP_ZSTD_REUSE || compressor == BMK_ZLIB_REUSE) { 289 z_stream def; 290 int ret; 291 int useSetDict = (dictBuffer != NULL); 292 if (compressor == BMK_ZLIB_REUSE || compressor == BMK_ZWRAP_ZLIB_REUSE) ZWRAP_useZSTDcompression(0); 293 else ZWRAP_useZSTDcompression(1); 294 def.zalloc = Z_NULL; 295 def.zfree = Z_NULL; 296 def.opaque = Z_NULL; 297 ret = deflateInit(&def, cLevel); 298 if (ret != Z_OK) EXM_THROW(1, "deflateInit failure"); 299 /* if (ZWRAP_isUsingZSTDcompression()) { 300 ret = ZWRAP_setPledgedSrcSize(&def, avgSize); 301 if (ret != Z_OK) EXM_THROW(1, "ZWRAP_setPledgedSrcSize failure"); 302 } */ 303 do { 304 U32 blockNb; 305 for (blockNb=0; blockNb<nbBlocks; blockNb++) { 306 if (ZWRAP_isUsingZSTDcompression()) 307 ret = ZWRAP_deflateReset_keepDict(&def); /* reuse dictionary to make compression faster */ 308 else 309 ret = deflateReset(&def); 310 if (ret != Z_OK) EXM_THROW(1, "deflateReset failure"); 311 if (useSetDict) { 312 ret = deflateSetDictionary(&def, dictBuffer, dictBufferSize); 313 if (ret != Z_OK) EXM_THROW(1, "deflateSetDictionary failure"); 314 if (ZWRAP_isUsingZSTDcompression()) useSetDict = 0; /* zstd doesn't require deflateSetDictionary after ZWRAP_deflateReset_keepDict */ 315 } 316 def.next_in = (z_const void*) blockTable[blockNb].srcPtr; 317 def.avail_in = (uInt)blockTable[blockNb].srcSize; 318 def.total_in = 0; 319 def.next_out = (void*) blockTable[blockNb].cPtr; 320 def.avail_out = (uInt)blockTable[blockNb].cRoom; 321 def.total_out = 0; 322 ret = deflate(&def, Z_FINISH); 323 if (ret != Z_STREAM_END) EXM_THROW(1, "deflate failure ret=%d srcSize=%d" , ret, (int)blockTable[blockNb].srcSize); 324 blockTable[blockNb].cSize = def.total_out; 325 } 326 nbLoops++; 327 } while (UTIL_clockSpanMicro(clockStart) < clockLoop); 328 ret = deflateEnd(&def); 329 if (ret != Z_OK) EXM_THROW(1, "deflateEnd failure"); 330 } else { 331 z_stream def; 332 if (compressor == BMK_ZLIB || compressor == BMK_ZWRAP_ZLIB) ZWRAP_useZSTDcompression(0); 333 else ZWRAP_useZSTDcompression(1); 334 do { 335 U32 blockNb; 336 for (blockNb=0; blockNb<nbBlocks; blockNb++) { 337 int ret; 338 def.zalloc = Z_NULL; 339 def.zfree = Z_NULL; 340 def.opaque = Z_NULL; 341 ret = deflateInit(&def, cLevel); 342 if (ret != Z_OK) EXM_THROW(1, "deflateInit failure"); 343 if (dictBuffer) { 344 ret = deflateSetDictionary(&def, dictBuffer, dictBufferSize); 345 if (ret != Z_OK) EXM_THROW(1, "deflateSetDictionary failure"); 346 } 347 def.next_in = (z_const void*) blockTable[blockNb].srcPtr; 348 def.avail_in = (uInt)blockTable[blockNb].srcSize; 349 def.total_in = 0; 350 def.next_out = (void*) blockTable[blockNb].cPtr; 351 def.avail_out = (uInt)blockTable[blockNb].cRoom; 352 def.total_out = 0; 353 ret = deflate(&def, Z_FINISH); 354 if (ret != Z_STREAM_END) EXM_THROW(1, "deflate failure"); 355 ret = deflateEnd(&def); 356 if (ret != Z_OK) EXM_THROW(1, "deflateEnd failure"); 357 blockTable[blockNb].cSize = def.total_out; 358 } 359 nbLoops++; 360 } while (UTIL_clockSpanMicro(clockStart) < clockLoop); 361 } 362 { U64 const clockSpan = UTIL_clockSpanMicro(clockStart); 363 if (clockSpan < fastestC*nbLoops) fastestC = clockSpan / nbLoops; 364 totalCTime += clockSpan; 365 cCompleted = totalCTime>maxTime; 366 } } 367 368 cSize = 0; 369 { U32 blockNb; for (blockNb=0; blockNb<nbBlocks; blockNb++) cSize += blockTable[blockNb].cSize; } 370 ratio = (double)srcSize / (double)cSize; 371 markNb = (markNb+1) % NB_MARKS; 372 DISPLAYLEVEL(2, "%2s-%-17.17s :%10u ->%10u (%5.3f),%6.1f MB/s\r", 373 marks[markNb], displayName, (U32)srcSize, (U32)cSize, ratio, 374 (double)srcSize / fastestC ); 375 376 (void)fastestD; (void)crcOrig; /* unused when decompression disabled */ 377 #if 1 378 /* Decompression */ 379 if (!dCompleted) memset(resultBuffer, 0xD6, srcSize); /* warm result buffer */ 380 381 UTIL_sleepMilli(1); /* give processor time to other processes */ 382 UTIL_waitForNextTick(); 383 clockStart = UTIL_getTime(); 384 385 if (!dCompleted) { 386 U32 nbLoops = 0; 387 if (compressor == BMK_ZSTD) { 388 ZSTD_DDict* ddict = ZSTD_createDDict(dictBuffer, dictBufferSize); 389 if (!ddict) EXM_THROW(2, "ZSTD_createDDict() allocation failure"); 390 do { 391 U32 blockNb; 392 for (blockNb=0; blockNb<nbBlocks; blockNb++) { 393 size_t const regenSize = ZSTD_decompress_usingDDict(dctx, 394 blockTable[blockNb].resPtr, blockTable[blockNb].srcSize, 395 blockTable[blockNb].cPtr, blockTable[blockNb].cSize, 396 ddict); 397 if (ZSTD_isError(regenSize)) { 398 DISPLAY("ZSTD_decompress_usingDDict() failed on block %u : %s \n", 399 blockNb, ZSTD_getErrorName(regenSize)); 400 clockLoop = 0; /* force immediate test end */ 401 break; 402 } 403 blockTable[blockNb].resSize = regenSize; 404 } 405 nbLoops++; 406 } while (UTIL_clockSpanMicro(clockStart) < clockLoop); 407 ZSTD_freeDDict(ddict); 408 } else if (compressor == BMK_ZSTD_STREAM) { 409 ZSTD_inBuffer inBuffer; 410 ZSTD_outBuffer outBuffer; 411 ZSTD_DStream* zbd = ZSTD_createDStream(); 412 size_t rSize; 413 if (zbd == NULL) EXM_THROW(1, "ZSTD_createDStream() allocation failure"); 414 rSize = ZSTD_initDStream_usingDict(zbd, dictBuffer, dictBufferSize); 415 if (ZSTD_isError(rSize)) EXM_THROW(1, "ZSTD_initDStream() failed : %s", ZSTD_getErrorName(rSize)); 416 do { 417 U32 blockNb; 418 for (blockNb=0; blockNb<nbBlocks; blockNb++) { 419 rSize = ZSTD_resetDStream(zbd); 420 if (ZSTD_isError(rSize)) EXM_THROW(1, "ZSTD_resetDStream() failed : %s", ZSTD_getErrorName(rSize)); 421 inBuffer.src = blockTable[blockNb].cPtr; 422 inBuffer.size = blockTable[blockNb].cSize; 423 inBuffer.pos = 0; 424 outBuffer.dst = blockTable[blockNb].resPtr; 425 outBuffer.size = blockTable[blockNb].srcSize; 426 outBuffer.pos = 0; 427 rSize = ZSTD_decompressStream(zbd, &outBuffer, &inBuffer); 428 if (ZSTD_isError(rSize)) EXM_THROW(1, "ZSTD_decompressStream() failed : %s", ZSTD_getErrorName(rSize)); 429 blockTable[blockNb].resSize = outBuffer.pos; 430 } 431 nbLoops++; 432 } while (UTIL_clockSpanMicro(clockStart) < clockLoop); 433 ZSTD_freeDStream(zbd); 434 } else if (compressor == BMK_ZWRAP_ZLIB_REUSE || compressor == BMK_ZWRAP_ZSTD_REUSE || compressor == BMK_ZLIB_REUSE) { 435 z_stream inf; 436 int ret; 437 if (compressor == BMK_ZLIB_REUSE) ZWRAP_setDecompressionType(ZWRAP_FORCE_ZLIB); 438 else ZWRAP_setDecompressionType(ZWRAP_AUTO); 439 inf.zalloc = Z_NULL; 440 inf.zfree = Z_NULL; 441 inf.opaque = Z_NULL; 442 ret = inflateInit(&inf); 443 if (ret != Z_OK) EXM_THROW(1, "inflateInit failure"); 444 do { 445 U32 blockNb; 446 for (blockNb=0; blockNb<nbBlocks; blockNb++) { 447 if (ZWRAP_isUsingZSTDdecompression(&inf)) 448 ret = ZWRAP_inflateReset_keepDict(&inf); /* reuse dictionary to make decompression faster; inflate will return Z_NEED_DICT only for the first time */ 449 else 450 ret = inflateReset(&inf); 451 if (ret != Z_OK) EXM_THROW(1, "inflateReset failure"); 452 inf.next_in = (z_const void*) blockTable[blockNb].cPtr; 453 inf.avail_in = (uInt)blockTable[blockNb].cSize; 454 inf.total_in = 0; 455 inf.next_out = (void*) blockTable[blockNb].resPtr; 456 inf.avail_out = (uInt)blockTable[blockNb].srcSize; 457 inf.total_out = 0; 458 ret = inflate(&inf, Z_FINISH); 459 if (ret == Z_NEED_DICT) { 460 ret = inflateSetDictionary(&inf, dictBuffer, dictBufferSize); 461 if (ret != Z_OK) EXM_THROW(1, "inflateSetDictionary failure"); 462 ret = inflate(&inf, Z_FINISH); 463 } 464 if (ret != Z_STREAM_END) EXM_THROW(1, "inflate failure"); 465 blockTable[blockNb].resSize = inf.total_out; 466 } 467 nbLoops++; 468 } while (UTIL_clockSpanMicro(clockStart) < clockLoop); 469 ret = inflateEnd(&inf); 470 if (ret != Z_OK) EXM_THROW(1, "inflateEnd failure"); 471 } else { 472 z_stream inf; 473 if (compressor == BMK_ZLIB) ZWRAP_setDecompressionType(ZWRAP_FORCE_ZLIB); 474 else ZWRAP_setDecompressionType(ZWRAP_AUTO); 475 do { 476 U32 blockNb; 477 for (blockNb=0; blockNb<nbBlocks; blockNb++) { 478 int ret; 479 inf.zalloc = Z_NULL; 480 inf.zfree = Z_NULL; 481 inf.opaque = Z_NULL; 482 ret = inflateInit(&inf); 483 if (ret != Z_OK) EXM_THROW(1, "inflateInit failure"); 484 inf.next_in = (z_const void*) blockTable[blockNb].cPtr; 485 inf.avail_in = (uInt)blockTable[blockNb].cSize; 486 inf.total_in = 0; 487 inf.next_out = (void*) blockTable[blockNb].resPtr; 488 inf.avail_out = (uInt)blockTable[blockNb].srcSize; 489 inf.total_out = 0; 490 ret = inflate(&inf, Z_FINISH); 491 if (ret == Z_NEED_DICT) { 492 ret = inflateSetDictionary(&inf, dictBuffer, dictBufferSize); 493 if (ret != Z_OK) EXM_THROW(1, "inflateSetDictionary failure"); 494 ret = inflate(&inf, Z_FINISH); 495 } 496 if (ret != Z_STREAM_END) EXM_THROW(1, "inflate failure"); 497 ret = inflateEnd(&inf); 498 if (ret != Z_OK) EXM_THROW(1, "inflateEnd failure"); 499 blockTable[blockNb].resSize = inf.total_out; 500 } 501 nbLoops++; 502 } while (UTIL_clockSpanMicro(clockStart) < clockLoop); 503 } 504 { U64 const clockSpan = UTIL_clockSpanMicro(clockStart); 505 if (clockSpan < fastestD*nbLoops) fastestD = clockSpan / nbLoops; 506 totalDTime += clockSpan; 507 dCompleted = totalDTime>maxTime; 508 } } 509 510 markNb = (markNb+1) % NB_MARKS; 511 DISPLAYLEVEL(2, "%2s-%-17.17s :%10u ->%10u (%5.3f),%6.1f MB/s ,%6.1f MB/s\r", 512 marks[markNb], displayName, (U32)srcSize, (U32)cSize, ratio, 513 (double)srcSize / fastestC, 514 (double)srcSize / fastestD ); 515 516 /* CRC Checking */ 517 { U64 const crcCheck = XXH64(resultBuffer, srcSize, 0); 518 if (crcOrig!=crcCheck) { 519 size_t u; 520 DISPLAY("!!! WARNING !!! %14s : Invalid Checksum : %x != %x \n", displayName, (unsigned)crcOrig, (unsigned)crcCheck); 521 for (u=0; u<srcSize; u++) { 522 if (((const BYTE*)srcBuffer)[u] != ((const BYTE*)resultBuffer)[u]) { 523 U32 segNb, bNb, pos; 524 size_t bacc = 0; 525 DISPLAY("Decoding error at pos %u ", (U32)u); 526 for (segNb = 0; segNb < nbBlocks; segNb++) { 527 if (bacc + blockTable[segNb].srcSize > u) break; 528 bacc += blockTable[segNb].srcSize; 529 } 530 pos = (U32)(u - bacc); 531 bNb = pos / (128 KB); 532 DISPLAY("(block %u, sub %u, pos %u) \n", segNb, bNb, pos); 533 break; 534 } 535 if (u==srcSize-1) { /* should never happen */ 536 DISPLAY("no difference detected\n"); 537 } } 538 break; 539 } } /* CRC Checking */ 540 #endif 541 } /* for (testNb = 1; testNb <= (g_nbIterations + !g_nbIterations); testNb++) */ 542 543 if (g_displayLevel == 1) { 544 double cSpeed = (double)srcSize / fastestC; 545 double dSpeed = (double)srcSize / fastestD; 546 if (g_additionalParam) 547 DISPLAY("-%-3i%11i (%5.3f) %6.2f MB/s %6.1f MB/s %s (param=%d)\n", cLevel, (int)cSize, ratio, cSpeed, dSpeed, displayName, g_additionalParam); 548 else 549 DISPLAY("-%-3i%11i (%5.3f) %6.2f MB/s %6.1f MB/s %s\n", cLevel, (int)cSize, ratio, cSpeed, dSpeed, displayName); 550 } 551 DISPLAYLEVEL(2, "%2i#\n", cLevel); 552 } /* Bench */ 553 554 /* clean up */ 555 free(blockTable); 556 free(compressedBuffer); 557 free(resultBuffer); 558 ZSTD_freeCCtx(ctx); 559 ZSTD_freeDCtx(dctx); 560 return 0; 561 } 562 563 564 static size_t BMK_findMaxMem(U64 requiredMem) 565 { 566 size_t const step = 64 MB; 567 BYTE* testmem = NULL; 568 569 requiredMem = (((requiredMem >> 26) + 1) << 26); 570 requiredMem += step; 571 if (requiredMem > maxMemory) requiredMem = maxMemory; 572 573 do { 574 testmem = (BYTE*)malloc((size_t)requiredMem); 575 requiredMem -= step; 576 } while (!testmem && requiredMem); /* do not allocate zero bytes */ 577 578 free(testmem); 579 return (size_t)(requiredMem+1); /* avoid zero */ 580 } 581 582 static void BMK_benchCLevel(void* srcBuffer, size_t benchedSize, 583 const char* displayName, int cLevel, int cLevelLast, 584 const size_t* fileSizes, unsigned nbFiles, 585 const void* dictBuffer, size_t dictBufferSize) 586 { 587 int l; 588 589 const char* pch = strrchr(displayName, '\\'); /* Windows */ 590 if (!pch) pch = strrchr(displayName, '/'); /* Linux */ 591 if (pch) displayName = pch+1; 592 593 SET_REALTIME_PRIORITY; 594 595 if (g_displayLevel == 1 && !g_additionalParam) 596 DISPLAY("bench %s %s: input %u bytes, %u seconds, %u KB blocks\n", ZSTD_VERSION_STRING, ZSTD_GIT_COMMIT_STRING, (U32)benchedSize, g_nbIterations, (U32)(g_blockSize>>10)); 597 598 if (cLevelLast < cLevel) cLevelLast = cLevel; 599 600 DISPLAY("benchmarking zstd %s (using ZSTD_CStream)\n", ZSTD_VERSION_STRING); 601 for (l=cLevel; l <= cLevelLast; l++) { 602 BMK_benchMem(srcBuffer, benchedSize, 603 displayName, l, 604 fileSizes, nbFiles, 605 dictBuffer, dictBufferSize, BMK_ZSTD_STREAM); 606 } 607 608 DISPLAY("benchmarking zstd %s (using ZSTD_CCtx)\n", ZSTD_VERSION_STRING); 609 for (l=cLevel; l <= cLevelLast; l++) { 610 BMK_benchMem(srcBuffer, benchedSize, 611 displayName, l, 612 fileSizes, nbFiles, 613 dictBuffer, dictBufferSize, BMK_ZSTD); 614 } 615 616 DISPLAY("benchmarking zstd %s (using zlibWrapper)\n", ZSTD_VERSION_STRING); 617 for (l=cLevel; l <= cLevelLast; l++) { 618 BMK_benchMem(srcBuffer, benchedSize, 619 displayName, l, 620 fileSizes, nbFiles, 621 dictBuffer, dictBufferSize, BMK_ZWRAP_ZSTD_REUSE); 622 } 623 624 DISPLAY("benchmarking zstd %s (zlibWrapper not reusing a context)\n", ZSTD_VERSION_STRING); 625 for (l=cLevel; l <= cLevelLast; l++) { 626 BMK_benchMem(srcBuffer, benchedSize, 627 displayName, l, 628 fileSizes, nbFiles, 629 dictBuffer, dictBufferSize, BMK_ZWRAP_ZSTD); 630 } 631 632 633 if (cLevelLast > Z_BEST_COMPRESSION) cLevelLast = Z_BEST_COMPRESSION; 634 635 DISPLAY("\n"); 636 DISPLAY("benchmarking zlib %s\n", ZLIB_VERSION); 637 for (l=cLevel; l <= cLevelLast; l++) { 638 BMK_benchMem(srcBuffer, benchedSize, 639 displayName, l, 640 fileSizes, nbFiles, 641 dictBuffer, dictBufferSize, BMK_ZLIB_REUSE); 642 } 643 644 DISPLAY("benchmarking zlib %s (zlib not reusing a context)\n", ZLIB_VERSION); 645 for (l=cLevel; l <= cLevelLast; l++) { 646 BMK_benchMem(srcBuffer, benchedSize, 647 displayName, l, 648 fileSizes, nbFiles, 649 dictBuffer, dictBufferSize, BMK_ZLIB); 650 } 651 652 DISPLAY("benchmarking zlib %s (using zlibWrapper)\n", ZLIB_VERSION); 653 for (l=cLevel; l <= cLevelLast; l++) { 654 BMK_benchMem(srcBuffer, benchedSize, 655 displayName, l, 656 fileSizes, nbFiles, 657 dictBuffer, dictBufferSize, BMK_ZWRAP_ZLIB_REUSE); 658 } 659 660 DISPLAY("benchmarking zlib %s (zlibWrapper not reusing a context)\n", ZLIB_VERSION); 661 for (l=cLevel; l <= cLevelLast; l++) { 662 BMK_benchMem(srcBuffer, benchedSize, 663 displayName, l, 664 fileSizes, nbFiles, 665 dictBuffer, dictBufferSize, BMK_ZWRAP_ZLIB); 666 } 667 } 668 669 670 /*! BMK_loadFiles() : 671 Loads `buffer` with content of files listed within `fileNamesTable`. 672 At most, fills `buffer` entirely */ 673 static void BMK_loadFiles(void* buffer, size_t bufferSize, 674 size_t* fileSizes, 675 const char** fileNamesTable, unsigned nbFiles) 676 { 677 size_t pos = 0, totalSize = 0; 678 unsigned n; 679 for (n=0; n<nbFiles; n++) { 680 FILE* f; 681 U64 fileSize = UTIL_getFileSize(fileNamesTable[n]); 682 if (UTIL_isDirectory(fileNamesTable[n])) { 683 DISPLAYLEVEL(2, "Ignoring %s directory... \n", fileNamesTable[n]); 684 fileSizes[n] = 0; 685 continue; 686 } 687 if (fileSize == UTIL_FILESIZE_UNKNOWN) { 688 DISPLAYLEVEL(2, "Cannot determine size of %s ... \n", fileNamesTable[n]); 689 fileSizes[n] = 0; 690 continue; 691 } 692 f = fopen(fileNamesTable[n], "rb"); 693 if (f==NULL) EXM_THROW(10, "impossible to open file %s", fileNamesTable[n]); 694 DISPLAYUPDATE(2, "Loading %s... \r", fileNamesTable[n]); 695 if (fileSize > bufferSize-pos) fileSize = bufferSize-pos, nbFiles=n; /* buffer too small - stop after this file */ 696 { size_t const readSize = fread(((char*)buffer)+pos, 1, (size_t)fileSize, f); 697 if (readSize != (size_t)fileSize) EXM_THROW(11, "could not read %s", fileNamesTable[n]); 698 pos += readSize; } 699 fileSizes[n] = (size_t)fileSize; 700 totalSize += (size_t)fileSize; 701 fclose(f); 702 } 703 704 if (totalSize == 0) EXM_THROW(12, "no data to bench"); 705 } 706 707 static void BMK_benchFileTable(const char** fileNamesTable, unsigned nbFiles, 708 const char* dictFileName, int cLevel, int cLevelLast) 709 { 710 void* srcBuffer; 711 size_t benchedSize; 712 void* dictBuffer = NULL; 713 size_t dictBufferSize = 0; 714 size_t* fileSizes = (size_t*)malloc(nbFiles * sizeof(size_t)); 715 U64 const totalSizeToLoad = UTIL_getTotalFileSize(fileNamesTable, nbFiles); 716 char mfName[20] = {0}; 717 718 if (!fileSizes) EXM_THROW(12, "not enough memory for fileSizes"); 719 720 /* Load dictionary */ 721 if (dictFileName != NULL) { 722 U64 const dictFileSize = UTIL_getFileSize(dictFileName); 723 if (dictFileSize > 64 MB) 724 EXM_THROW(10, "dictionary file %s too large", dictFileName); 725 dictBufferSize = (size_t)dictFileSize; 726 dictBuffer = malloc(dictBufferSize); 727 if (dictBuffer==NULL) 728 EXM_THROW(11, "not enough memory for dictionary (%u bytes)", (U32)dictBufferSize); 729 BMK_loadFiles(dictBuffer, dictBufferSize, fileSizes, &dictFileName, 1); 730 } 731 732 /* Memory allocation & restrictions */ 733 benchedSize = BMK_findMaxMem(totalSizeToLoad * 3) / 3; 734 if ((U64)benchedSize > totalSizeToLoad) benchedSize = (size_t)totalSizeToLoad; 735 if (benchedSize < totalSizeToLoad) 736 DISPLAY("Not enough memory; testing %u MB only...\n", (U32)(benchedSize >> 20)); 737 srcBuffer = malloc(benchedSize + !benchedSize); 738 if (!srcBuffer) EXM_THROW(12, "not enough memory"); 739 740 /* Load input buffer */ 741 BMK_loadFiles(srcBuffer, benchedSize, fileSizes, fileNamesTable, nbFiles); 742 743 /* Bench */ 744 snprintf (mfName, sizeof(mfName), " %u files", nbFiles); 745 { const char* displayName = (nbFiles > 1) ? mfName : fileNamesTable[0]; 746 BMK_benchCLevel(srcBuffer, benchedSize, 747 displayName, cLevel, cLevelLast, 748 fileSizes, nbFiles, 749 dictBuffer, dictBufferSize); 750 } 751 752 /* clean up */ 753 free(srcBuffer); 754 free(dictBuffer); 755 free(fileSizes); 756 } 757 758 759 static void BMK_syntheticTest(int cLevel, int cLevelLast, double compressibility) 760 { 761 char name[20] = {0}; 762 size_t benchedSize = 10000000; 763 void* const srcBuffer = malloc(benchedSize); 764 765 /* Memory allocation */ 766 if (!srcBuffer) EXM_THROW(21, "not enough memory"); 767 768 /* Fill input buffer */ 769 RDG_genBuffer(srcBuffer, benchedSize, compressibility, 0.0, 0); 770 771 /* Bench */ 772 snprintf (name, sizeof(name), "Synthetic %2u%%", (unsigned)(compressibility*100)); 773 BMK_benchCLevel(srcBuffer, benchedSize, name, cLevel, cLevelLast, &benchedSize, 1, NULL, 0); 774 775 /* clean up */ 776 free(srcBuffer); 777 } 778 779 780 int BMK_benchFiles(const char** fileNamesTable, unsigned nbFiles, 781 const char* dictFileName, int cLevel, int cLevelLast) 782 { 783 double const compressibility = (double)g_compressibilityDefault / 100; 784 785 if (nbFiles == 0) 786 BMK_syntheticTest(cLevel, cLevelLast, compressibility); 787 else 788 BMK_benchFileTable(fileNamesTable, nbFiles, dictFileName, cLevel, cLevelLast); 789 return 0; 790 } 791 792 793 794 795 /*-************************************ 796 * Command Line 797 **************************************/ 798 static int usage(const char* programName) 799 { 800 DISPLAY(WELCOME_MESSAGE); 801 DISPLAY( "Usage :\n"); 802 DISPLAY( " %s [args] [FILE(s)] [-o file]\n", programName); 803 DISPLAY( "\n"); 804 DISPLAY( "FILE : a filename\n"); 805 DISPLAY( " with no FILE, or when FILE is - , read standard input\n"); 806 DISPLAY( "Arguments :\n"); 807 DISPLAY( " -D file: use `file` as Dictionary \n"); 808 DISPLAY( " -h/-H : display help/long help and exit\n"); 809 DISPLAY( " -V : display Version number and exit\n"); 810 DISPLAY( " -v : verbose mode; specify multiple times to increase log level (default:%d)\n", DEFAULT_DISPLAY_LEVEL); 811 DISPLAY( " -q : suppress warnings; specify twice to suppress errors too\n"); 812 #ifdef UTIL_HAS_CREATEFILELIST 813 DISPLAY( " -r : operate recursively on directories\n"); 814 #endif 815 DISPLAY( "\n"); 816 DISPLAY( "Benchmark arguments :\n"); 817 DISPLAY( " -b# : benchmark file(s), using # compression level (default : %d) \n", ZSTDCLI_CLEVEL_DEFAULT); 818 DISPLAY( " -e# : test all compression levels from -bX to # (default: %d)\n", ZSTDCLI_CLEVEL_DEFAULT); 819 DISPLAY( " -i# : minimum evaluation time in seconds (default : 3s)\n"); 820 DISPLAY( " -B# : cut file into independent blocks of size # (default: no block)\n"); 821 return 0; 822 } 823 824 static int badusage(const char* programName) 825 { 826 DISPLAYLEVEL(1, "Incorrect parameters\n"); 827 if (g_displayLevel >= 1) usage(programName); 828 return 1; 829 } 830 831 static void waitEnter(void) 832 { 833 int unused; 834 DISPLAY("Press enter to continue...\n"); 835 unused = getchar(); 836 (void)unused; 837 } 838 839 /*! readU32FromChar() : 840 @return : unsigned integer value reach from input in `char` format 841 Will also modify `*stringPtr`, advancing it to position where it stopped reading. 842 Note : this function can overflow if digit string > MAX_UINT */ 843 static unsigned readU32FromChar(const char** stringPtr) 844 { 845 unsigned result = 0; 846 while ((**stringPtr >='0') && (**stringPtr <='9')) 847 result *= 10, result += **stringPtr - '0', (*stringPtr)++ ; 848 return result; 849 } 850 851 852 #define CLEAN_RETURN(i) { operationResult = (i); goto _end; } 853 854 int main(int argCount, char** argv) 855 { 856 int argNb, 857 main_pause=0, 858 nextEntryIsDictionary=0, 859 operationResult=0, 860 nextArgumentIsFile=0; 861 int cLevel = ZSTDCLI_CLEVEL_DEFAULT; 862 int cLevelLast = 1; 863 unsigned recursive = 0; 864 const char** filenameTable = (const char**)malloc(argCount * sizeof(const char*)); /* argCount >= 1 */ 865 unsigned filenameIdx = 0; 866 const char* programName = argv[0]; 867 const char* dictFileName = NULL; 868 char* dynNameSpace = NULL; 869 #ifdef UTIL_HAS_CREATEFILELIST 870 const char** fileNamesTable = NULL; 871 char* fileNamesBuf = NULL; 872 unsigned fileNamesNb; 873 #endif 874 875 /* init */ 876 if (filenameTable==NULL) { DISPLAY("zstd: %s \n", strerror(errno)); exit(1); } 877 displayOut = stderr; 878 879 /* Pick out program name from path. Don't rely on stdlib because of conflicting behavior */ 880 { size_t pos; 881 for (pos = (int)strlen(programName); pos > 0; pos--) { if (programName[pos] == '/') { pos++; break; } } 882 programName += pos; 883 } 884 885 /* command switches */ 886 for(argNb=1; argNb<argCount; argNb++) { 887 const char* argument = argv[argNb]; 888 if(!argument) continue; /* Protection if argument empty */ 889 890 if (nextArgumentIsFile==0) { 891 892 /* long commands (--long-word) */ 893 if (!strcmp(argument, "--")) { nextArgumentIsFile=1; continue; } 894 if (!strcmp(argument, "--version")) { displayOut=stdout; DISPLAY(WELCOME_MESSAGE); CLEAN_RETURN(0); } 895 if (!strcmp(argument, "--help")) { displayOut=stdout; CLEAN_RETURN(usage(programName)); } 896 if (!strcmp(argument, "--verbose")) { g_displayLevel++; continue; } 897 if (!strcmp(argument, "--quiet")) { g_displayLevel--; continue; } 898 899 /* Decode commands (note : aggregated commands are allowed) */ 900 if (argument[0]=='-') { 901 argument++; 902 903 while (argument[0]!=0) { 904 switch(argument[0]) 905 { 906 /* Display help */ 907 case 'V': displayOut=stdout; DISPLAY(WELCOME_MESSAGE); CLEAN_RETURN(0); /* Version Only */ 908 case 'H': 909 case 'h': displayOut=stdout; CLEAN_RETURN(usage(programName)); 910 911 /* Use file content as dictionary */ 912 case 'D': nextEntryIsDictionary = 1; argument++; break; 913 914 /* Verbose mode */ 915 case 'v': g_displayLevel++; argument++; break; 916 917 /* Quiet mode */ 918 case 'q': g_displayLevel--; argument++; break; 919 920 #ifdef UTIL_HAS_CREATEFILELIST 921 /* recursive */ 922 case 'r': recursive=1; argument++; break; 923 #endif 924 925 /* Benchmark */ 926 case 'b': 927 /* first compression Level */ 928 argument++; 929 cLevel = readU32FromChar(&argument); 930 break; 931 932 /* range bench (benchmark only) */ 933 case 'e': 934 /* last compression Level */ 935 argument++; 936 cLevelLast = readU32FromChar(&argument); 937 break; 938 939 /* Modify Nb Iterations (benchmark only) */ 940 case 'i': 941 argument++; 942 { U32 const iters = readU32FromChar(&argument); 943 BMK_setNotificationLevel(g_displayLevel); 944 BMK_SetNbIterations(iters); 945 } 946 break; 947 948 /* cut input into blocks (benchmark only) */ 949 case 'B': 950 argument++; 951 { size_t bSize = readU32FromChar(&argument); 952 if (toupper(*argument)=='K') bSize<<=10, argument++; /* allows using KB notation */ 953 if (toupper(*argument)=='M') bSize<<=20, argument++; 954 if (toupper(*argument)=='B') argument++; 955 BMK_setNotificationLevel(g_displayLevel); 956 BMK_SetBlockSize(bSize); 957 } 958 break; 959 960 /* Pause at the end (-p) or set an additional param (-p#) (hidden option) */ 961 case 'p': argument++; 962 if ((*argument>='0') && (*argument<='9')) { 963 BMK_setAdditionalParam(readU32FromChar(&argument)); 964 } else 965 main_pause=1; 966 break; 967 /* unknown command */ 968 default : CLEAN_RETURN(badusage(programName)); 969 } 970 } 971 continue; 972 } /* if (argument[0]=='-') */ 973 974 } /* if (nextArgumentIsAFile==0) */ 975 976 if (nextEntryIsDictionary) { 977 nextEntryIsDictionary = 0; 978 dictFileName = argument; 979 continue; 980 } 981 982 /* add filename to list */ 983 filenameTable[filenameIdx++] = argument; 984 } 985 986 /* Welcome message (if verbose) */ 987 DISPLAYLEVEL(3, WELCOME_MESSAGE); 988 989 #ifdef UTIL_HAS_CREATEFILELIST 990 if (recursive) { 991 fileNamesTable = UTIL_createFileList(filenameTable, filenameIdx, &fileNamesBuf, &fileNamesNb, 1); 992 if (fileNamesTable) { 993 unsigned u; 994 for (u=0; u<fileNamesNb; u++) DISPLAYLEVEL(4, "%u %s\n", u, fileNamesTable[u]); 995 free((void*)filenameTable); 996 filenameTable = fileNamesTable; 997 filenameIdx = fileNamesNb; 998 } 999 } 1000 #endif 1001 1002 BMK_setNotificationLevel(g_displayLevel); 1003 BMK_benchFiles(filenameTable, filenameIdx, dictFileName, cLevel, cLevelLast); 1004 1005 _end: 1006 if (main_pause) waitEnter(); 1007 free(dynNameSpace); 1008 #ifdef UTIL_HAS_CREATEFILELIST 1009 if (fileNamesTable) 1010 UTIL_freeFileList(fileNamesTable, fileNamesBuf); 1011 else 1012 #endif 1013 free((void*)filenameTable); 1014 return operationResult; 1015 } 1016