From ac7992896077695b576587431a1e254693d690c8 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 3 Jul 2017 14:11:55 -0700 Subject: [PATCH 001/145] version one complete, can compress a file given input and output names --- contrib/adaptive-compression/Makefile | 26 ++++++++++ contrib/adaptive-compression/v1.c | 73 +++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 contrib/adaptive-compression/Makefile create mode 100644 contrib/adaptive-compression/v1.c diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile new file mode 100644 index 00000000..12607edf --- /dev/null +++ b/contrib/adaptive-compression/Makefile @@ -0,0 +1,26 @@ + +ZSTDDIR = ../../lib +ZSTDCOMMON_FILES := $(ZSTDDIR)/common/*.c +ZSTDCOMP_FILES := $(ZSTDDIR)/compress/*.c +ZSTDDECOMP_FILES := $(ZSTDDIR)/decompress/*.c +ZSTD_FILES := $(ZSTDDECOMP_FILES) $(ZSTDCOMMON_FILES) $(ZSTDCOMP_FILES) + +DEBUGFLAGS= -g -DZSTD_DEBUG=1 +CPPFLAGS += -I$(ZSTDDIR) -I$(ZSTDDIR)/common -I$(ZSTDDIR)/compress \ + -I$(ZSTDDIR)/dictBuilder -I$(ZSTDDIR)/deprecated -I$(PRGDIR) +CFLAGS ?= -O3 +CFLAGS += -Wall -Wextra -Wcast-qual -Wcast-align -Wshadow \ + -Wstrict-aliasing=1 -Wswitch-enum -Wdeclaration-after-statement \ + -Wstrict-prototypes -Wundef -Wformat-security \ + -Wvla -Wformat=2 -Winit-self -Wfloat-equal -Wwrite-strings \ + -Wredundant-decls +CFLAGS += $(DEBUGFLAGS) +CFLAGS += $(MOREFLAGS) +FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) + +adaptive: $(ZSTD_FILES) v1.c + $(CC) $(FLAGS) $^ -o $@ + +clean: + @$(RM) -f adaptive + @$(RM) -f tmp* diff --git a/contrib/adaptive-compression/v1.c b/contrib/adaptive-compression/v1.c new file mode 100644 index 00000000..ef8062d8 --- /dev/null +++ b/contrib/adaptive-compression/v1.c @@ -0,0 +1,73 @@ +#define DISPLAY(...) fprintf(stderr, __VA_ARGS__) +#define FILE_CHUNK_SIZE 4 << 20 +typedef unsigned char BYTE; + +#include +#include +#include "zstd.h" + + + +/* return 0 if successful, else return error */ +int main(int argCount, const char* argv[]) +{ + const char* const srcFilename = argv[1]; + const char* const dstFilename = argv[2]; + FILE* const srcFile = fopen(srcFilename, "rb"); + FILE* const dstFile = fopen(dstFilename, "wb"); + BYTE* const src = malloc(FILE_CHUNK_SIZE); + size_t const dstSize = ZSTD_compressBound(FILE_CHUNK_SIZE); + BYTE* const dst = malloc(dstSize); + int ret = 0; + + /* checking for errors */ + if (!srcFilename || !dstFilename || !src || !dst) { + DISPLAY("Error: initial variables could not be allocated\n"); + ret = 1; + goto cleanup; + } + + /* compressing in blocks */ + for ( ; ; ) { + size_t const readSize = fread(src, 1, FILE_CHUNK_SIZE, srcFile); + if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { + DISPLAY("Error: could not read %d bytes\n", FILE_CHUNK_SIZE); + ret = 1; + goto cleanup; + } + { + size_t const compressedSize = ZSTD_compress(dst, dstSize, src, readSize, 6); + if (ZSTD_isError(compressedSize)) { + DISPLAY("Error: something went wrong during compression\n"); + ret = 1; + goto cleanup; + } + { + size_t const writeSize = fwrite(dst, 1, compressedSize, dstFile); + if (writeSize != compressedSize) { + DISPLAY("Error: could not write compressed data to file\n"); + ret = 1; + goto cleanup; + } + } + } + if (feof(srcFile)) { + /* reached end of file */ + break; + } + } + + /* file compression completed */ + { + int const error = fclose(srcFile); + if (ret != 0) { + DISPLAY("Error: could not close the file\n"); + ret = error; + goto cleanup; + } + } +cleanup: + if (src != NULL) free(src); + if (dst != NULL) free(dst); + return ret; +} From 00b5e6c512a2c9d8bc0db321ff1f79889d803c33 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 3 Jul 2017 14:18:46 -0700 Subject: [PATCH 002/145] continuing work on v2 --- contrib/adaptive-compression/Makefile | 9 +++- contrib/adaptive-compression/v2.c | 73 +++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 contrib/adaptive-compression/v2.c diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index 12607edf..b2c47654 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -18,9 +18,14 @@ CFLAGS += $(DEBUGFLAGS) CFLAGS += $(MOREFLAGS) FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -adaptive: $(ZSTD_FILES) v1.c +all: clean v1 v2 +v1: $(ZSTD_FILES) v1.c + $(CC) $(FLAGS) $^ -o $@ + +v2: $(ZSTD_FILES) v2.c $(CC) $(FLAGS) $^ -o $@ clean: - @$(RM) -f adaptive + @$(RM) -f v1 v2 + @$(RM) -rf *.dSYM @$(RM) -f tmp* diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/v2.c new file mode 100644 index 00000000..ef8062d8 --- /dev/null +++ b/contrib/adaptive-compression/v2.c @@ -0,0 +1,73 @@ +#define DISPLAY(...) fprintf(stderr, __VA_ARGS__) +#define FILE_CHUNK_SIZE 4 << 20 +typedef unsigned char BYTE; + +#include +#include +#include "zstd.h" + + + +/* return 0 if successful, else return error */ +int main(int argCount, const char* argv[]) +{ + const char* const srcFilename = argv[1]; + const char* const dstFilename = argv[2]; + FILE* const srcFile = fopen(srcFilename, "rb"); + FILE* const dstFile = fopen(dstFilename, "wb"); + BYTE* const src = malloc(FILE_CHUNK_SIZE); + size_t const dstSize = ZSTD_compressBound(FILE_CHUNK_SIZE); + BYTE* const dst = malloc(dstSize); + int ret = 0; + + /* checking for errors */ + if (!srcFilename || !dstFilename || !src || !dst) { + DISPLAY("Error: initial variables could not be allocated\n"); + ret = 1; + goto cleanup; + } + + /* compressing in blocks */ + for ( ; ; ) { + size_t const readSize = fread(src, 1, FILE_CHUNK_SIZE, srcFile); + if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { + DISPLAY("Error: could not read %d bytes\n", FILE_CHUNK_SIZE); + ret = 1; + goto cleanup; + } + { + size_t const compressedSize = ZSTD_compress(dst, dstSize, src, readSize, 6); + if (ZSTD_isError(compressedSize)) { + DISPLAY("Error: something went wrong during compression\n"); + ret = 1; + goto cleanup; + } + { + size_t const writeSize = fwrite(dst, 1, compressedSize, dstFile); + if (writeSize != compressedSize) { + DISPLAY("Error: could not write compressed data to file\n"); + ret = 1; + goto cleanup; + } + } + } + if (feof(srcFile)) { + /* reached end of file */ + break; + } + } + + /* file compression completed */ + { + int const error = fclose(srcFile); + if (ret != 0) { + DISPLAY("Error: could not close the file\n"); + ret = error; + goto cleanup; + } + } +cleanup: + if (src != NULL) free(src); + if (dst != NULL) free(dst); + return ret; +} From 0887e98d4bb9f8a62b29d0dcb9b0af7dd732aaa5 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 3 Jul 2017 17:28:59 -0700 Subject: [PATCH 003/145] finished main portion of code, now need to debug --- contrib/adaptive-compression/Makefile | 1 + contrib/adaptive-compression/v2.c | 285 +++++++++++++++++++++++--- 2 files changed, 253 insertions(+), 33 deletions(-) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index b2c47654..fbab219c 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -29,3 +29,4 @@ clean: @$(RM) -f v1 v2 @$(RM) -rf *.dSYM @$(RM) -f tmp* + @echo "finished cleaning" diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/v2.c index ef8062d8..f85ccfe1 100644 --- a/contrib/adaptive-compression/v2.c +++ b/contrib/adaptive-compression/v2.c @@ -2,72 +2,291 @@ #define FILE_CHUNK_SIZE 4 << 20 typedef unsigned char BYTE; -#include -#include +#include /* fprintf */ +#include /* malloc, free */ +#include /* pthread functions */ +#include /* memset */ #include "zstd.h" +typedef struct { + void* start; + size_t size; +} buffer_t; + +typedef struct { + buffer_t src; + buffer_t dst; + unsigned compressionLevel; + unsigned jobID; + unsigned jobCompleted; + unsigned jobReady; + pthread_mutex_t* jobCompleted_mutex; + pthread_cond_t* jobCompleted_cond; + pthread_mutex_t* jobReady_mutex; + pthread_cond_t* jobReady_cond; + size_t compressedSize; +} jobDescription; + +typedef struct { + unsigned compressionLevel; + unsigned numActiveThreads; + unsigned numJobs; + unsigned nextJobID; + unsigned threadError; + pthread_mutex_t jobCompleted_mutex; + pthread_cond_t jobCompleted_cond; + pthread_mutex_t jobReady_mutex; + pthread_cond_t jobReady_cond; + jobDescription* jobs; + FILE* dstFile; +} adaptCCtx; + +static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) +{ + adaptCCtx* ctx = malloc(sizeof(adaptCCtx)); + memset(ctx, 0, sizeof(adaptCCtx)); + ctx->compressionLevel = 6; /* default */ + pthread_mutex_init(&ctx->jobCompleted_mutex, NULL); + pthread_cond_init(&ctx->jobCompleted_cond, NULL); + pthread_mutex_init(&ctx->jobReady_mutex, NULL); + pthread_cond_init(&ctx->jobReady_cond, NULL); + ctx->numJobs = numJobs; + ctx->jobs = malloc(numJobs*sizeof(jobDescription)); + ctx->nextJobID = 0; + ctx->threadError = 0; + if (!ctx->jobs) { + DISPLAY("Error: could not allocate space for jobs during context creation\n"); + return NULL; + } + { + FILE* dstFile = fopen(outFilename, "wb"); + if (dstFile == NULL) { + DISPLAY("Error: could not open output file\n"); + return NULL; + } + ctx->dstFile = dstFile; + } + return ctx; +} + +static void freeCompressionJobs(adaptCCtx* ctx) +{ + unsigned u; + for (u=0; unumJobs; u++) { + jobDescription job = ctx->jobs[u]; + if (job.dst.start) free(job.dst.start); + if (job.src.start) free(job.src.start); + } +} + +static int freeCCtx(adaptCCtx* ctx) +{ + int const completedMutexError = pthread_mutex_destroy(&ctx->jobCompleted_mutex); + int const completedCondError = pthread_cond_destroy(&ctx->jobCompleted_cond); + int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); + int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); + int const fileError = fclose(ctx->dstFile); + freeCompressionJobs(ctx); + free(ctx->jobs); + return completedMutexError | completedCondError | readyMutexError | readyCondError | fileError; +} + +static void* compressionThread(void* arg) +{ + adaptCCtx* ctx = (adaptCCtx*)arg; + unsigned currJob = 0; + for ( ; ; ) { + jobDescription* job = &ctx->jobs[currJob]; + pthread_mutex_lock(job->jobReady_mutex); + while(job->jobReady == 0) { + pthread_cond_wait(job->jobReady_cond, job->jobReady_mutex); + } + pthread_mutex_unlock(job->jobReady_mutex); + + /* compress the data */ + { + size_t const compressedSize = ZSTD_compress(job->dst.start, job->dst.size, job->src.start, job->src.size, job->compressionLevel); + if (ZSTD_isError(compressedSize)) { + ctx->threadError = 1; + DISPLAY("Error: somethign went wrong during compression\n"); + return arg; + } + job->compressedSize = compressedSize; + } + currJob++; + if (currJob >= ctx->numJobs || ctx->threadError) { + /* finished compressing all jobs */ + break; + } + } + return arg; +} + +static void* outputThread(void* arg) +{ + adaptCCtx* ctx = (adaptCCtx*)arg; + unsigned currJob = 0; + for ( ; ; ) { + jobDescription* job = &ctx->jobs[currJob]; + pthread_mutex_lock(job->jobCompleted_mutex); + while (job->jobCompleted == 0) { + pthread_cond_wait(job->jobCompleted_cond, job->jobCompleted_mutex); + } + pthread_mutex_unlock(job->jobCompleted_mutex); + { + size_t const compressedSize = job->compressedSize; + if (ZSTD_isError(compressedSize)) { + DISPLAY("Error: an error occurred during compression\n"); + return arg; /* TODO: return something else if error */ + } + { + size_t const writeSize = fwrite(ctx->jobs[currJob].dst.start, 1, compressedSize, ctx->dstFile); + if (writeSize != compressedSize) { + DISPLAY("Error: an error occurred during file write operation\n"); + return arg; /* TODO: return something else if error */ + } + } + } + currJob++; + if (currJob >= ctx->numJobs || ctx->threadError) { + /* finished with all jobs */ + break; + } + } + return arg; +} + + +static size_t getFileSize(const char* const filename) +{ + FILE* fd = fopen(filename, "rb"); + if (fd == NULL) { + DISPLAY("Error: could not open file in order to get file size\n"); + return -1; /* intentional underflow */ + } + if (fseek(fd, 0, SEEK_END) != 0) { + DISPLAY("Error: fseek failed during file size computation\n"); + return -1; + } + { + size_t const fileSize = ftell(fd); + if (fclose(fd) != 0) { + DISPLAY("Error: could not close file during file size computation\n"); + return -1; + } + return fileSize; + } +} + +static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) +{ + unsigned const nextJob = ctx->nextJobID; + jobDescription job = ctx->jobs[nextJob]; + job.compressionLevel = ctx->compressionLevel; + job.src.start = malloc(srcSize); + job.src.size = srcSize; + job.dst.size = ZSTD_compressBound(srcSize); + job.dst.start = malloc(job.dst.size); + job.jobCompleted = 0; + job.jobCompleted_cond = &ctx->jobCompleted_cond; + job.jobCompleted_mutex = &ctx->jobCompleted_mutex; + job.jobReady_cond = &ctx->jobReady_cond; + job.jobReady_mutex = &ctx->jobReady_mutex; + job.jobID = nextJob; + if (!job.src.start || !job.dst.start) { + /* problem occurred, free things then return */ + if (job.src.start) free(job.src.start); + if (job.dst.start) free(job.dst.start); + return 1; + } + memcpy(job.src.start, data, srcSize); + ctx->nextJobID++; + return 0; +} + /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) { const char* const srcFilename = argv[1]; const char* const dstFilename = argv[2]; - FILE* const srcFile = fopen(srcFilename, "rb"); - FILE* const dstFile = fopen(dstFilename, "wb"); BYTE* const src = malloc(FILE_CHUNK_SIZE); - size_t const dstSize = ZSTD_compressBound(FILE_CHUNK_SIZE); - BYTE* const dst = malloc(dstSize); + FILE* const srcFile = fopen(srcFilename, "rb"); + size_t fileSize = getFileSize(srcFilename); + size_t const numJobsPrelim = (fileSize / FILE_CHUNK_SIZE) + 1; + size_t const numJobs = (numJobsPrelim * FILE_CHUNK_SIZE) == fileSize ? numJobsPrelim : numJobsPrelim + 1; int ret = 0; + adaptCCtx* ctx = NULL; + /* checking for errors */ - if (!srcFilename || !dstFilename || !src || !dst) { + if (fileSize == (size_t)(-1)) { + ret = 1; + goto cleanup; + } + if (!srcFilename || !dstFilename || !src) { DISPLAY("Error: initial variables could not be allocated\n"); ret = 1; goto cleanup; } - /* compressing in blocks */ - for ( ; ; ) { - size_t const readSize = fread(src, 1, FILE_CHUNK_SIZE, srcFile); - if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { - DISPLAY("Error: could not read %d bytes\n", FILE_CHUNK_SIZE); + /* creating context */ + ctx = createCCtx(numJobs, dstFilename); + if (ctx == NULL) { + ret = 1; + goto cleanup; + } + + /* create output thread */ + { + pthread_t out; + if (pthread_create(&out, NULL, &outputThread, ctx)) { + DISPLAY("Error: could not create output thread\n"); ret = 1; goto cleanup; } + } + /* create compression thread */ + { + pthread_t compression; + if (pthread_create(&compression, NULL, &compressionThread, ctx)) { + DISPLAY("Error: could not create compression thread\n"); + ret = 1; + goto cleanup; + } + } + + /* creating jobs */ + for ( ; ; ) { + size_t const readSize = fread(src, 1, FILE_CHUNK_SIZE, srcFile); + if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { + DISPLAY("Error: problem occurred during read from src file\n"); + ret = 1; + goto cleanup; + } + + /* reading was fine, now create the compression job */ { - size_t const compressedSize = ZSTD_compress(dst, dstSize, src, readSize, 6); - if (ZSTD_isError(compressedSize)) { - DISPLAY("Error: something went wrong during compression\n"); - ret = 1; + int const error = createCompressionJob(ctx, src, readSize); + if (error != 0) { + ret = error; goto cleanup; } - { - size_t const writeSize = fwrite(dst, 1, compressedSize, dstFile); - if (writeSize != compressedSize) { - DISPLAY("Error: could not write compressed data to file\n"); - ret = 1; - goto cleanup; - } - } - } - if (feof(srcFile)) { - /* reached end of file */ - break; } } /* file compression completed */ { - int const error = fclose(srcFile); - if (ret != 0) { - DISPLAY("Error: could not close the file\n"); - ret = error; + int const fileCloseError = fclose(srcFile); + int const cctxReleaseError = freeCCtx(ctx); + if (fileCloseError | cctxReleaseError) { + ret = 1; goto cleanup; } } cleanup: if (src != NULL) free(src); - if (dst != NULL) free(dst); + if (ctx != NULL) freeCCtx(ctx); return ret; } From dd96efa9efc20b298ec152daed9c9b18d641e3f0 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 3 Jul 2017 17:44:22 -0700 Subject: [PATCH 004/145] added print statements for debugging, fixed long memset by changing to calloc --- contrib/adaptive-compression/run.sh | 2 ++ contrib/adaptive-compression/v2 | Bin 0 -> 467088 bytes contrib/adaptive-compression/v2.c | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100755 contrib/adaptive-compression/run.sh create mode 100755 contrib/adaptive-compression/v2 diff --git a/contrib/adaptive-compression/run.sh b/contrib/adaptive-compression/run.sh new file mode 100755 index 00000000..f9e27673 --- /dev/null +++ b/contrib/adaptive-compression/run.sh @@ -0,0 +1,2 @@ +make clean v2 +./v2 tests/test2048.pdf tmp.zst diff --git a/contrib/adaptive-compression/v2 b/contrib/adaptive-compression/v2 new file mode 100755 index 0000000000000000000000000000000000000000..974745ab75ce65419904f0cdf236697bc45f05ca GIT binary patch literal 467088 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 literal 0 HcmV?d00001 diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/v2.c index f85ccfe1..4a171bd0 100644 --- a/contrib/adaptive-compression/v2.c +++ b/contrib/adaptive-compression/v2.c @@ -45,6 +45,7 @@ typedef struct { static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) { + adaptCCtx* ctx = malloc(sizeof(adaptCCtx)); memset(ctx, 0, sizeof(adaptCCtx)); ctx->compressionLevel = 6; /* default */ @@ -53,7 +54,7 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) pthread_mutex_init(&ctx->jobReady_mutex, NULL); pthread_cond_init(&ctx->jobReady_cond, NULL); ctx->numJobs = numJobs; - ctx->jobs = malloc(numJobs*sizeof(jobDescription)); + ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); ctx->nextJobID = 0; ctx->threadError = 0; if (!ctx->jobs) { @@ -95,6 +96,7 @@ static int freeCCtx(adaptCCtx* ctx) static void* compressionThread(void* arg) { + DISPLAY("started compression thread\n"); adaptCCtx* ctx = (adaptCCtx*)arg; unsigned currJob = 0; for ( ; ; ) { @@ -126,6 +128,7 @@ static void* compressionThread(void* arg) static void* outputThread(void* arg) { + DISPLAY("started output thread\n"); adaptCCtx* ctx = (adaptCCtx*)arg; unsigned currJob = 0; for ( ; ; ) { @@ -202,6 +205,10 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) return 1; } memcpy(job.src.start, data, srcSize); + pthread_mutex_lock(job.jobReady_mutex); + job.jobReady = 1; + pthread_cond_signal(job.jobReady_cond); + pthread_mutex_unlock(job.jobReady_mutex); ctx->nextJobID++; return 0; } @@ -209,6 +216,10 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) { + if (argCount < 2) { + DISPLAY("Error: not enough arguments\n"); + return 1; + } const char* const srcFilename = argv[1]; const char* const dstFilename = argv[2]; BYTE* const src = malloc(FILE_CHUNK_SIZE); @@ -228,7 +239,7 @@ int main(int argCount, const char* argv[]) if (!srcFilename || !dstFilename || !src) { DISPLAY("Error: initial variables could not be allocated\n"); ret = 1; - goto cleanup; + goto cleanup; } /* creating context */ From ff9ac637d9f11e9e8e574ccad6989aafb8018a37 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 3 Jul 2017 17:44:40 -0700 Subject: [PATCH 005/145] removed unnecessary files --- contrib/adaptive-compression/v2 | Bin 467088 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 contrib/adaptive-compression/v2 diff --git a/contrib/adaptive-compression/v2 b/contrib/adaptive-compression/v2 deleted file mode 100755 index 974745ab75ce65419904f0cdf236697bc45f05ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 467088 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 From dd447bb9a73bc4cee24c545f35349d88bdb88287 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 3 Jul 2017 19:00:55 -0700 Subject: [PATCH 006/145] fixed an error with number of jobs --- contrib/adaptive-compression/v2 | Bin 0 -> 467087 bytes contrib/adaptive-compression/v2.c | 32 +++++++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) create mode 100755 contrib/adaptive-compression/v2 diff --git a/contrib/adaptive-compression/v2 b/contrib/adaptive-compression/v2 new file mode 100755 index 0000000000000000000000000000000000000000..0c0e194ebae05845c80f8f24d2534a53a56c414d GIT binary patch literal 467087 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%0A=65g)!C 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

o-VM=e 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 literal 0 HcmV?d00001 diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/v2.c index 4a171bd0..5bbb8b9b 100644 --- a/contrib/adaptive-compression/v2.c +++ b/contrib/adaptive-compression/v2.c @@ -47,6 +47,10 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) { adaptCCtx* ctx = malloc(sizeof(adaptCCtx)); + if (ctx == NULL) { + DISPLAY("Error: could not allocate space for context\n"); + return NULL; + } memset(ctx, 0, sizeof(adaptCCtx)); ctx->compressionLevel = 6; /* default */ pthread_mutex_init(&ctx->jobCompleted_mutex, NULL); @@ -76,6 +80,8 @@ static void freeCompressionJobs(adaptCCtx* ctx) { unsigned u; for (u=0; unumJobs; u++) { + DISPLAY("freeing compression job %u\n", u); + DISPLAY("%u\n", ctx->numJobs); jobDescription job = ctx->jobs[u]; if (job.dst.start) free(job.dst.start); if (job.src.start) free(job.src.start); @@ -84,6 +90,7 @@ static void freeCompressionJobs(adaptCCtx* ctx) static int freeCCtx(adaptCCtx* ctx) { + /* TODO: wait until jobs finish */ int const completedMutexError = pthread_mutex_destroy(&ctx->jobCompleted_mutex); int const completedCondError = pthread_cond_destroy(&ctx->jobCompleted_cond); int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); @@ -130,6 +137,8 @@ static void* outputThread(void* arg) { DISPLAY("started output thread\n"); adaptCCtx* ctx = (adaptCCtx*)arg; + DISPLAY("casted ctx\n"); + unsigned currJob = 0; for ( ; ; ) { jobDescription* job = &ctx->jobs[currJob]; @@ -225,7 +234,7 @@ int main(int argCount, const char* argv[]) BYTE* const src = malloc(FILE_CHUNK_SIZE); FILE* const srcFile = fopen(srcFilename, "rb"); size_t fileSize = getFileSize(srcFilename); - size_t const numJobsPrelim = (fileSize / FILE_CHUNK_SIZE) + 1; + size_t const numJobsPrelim = (fileSize >> 22) + 1; /* TODO: figure out why can't divide here */ size_t const numJobs = (numJobsPrelim * FILE_CHUNK_SIZE) == fileSize ? numJobsPrelim : numJobsPrelim + 1; int ret = 0; adaptCCtx* ctx = NULL; @@ -236,7 +245,7 @@ int main(int argCount, const char* argv[]) ret = 1; goto cleanup; } - if (!srcFilename || !dstFilename || !src) { + if (!srcFilename || !dstFilename || !src || !srcFile) { DISPLAY("Error: initial variables could not be allocated\n"); ret = 1; goto cleanup; @@ -270,13 +279,14 @@ int main(int argCount, const char* argv[]) /* creating jobs */ for ( ; ; ) { + DISPLAY("in job creation loop\n"); size_t const readSize = fread(src, 1, FILE_CHUNK_SIZE, srcFile); if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); ret = 1; goto cleanup; } - + DISPLAY("reading was fine\n"); /* reading was fine, now create the compression job */ { int const error = createCompressionJob(ctx, src, readSize); @@ -285,19 +295,13 @@ int main(int argCount, const char* argv[]) goto cleanup; } } + if (feof(srcFile)) break; } - - /* file compression completed */ - { - int const fileCloseError = fclose(srcFile); - int const cctxReleaseError = freeCCtx(ctx); - if (fileCloseError | cctxReleaseError) { - ret = 1; - goto cleanup; - } - } + DISPLAY("cleanup\n"); cleanup: + /* file compression completed */ + ret |= (srcFile != NULL) ? fclose(srcFile) : 0; + ret |= (ctx != NULL) ? freeCCtx(ctx) : 0; if (src != NULL) free(src); - if (ctx != NULL) freeCCtx(ctx); return ret; } From 95ea54b4cf3fd49d4ada2809ebfa41d73303761b Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 3 Jul 2017 19:24:22 -0700 Subject: [PATCH 007/145] added code for waiitng for all jobs to finish --- contrib/adaptive-compression/v2 | Bin 467087 -> 466752 bytes contrib/adaptive-compression/v2.c | 37 ++++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/contrib/adaptive-compression/v2 b/contrib/adaptive-compression/v2 index 0c0e194ebae05845c80f8f24d2534a53a56c414d..38034cdab55dde6c55382233c34d86b0b58d2083 100755 GIT binary patch delta 40567 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 W>#Tcx=Xcxu8g-uR@vV9J_xwL!k6sS| delta 41799 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 diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/v2.c index 5bbb8b9b..eaaecb76 100644 --- a/contrib/adaptive-compression/v2.c +++ b/contrib/adaptive-compression/v2.c @@ -35,10 +35,13 @@ typedef struct { unsigned numJobs; unsigned nextJobID; unsigned threadError; + unsigned allJobsCompleted; pthread_mutex_t jobCompleted_mutex; pthread_cond_t jobCompleted_cond; pthread_mutex_t jobReady_mutex; pthread_cond_t jobReady_cond; + pthread_mutex_t allJobsCompleted_mutex; + pthread_cond_t allJobsCompleted_cond; jobDescription* jobs; FILE* dstFile; } adaptCCtx; @@ -57,10 +60,13 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) pthread_cond_init(&ctx->jobCompleted_cond, NULL); pthread_mutex_init(&ctx->jobReady_mutex, NULL); pthread_cond_init(&ctx->jobReady_cond, NULL); + pthread_mutex_init(&ctx->allJobsCompleted_mutex, NULL); + pthread_cond_init(&ctx->allJobsCompleted_cond, NULL); ctx->numJobs = numJobs; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); ctx->nextJobID = 0; ctx->threadError = 0; + ctx->allJobsCompleted = 0; if (!ctx->jobs) { DISPLAY("Error: could not allocate space for jobs during context creation\n"); return NULL; @@ -90,15 +96,21 @@ static void freeCompressionJobs(adaptCCtx* ctx) static int freeCCtx(adaptCCtx* ctx) { - /* TODO: wait until jobs finish */ - int const completedMutexError = pthread_mutex_destroy(&ctx->jobCompleted_mutex); - int const completedCondError = pthread_cond_destroy(&ctx->jobCompleted_cond); - int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); - int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); - int const fileError = fclose(ctx->dstFile); - freeCompressionJobs(ctx); - free(ctx->jobs); - return completedMutexError | completedCondError | readyMutexError | readyCondError | fileError; + pthread_mutex_lock(&ctx->allJobsCompleted_mutex); + while (ctx->allJobsCompleted == 0) { + pthread_cond_wait(&ctx->allJobsCompleted_cond, &ctx->allJobsCompleted_mutex); + } + pthread_mutex_unlock(&ctx->allJobsCompleted_mutex); + { + int const completedMutexError = pthread_mutex_destroy(&ctx->jobCompleted_mutex); + int const completedCondError = pthread_cond_destroy(&ctx->jobCompleted_cond); + int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); + int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); + int const fileError = fclose(ctx->dstFile); + freeCompressionJobs(ctx); + free(ctx->jobs); + return completedMutexError | completedCondError | readyMutexError | readyCondError | fileError; + } } static void* compressionThread(void* arg) @@ -164,6 +176,10 @@ static void* outputThread(void* arg) currJob++; if (currJob >= ctx->numJobs || ctx->threadError) { /* finished with all jobs */ + pthread_mutex_lock(&ctx->allJobsCompleted_mutex); + ctx->allJobsCompleted = 1; + pthread_cond_signal(&ctx->allJobsCompleted_cond); + pthread_mutex_unlock(&ctx->allJobsCompleted_mutex); break; } } @@ -267,6 +283,7 @@ int main(int argCount, const char* argv[]) goto cleanup; } } + /* create compression thread */ { pthread_t compression; @@ -297,7 +314,7 @@ int main(int argCount, const char* argv[]) } if (feof(srcFile)) break; } - DISPLAY("cleanup\n"); + cleanup: /* file compression completed */ ret |= (srcFile != NULL) ? fclose(srcFile) : 0; From cd50382c0374dbe5109027ed126a6f22b0a75763 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 3 Jul 2017 19:28:48 -0700 Subject: [PATCH 008/145] fixed some issues with segfaults --- contrib/adaptive-compression/v2 | Bin 466752 -> 0 bytes contrib/adaptive-compression/v2.c | 11 ++++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) delete mode 100755 contrib/adaptive-compression/v2 diff --git a/contrib/adaptive-compression/v2 b/contrib/adaptive-compression/v2 deleted file mode 100755 index 38034cdab55dde6c55382233c34d86b0b58d2083..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 466752 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

yd?lewIEBJ>78lO*Xe@OA4kA&W1 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 N^fT3g@c%n-?>_|oMuz|Z diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/v2.c index eaaecb76..edb2d0f3 100644 --- a/contrib/adaptive-compression/v2.c +++ b/contrib/adaptive-compression/v2.c @@ -64,6 +64,15 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) pthread_cond_init(&ctx->allJobsCompleted_cond, NULL); ctx->numJobs = numJobs; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); + { + unsigned u; + for (u=0; ujobs[u].jobCompleted_mutex = &ctx->jobCompleted_mutex; + ctx->jobs[u].jobCompleted_cond = &ctx->jobCompleted_cond; + ctx->jobs[u].jobReady_mutex = &ctx->jobReady_mutex; + ctx->jobs[u].jobReady_cond = &ctx->jobReady_cond; + } + } ctx->nextJobID = 0; ctx->threadError = 0; ctx->allJobsCompleted = 0; @@ -314,7 +323,7 @@ int main(int argCount, const char* argv[]) } if (feof(srcFile)) break; } - + cleanup: /* file compression completed */ ret |= (srcFile != NULL) ? fclose(srcFile) : 0; From 0b70152a9b262e6e2f4106c1ca261d7546663865 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 3 Jul 2017 20:05:42 -0700 Subject: [PATCH 009/145] working I believe --- contrib/adaptive-compression/v2.c | 68 ++++++++++++++++++------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/v2.c index edb2d0f3..2f58e750 100644 --- a/contrib/adaptive-compression/v2.c +++ b/contrib/adaptive-compression/v2.c @@ -33,6 +33,7 @@ typedef struct { unsigned compressionLevel; unsigned numActiveThreads; unsigned numJobs; + unsigned lastJobID; unsigned nextJobID; unsigned threadError; unsigned allJobsCompleted; @@ -63,7 +64,9 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) pthread_mutex_init(&ctx->allJobsCompleted_mutex, NULL); pthread_cond_init(&ctx->allJobsCompleted_cond, NULL); ctx->numJobs = numJobs; + ctx->lastJobID = -1; /* intentional underflow */ ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); + DISPLAY("jobs %p\n", ctx->jobs); { unsigned u; for (u=0; ujobCompleted_cond); int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); + int const allJobsMutexError = pthread_mutex_destroy(&ctx->allJobsCompleted_mutex); + int const allJobsCondError = pthread_cond_destroy(&ctx->allJobsCompleted_cond); int const fileError = fclose(ctx->dstFile); freeCompressionJobs(ctx); free(ctx->jobs); - return completedMutexError | completedCondError | readyMutexError | readyCondError | fileError; + return completedMutexError | completedCondError | readyMutexError | readyCondError | fileError | allJobsMutexError | allJobsCondError; } } @@ -131,10 +136,10 @@ static void* compressionThread(void* arg) jobDescription* job = &ctx->jobs[currJob]; pthread_mutex_lock(job->jobReady_mutex); while(job->jobReady == 0) { + DISPLAY("waiting\n"); pthread_cond_wait(job->jobReady_cond, job->jobReady_mutex); } pthread_mutex_unlock(job->jobReady_mutex); - /* compress the data */ { size_t const compressedSize = ZSTD_compress(job->dst.start, job->dst.size, job->src.start, job->src.size, job->compressionLevel); @@ -145,8 +150,12 @@ static void* compressionThread(void* arg) } job->compressedSize = compressedSize; } + pthread_mutex_lock(job->jobCompleted_mutex); + job->jobCompleted = 1; + pthread_cond_signal(job->jobCompleted_cond); + pthread_mutex_unlock(job->jobCompleted_mutex); currJob++; - if (currJob >= ctx->numJobs || ctx->threadError) { + if (currJob >= ctx->lastJobID || ctx->threadError) { /* finished compressing all jobs */ break; } @@ -158,7 +167,6 @@ static void* outputThread(void* arg) { DISPLAY("started output thread\n"); adaptCCtx* ctx = (adaptCCtx*)arg; - DISPLAY("casted ctx\n"); unsigned currJob = 0; for ( ; ; ) { @@ -183,7 +191,7 @@ static void* outputThread(void* arg) } } currJob++; - if (currJob >= ctx->numJobs || ctx->threadError) { + if (currJob >= ctx->lastJobID || ctx->threadError) { /* finished with all jobs */ pthread_mutex_lock(&ctx->allJobsCompleted_mutex); ctx->allJobsCompleted = 1; @@ -220,29 +228,30 @@ static size_t getFileSize(const char* const filename) static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) { unsigned const nextJob = ctx->nextJobID; - jobDescription job = ctx->jobs[nextJob]; - job.compressionLevel = ctx->compressionLevel; - job.src.start = malloc(srcSize); - job.src.size = srcSize; - job.dst.size = ZSTD_compressBound(srcSize); - job.dst.start = malloc(job.dst.size); - job.jobCompleted = 0; - job.jobCompleted_cond = &ctx->jobCompleted_cond; - job.jobCompleted_mutex = &ctx->jobCompleted_mutex; - job.jobReady_cond = &ctx->jobReady_cond; - job.jobReady_mutex = &ctx->jobReady_mutex; - job.jobID = nextJob; - if (!job.src.start || !job.dst.start) { + jobDescription* job = &ctx->jobs[nextJob]; + job->compressionLevel = ctx->compressionLevel; + job->src.start = malloc(srcSize); + job->src.size = srcSize; + job->dst.size = ZSTD_compressBound(srcSize); + job->dst.start = malloc(job->dst.size); + job->jobCompleted = 0; + job->jobCompleted_cond = &ctx->jobCompleted_cond; + job->jobCompleted_mutex = &ctx->jobCompleted_mutex; + job->jobReady_cond = &ctx->jobReady_cond; + job->jobReady_mutex = &ctx->jobReady_mutex; + job->jobID = nextJob; + if (!job->src.start || !job->dst.start) { /* problem occurred, free things then return */ - if (job.src.start) free(job.src.start); - if (job.dst.start) free(job.dst.start); + DISPLAY("Error: problem occurred during job creation\n"); + if (job->src.start) free(job->src.start); + if (job->dst.start) free(job->dst.start); return 1; } - memcpy(job.src.start, data, srcSize); - pthread_mutex_lock(job.jobReady_mutex); - job.jobReady = 1; - pthread_cond_signal(job.jobReady_cond); - pthread_mutex_unlock(job.jobReady_mutex); + memcpy(job->src.start, data, srcSize); + pthread_mutex_lock(job->jobReady_mutex); + job->jobReady = 1; + pthread_cond_signal(job->jobReady_cond); + pthread_mutex_unlock(job->jobReady_mutex); ctx->nextJobID++; return 0; } @@ -305,14 +314,12 @@ int main(int argCount, const char* argv[]) /* creating jobs */ for ( ; ; ) { - DISPLAY("in job creation loop\n"); size_t const readSize = fread(src, 1, FILE_CHUNK_SIZE, srcFile); if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); ret = 1; goto cleanup; } - DISPLAY("reading was fine\n"); /* reading was fine, now create the compression job */ { int const error = createCompressionJob(ctx, src, readSize); @@ -321,9 +328,12 @@ int main(int argCount, const char* argv[]) goto cleanup; } } - if (feof(srcFile)) break; + if (feof(srcFile)) { + ctx->lastJobID = ctx->nextJobID; + break; + } } - + cleanup: /* file compression completed */ ret |= (srcFile != NULL) ? fclose(srcFile) : 0; From a47ebb16070af45f31407f614b1aee8f031ffc3d Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 09:23:46 -0700 Subject: [PATCH 010/145] removed print statements --- contrib/adaptive-compression/v2.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/v2.c index 2f58e750..9a050bf7 100644 --- a/contrib/adaptive-compression/v2.c +++ b/contrib/adaptive-compression/v2.c @@ -66,7 +66,6 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->numJobs = numJobs; ctx->lastJobID = -1; /* intentional underflow */ ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); - DISPLAY("jobs %p\n", ctx->jobs); { unsigned u; for (u=0; unumJobs; u++) { - DISPLAY("freeing compression job %u\n", u); - DISPLAY("%u\n", ctx->numJobs); jobDescription job = ctx->jobs[u]; if (job.dst.start) free(job.dst.start); if (job.src.start) free(job.src.start); @@ -129,14 +126,12 @@ static int freeCCtx(adaptCCtx* ctx) static void* compressionThread(void* arg) { - DISPLAY("started compression thread\n"); adaptCCtx* ctx = (adaptCCtx*)arg; unsigned currJob = 0; for ( ; ; ) { jobDescription* job = &ctx->jobs[currJob]; pthread_mutex_lock(job->jobReady_mutex); while(job->jobReady == 0) { - DISPLAY("waiting\n"); pthread_cond_wait(job->jobReady_cond, job->jobReady_mutex); } pthread_mutex_unlock(job->jobReady_mutex); @@ -165,7 +160,6 @@ static void* compressionThread(void* arg) static void* outputThread(void* arg) { - DISPLAY("started output thread\n"); adaptCCtx* ctx = (adaptCCtx*)arg; unsigned currJob = 0; From 9a147d86715da50610c905d347c88ae10756c9f5 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 09:37:52 -0700 Subject: [PATCH 011/145] removed unnecessary checks for null pointer on free --- contrib/adaptive-compression/v2.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/v2.c index 9a050bf7..df6bf570 100644 --- a/contrib/adaptive-compression/v2.c +++ b/contrib/adaptive-compression/v2.c @@ -98,8 +98,8 @@ static void freeCompressionJobs(adaptCCtx* ctx) unsigned u; for (u=0; unumJobs; u++) { jobDescription job = ctx->jobs[u]; - if (job.dst.start) free(job.dst.start); - if (job.src.start) free(job.src.start); + free(job.dst.start); + free(job.src.start); } } @@ -237,8 +237,8 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) if (!job->src.start || !job->dst.start) { /* problem occurred, free things then return */ DISPLAY("Error: problem occurred during job creation\n"); - if (job->src.start) free(job->src.start); - if (job->dst.start) free(job->dst.start); + free(job->src.start); + free(job->dst.start); return 1; } memcpy(job->src.start, data, srcSize); @@ -332,6 +332,6 @@ cleanup: /* file compression completed */ ret |= (srcFile != NULL) ? fclose(srcFile) : 0; ret |= (ctx != NULL) ? freeCCtx(ctx) : 0; - if (src != NULL) free(src); + free(src); return ret; } From c9f49198b858a89781562d07a35772712c272b8a Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 09:49:27 -0700 Subject: [PATCH 012/145] fixed TODOs --- contrib/adaptive-compression/v2.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/v2.c index df6bf570..c25f1db8 100644 --- a/contrib/adaptive-compression/v2.c +++ b/contrib/adaptive-compression/v2.c @@ -174,13 +174,13 @@ static void* outputThread(void* arg) size_t const compressedSize = job->compressedSize; if (ZSTD_isError(compressedSize)) { DISPLAY("Error: an error occurred during compression\n"); - return arg; /* TODO: return something else if error */ + return arg; } { size_t const writeSize = fwrite(ctx->jobs[currJob].dst.start, 1, compressedSize, ctx->dstFile); if (writeSize != compressedSize) { DISPLAY("Error: an error occurred during file write operation\n"); - return arg; /* TODO: return something else if error */ + return arg; } } } @@ -262,7 +262,7 @@ int main(int argCount, const char* argv[]) BYTE* const src = malloc(FILE_CHUNK_SIZE); FILE* const srcFile = fopen(srcFilename, "rb"); size_t fileSize = getFileSize(srcFilename); - size_t const numJobsPrelim = (fileSize >> 22) + 1; /* TODO: figure out why can't divide here */ + size_t const numJobsPrelim = (fileSize / ((size_t)FILE_CHUNK_SIZE)); size_t const numJobs = (numJobsPrelim * FILE_CHUNK_SIZE) == fileSize ? numJobsPrelim : numJobsPrelim + 1; int ret = 0; adaptCCtx* ctx = NULL; From 5df4cb053029b4b4fcb5c90f8ee831ae423623c2 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 09:57:50 -0700 Subject: [PATCH 013/145] renamed files --- contrib/adaptive-compression/Makefile | 8 ++++---- contrib/adaptive-compression/{v2.c => multi.c} | 4 ++-- contrib/adaptive-compression/run.sh | 8 ++++++-- contrib/adaptive-compression/{v1.c => single.c} | 0 4 files changed, 12 insertions(+), 8 deletions(-) rename contrib/adaptive-compression/{v2.c => multi.c} (99%) rename contrib/adaptive-compression/{v1.c => single.c} (100%) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index fbab219c..c6862198 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -18,15 +18,15 @@ CFLAGS += $(DEBUGFLAGS) CFLAGS += $(MOREFLAGS) FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -all: clean v1 v2 -v1: $(ZSTD_FILES) v1.c +all: clean single multi +single: $(ZSTD_FILES) single.c $(CC) $(FLAGS) $^ -o $@ -v2: $(ZSTD_FILES) v2.c +multi: $(ZSTD_FILES) multi.c $(CC) $(FLAGS) $^ -o $@ clean: - @$(RM) -f v1 v2 + @$(RM) -f single multi @$(RM) -rf *.dSYM @$(RM) -f tmp* @echo "finished cleaning" diff --git a/contrib/adaptive-compression/v2.c b/contrib/adaptive-compression/multi.c similarity index 99% rename from contrib/adaptive-compression/v2.c rename to contrib/adaptive-compression/multi.c index c25f1db8..e1fa3177 100644 --- a/contrib/adaptive-compression/v2.c +++ b/contrib/adaptive-compression/multi.c @@ -174,7 +174,7 @@ static void* outputThread(void* arg) size_t const compressedSize = job->compressedSize; if (ZSTD_isError(compressedSize)) { DISPLAY("Error: an error occurred during compression\n"); - return arg; + return arg; } { size_t const writeSize = fwrite(ctx->jobs[currJob].dst.start, 1, compressedSize, ctx->dstFile); @@ -253,7 +253,7 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) { - if (argCount < 2) { + if (argCount < 3) { DISPLAY("Error: not enough arguments\n"); return 1; } diff --git a/contrib/adaptive-compression/run.sh b/contrib/adaptive-compression/run.sh index f9e27673..b9c4caf7 100755 --- a/contrib/adaptive-compression/run.sh +++ b/contrib/adaptive-compression/run.sh @@ -1,2 +1,6 @@ -make clean v2 -./v2 tests/test2048.pdf tmp.zst +make clean multi +./multi tests/test2048.pdf tmp.zst +zstd -d tmp.zst +diff tmp tests/test2048.pdf +echo "diff test complete" +make clean diff --git a/contrib/adaptive-compression/v1.c b/contrib/adaptive-compression/single.c similarity index 100% rename from contrib/adaptive-compression/v1.c rename to contrib/adaptive-compression/single.c From 9ccd55f3a8957eca5d580b669b4c69db349fff06 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 10:20:56 -0700 Subject: [PATCH 014/145] free ctx fields when error occurs during creation --- contrib/adaptive-compression/multi.c | 57 +++++++++++++++++----------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index e1fa3177..a461dca7 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -47,6 +47,34 @@ typedef struct { FILE* dstFile; } adaptCCtx; +static void freeCompressionJobs(adaptCCtx* ctx) +{ + unsigned u; + for (u=0; unumJobs; u++) { + jobDescription job = ctx->jobs[u]; + free(job.dst.start); + free(job.src.start); + } +} + +static int freeCCtx(adaptCCtx* ctx) +{ + { + int const completedMutexError = pthread_mutex_destroy(&ctx->jobCompleted_mutex); + int const completedCondError = pthread_cond_destroy(&ctx->jobCompleted_cond); + int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); + int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); + int const allJobsMutexError = pthread_mutex_destroy(&ctx->allJobsCompleted_mutex); + int const allJobsCondError = pthread_cond_destroy(&ctx->allJobsCompleted_cond); + int const fileCloseError = ctx->dstFile != NULL ? fclose(ctx->dstFile) : 0; + if (ctx->jobs){ + freeCompressionJobs(ctx); + free(ctx->jobs); + } + return completedMutexError | completedCondError | readyMutexError | readyCondError | fileCloseError | allJobsMutexError | allJobsCondError; + } +} + static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) { @@ -80,12 +108,14 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->allJobsCompleted = 0; if (!ctx->jobs) { DISPLAY("Error: could not allocate space for jobs during context creation\n"); + freeCCtx(ctx); return NULL; } { FILE* dstFile = fopen(outFilename, "wb"); if (dstFile == NULL) { DISPLAY("Error: could not open output file\n"); + freeCCtx(ctx); return NULL; } ctx->dstFile = dstFile; @@ -93,35 +123,15 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) return ctx; } -static void freeCompressionJobs(adaptCCtx* ctx) -{ - unsigned u; - for (u=0; unumJobs; u++) { - jobDescription job = ctx->jobs[u]; - free(job.dst.start); - free(job.src.start); - } -} -static int freeCCtx(adaptCCtx* ctx) + +static void waitUntilAllJobsCompleted(adaptCCtx* ctx) { pthread_mutex_lock(&ctx->allJobsCompleted_mutex); while (ctx->allJobsCompleted == 0) { pthread_cond_wait(&ctx->allJobsCompleted_cond, &ctx->allJobsCompleted_mutex); } pthread_mutex_unlock(&ctx->allJobsCompleted_mutex); - { - int const completedMutexError = pthread_mutex_destroy(&ctx->jobCompleted_mutex); - int const completedCondError = pthread_cond_destroy(&ctx->jobCompleted_cond); - int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); - int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); - int const allJobsMutexError = pthread_mutex_destroy(&ctx->allJobsCompleted_mutex); - int const allJobsCondError = pthread_cond_destroy(&ctx->allJobsCompleted_cond); - int const fileError = fclose(ctx->dstFile); - freeCompressionJobs(ctx); - free(ctx->jobs); - return completedMutexError | completedCondError | readyMutexError | readyCondError | fileError | allJobsMutexError | allJobsCondError; - } } static void* compressionThread(void* arg) @@ -140,7 +150,7 @@ static void* compressionThread(void* arg) size_t const compressedSize = ZSTD_compress(job->dst.start, job->dst.size, job->src.start, job->src.size, job->compressionLevel); if (ZSTD_isError(compressedSize)) { ctx->threadError = 1; - DISPLAY("Error: somethign went wrong during compression\n"); + DISPLAY("Error: something went wrong during compression: %s\n", ZSTD_getErrorName(compressedSize)); return arg; } job->compressedSize = compressedSize; @@ -329,6 +339,7 @@ int main(int argCount, const char* argv[]) } cleanup: + waitUntilAllJobsCompleted(ctx); /* file compression completed */ ret |= (srcFile != NULL) ? fclose(srcFile) : 0; ret |= (ctx != NULL) ? freeCCtx(ctx) : 0; From dd8a591d5d74d50f49ea34088e401ed8eadd78a0 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 10:48:04 -0700 Subject: [PATCH 015/145] moved main logic for job creation into a separate function --- contrib/adaptive-compression/multi.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index a461dca7..b89d0c23 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -260,15 +260,8 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) return 0; } -/* return 0 if successful, else return error */ -int main(int argCount, const char* argv[]) +static int compressFilename(const char* const srcFilename, const char* const dstFilename) { - if (argCount < 3) { - DISPLAY("Error: not enough arguments\n"); - return 1; - } - const char* const srcFilename = argv[1]; - const char* const dstFilename = argv[2]; BYTE* const src = malloc(FILE_CHUNK_SIZE); FILE* const srcFile = fopen(srcFilename, "rb"); size_t fileSize = getFileSize(srcFilename); @@ -346,3 +339,13 @@ cleanup: free(src); return ret; } + +/* return 0 if successful, else return error */ +int main(int argCount, const char* argv[]) +{ + if (argCount < 3) { + DISPLAY("Error: not enough arguments\n"); + return 1; + } + return compressFilename(argv[1], argv[2]); +} From a2680e5b9605941b2b9d250f3a7915fa7f48d5b8 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 11:52:55 -0700 Subject: [PATCH 016/145] removed calculation of file size and replaced with limited number of available jobs --- contrib/adaptive-compression/multi.c | 39 +++++++++++++++++++++++----- contrib/adaptive-compression/run.sh | 33 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index b89d0c23..a60f48ec 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -1,5 +1,6 @@ #define DISPLAY(...) fprintf(stderr, __VA_ARGS__) #define FILE_CHUNK_SIZE 4 << 20 +#define MAX_NUM_JOBS 30; typedef unsigned char BYTE; #include /* fprintf */ @@ -22,10 +23,13 @@ typedef struct { unsigned jobID; unsigned jobCompleted; unsigned jobReady; + unsigned jobWritten; pthread_mutex_t* jobCompleted_mutex; pthread_cond_t* jobCompleted_cond; pthread_mutex_t* jobReady_mutex; pthread_cond_t* jobReady_cond; + pthread_mutex_t* jobWrite_mutex; + pthread_cond_t* jobWrite_cond; size_t compressedSize; } jobDescription; @@ -43,6 +47,8 @@ typedef struct { pthread_cond_t jobReady_cond; pthread_mutex_t allJobsCompleted_mutex; pthread_cond_t allJobsCompleted_cond; + pthread_mutex_t jobWrite_mutex; + pthread_cond_t jobWrite_cond; jobDescription* jobs; FILE* dstFile; } adaptCCtx; @@ -66,12 +72,14 @@ static int freeCCtx(adaptCCtx* ctx) int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); int const allJobsMutexError = pthread_mutex_destroy(&ctx->allJobsCompleted_mutex); int const allJobsCondError = pthread_cond_destroy(&ctx->allJobsCompleted_cond); + int const jobWriteMutexError = pthread_mutex_destroy(&ctx->jobWrite_mutex); + int const jobWriteCondError = pthread_cond_destroy(&ctx->jobWrite_cond); int const fileCloseError = ctx->dstFile != NULL ? fclose(ctx->dstFile) : 0; if (ctx->jobs){ freeCompressionJobs(ctx); free(ctx->jobs); } - return completedMutexError | completedCondError | readyMutexError | readyCondError | fileCloseError | allJobsMutexError | allJobsCondError; + return completedMutexError | completedCondError | readyMutexError | readyCondError | fileCloseError | allJobsMutexError | allJobsCondError | jobWriteMutexError | jobWriteCondError; } } @@ -91,6 +99,8 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) pthread_cond_init(&ctx->jobReady_cond, NULL); pthread_mutex_init(&ctx->allJobsCompleted_mutex, NULL); pthread_cond_init(&ctx->allJobsCompleted_cond, NULL); + pthread_mutex_init(&ctx->jobWrite_mutex, NULL); + pthread_cond_init(&ctx->jobWrite_cond, NULL); ctx->numJobs = numJobs; ctx->lastJobID = -1; /* intentional underflow */ ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); @@ -101,6 +111,9 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->jobs[u].jobCompleted_cond = &ctx->jobCompleted_cond; ctx->jobs[u].jobReady_mutex = &ctx->jobReady_mutex; ctx->jobs[u].jobReady_cond = &ctx->jobReady_cond; + ctx->jobs[u].jobWrite_mutex = &ctx->jobWrite_mutex; + ctx->jobs[u].jobWrite_cond = &ctx->jobWrite_cond; + ctx->jobs[u].jobWritten = 1; } } ctx->nextJobID = 0; @@ -139,7 +152,8 @@ static void* compressionThread(void* arg) adaptCCtx* ctx = (adaptCCtx*)arg; unsigned currJob = 0; for ( ; ; ) { - jobDescription* job = &ctx->jobs[currJob]; + unsigned const currJobIndex = currJob % ctx->numJobs; + jobDescription* job = &ctx->jobs[currJobIndex]; pthread_mutex_lock(job->jobReady_mutex); while(job->jobReady == 0) { pthread_cond_wait(job->jobReady_cond, job->jobReady_mutex); @@ -174,7 +188,8 @@ static void* outputThread(void* arg) unsigned currJob = 0; for ( ; ; ) { - jobDescription* job = &ctx->jobs[currJob]; + unsigned const currJobIndex = currJob % ctx->numJobs; + jobDescription* job = &ctx->jobs[currJobIndex]; pthread_mutex_lock(job->jobCompleted_mutex); while (job->jobCompleted == 0) { pthread_cond_wait(job->jobCompleted_cond, job->jobCompleted_mutex); @@ -187,7 +202,7 @@ static void* outputThread(void* arg) return arg; } { - size_t const writeSize = fwrite(ctx->jobs[currJob].dst.start, 1, compressedSize, ctx->dstFile); + size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, ctx->dstFile); if (writeSize != compressedSize) { DISPLAY("Error: an error occurred during file write operation\n"); return arg; @@ -195,6 +210,10 @@ static void* outputThread(void* arg) } } currJob++; + pthread_mutex_lock(job->jobWrite_mutex); + job->jobWritten = 1; + pthread_cond_signal(job->jobWrite_cond); + pthread_mutex_unlock(job->jobWrite_mutex); if (currJob >= ctx->lastJobID || ctx->threadError) { /* finished with all jobs */ pthread_mutex_lock(&ctx->allJobsCompleted_mutex); @@ -232,13 +251,20 @@ static size_t getFileSize(const char* const filename) static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) { unsigned const nextJob = ctx->nextJobID; - jobDescription* job = &ctx->jobs[nextJob]; + unsigned const nextJobIndex = nextJob % ctx->numJobs; + jobDescription* job = &ctx->jobs[nextJobIndex]; + pthread_mutex_lock(job->jobWrite_mutex); + while (job->jobWritten == 0) { + pthread_cond_wait(job->jobWrite_cond, job->jobWrite_mutex); + } + pthread_mutex_unlock(job->jobWrite_mutex); job->compressionLevel = ctx->compressionLevel; job->src.start = malloc(srcSize); job->src.size = srcSize; job->dst.size = ZSTD_compressBound(srcSize); job->dst.start = malloc(job->dst.size); job->jobCompleted = 0; + job->jobWritten = 0; job->jobCompleted_cond = &ctx->jobCompleted_cond; job->jobCompleted_mutex = &ctx->jobCompleted_mutex; job->jobReady_cond = &ctx->jobReady_cond; @@ -265,8 +291,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst BYTE* const src = malloc(FILE_CHUNK_SIZE); FILE* const srcFile = fopen(srcFilename, "rb"); size_t fileSize = getFileSize(srcFilename); - size_t const numJobsPrelim = (fileSize / ((size_t)FILE_CHUNK_SIZE)); - size_t const numJobs = (numJobsPrelim * FILE_CHUNK_SIZE) == fileSize ? numJobsPrelim : numJobsPrelim + 1; + size_t const numJobs = MAX_NUM_JOBS; int ret = 0; adaptCCtx* ctx = NULL; diff --git a/contrib/adaptive-compression/run.sh b/contrib/adaptive-compression/run.sh index b9c4caf7..9d2e9870 100755 --- a/contrib/adaptive-compression/run.sh +++ b/contrib/adaptive-compression/run.sh @@ -1,6 +1,39 @@ make clean multi + ./multi tests/test2048.pdf tmp.zst zstd -d tmp.zst diff tmp tests/test2048.pdf echo "diff test complete" +rm tmp* + +./multi tests/test512.pdf tmp.zst +zstd -d tmp.zst +diff tmp tests/test512.pdf +echo "diff test complete" +rm tmp* + +./multi tests/test64.pdf tmp.zst +zstd -d tmp.zst +diff tmp tests/test64.pdf +echo "diff test complete" +rm tmp* + +./multi tests/test16.pdf tmp.zst +zstd -d tmp.zst +diff tmp tests/test16.pdf +echo "diff test complete" +rm tmp* + +./multi tests/test4.pdf tmp.zst +zstd -d tmp.zst +diff tmp tests/test4.pdf +echo "diff test complete" +rm tmp* + +./multi tests/test.pdf tmp.zst +zstd -d tmp.zst +diff tmp tests/test.pdf +echo "diff test complete" +rm tmp* + make clean From 898c1a5b467047637f9ebb8cbe6b525bdee987d5 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 11:54:21 -0700 Subject: [PATCH 017/145] removed references to file size computation and file size function --- contrib/adaptive-compression/multi.c | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index a60f48ec..79904551 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -226,28 +226,6 @@ static void* outputThread(void* arg) return arg; } - -static size_t getFileSize(const char* const filename) -{ - FILE* fd = fopen(filename, "rb"); - if (fd == NULL) { - DISPLAY("Error: could not open file in order to get file size\n"); - return -1; /* intentional underflow */ - } - if (fseek(fd, 0, SEEK_END) != 0) { - DISPLAY("Error: fseek failed during file size computation\n"); - return -1; - } - { - size_t const fileSize = ftell(fd); - if (fclose(fd) != 0) { - DISPLAY("Error: could not close file during file size computation\n"); - return -1; - } - return fileSize; - } -} - static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) { unsigned const nextJob = ctx->nextJobID; @@ -290,17 +268,12 @@ static int compressFilename(const char* const srcFilename, const char* const dst { BYTE* const src = malloc(FILE_CHUNK_SIZE); FILE* const srcFile = fopen(srcFilename, "rb"); - size_t fileSize = getFileSize(srcFilename); size_t const numJobs = MAX_NUM_JOBS; int ret = 0; adaptCCtx* ctx = NULL; /* checking for errors */ - if (fileSize == (size_t)(-1)) { - ret = 1; - goto cleanup; - } if (!srcFilename || !dstFilename || !src || !srcFile) { DISPLAY("Error: initial variables could not be allocated\n"); ret = 1; From b42108386a5eaccafbc5b8d15e3ac71e4c548a9a Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 12:20:16 -0700 Subject: [PATCH 018/145] added some basic parsing for args --- contrib/adaptive-compression/multi.c | 35 +++++++++++++++++++++++----- contrib/adaptive-compression/run.sh | 12 +++++----- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 79904551..4eb3d3b1 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -1,6 +1,8 @@ #define DISPLAY(...) fprintf(stderr, __VA_ARGS__) #define FILE_CHUNK_SIZE 4 << 20 #define MAX_NUM_JOBS 30; +#define stdinmark "/*stdin*\\" +#define stdoutmark "/*stdout*\\" typedef unsigned char BYTE; #include /* fprintf */ @@ -125,7 +127,8 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) return NULL; } { - FILE* dstFile = fopen(outFilename, "wb"); + unsigned const stdoutUsed = !strcmp(outFilename, stdoutmark); + FILE* dstFile = stdoutUsed ? stdout : fopen(outFilename, "wb"); if (dstFile == NULL) { DISPLAY("Error: could not open output file\n"); freeCCtx(ctx); @@ -267,7 +270,8 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) static int compressFilename(const char* const srcFilename, const char* const dstFilename) { BYTE* const src = malloc(FILE_CHUNK_SIZE); - FILE* const srcFile = fopen(srcFilename, "rb"); + unsigned const stdinUsed = !strcmp(srcFilename, stdinmark); + FILE* const srcFile = stdinUsed ? stdin : fopen(srcFilename, "rb"); size_t const numJobs = MAX_NUM_JOBS; int ret = 0; adaptCCtx* ctx = NULL; @@ -341,9 +345,28 @@ cleanup: /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) { - if (argCount < 3) { - DISPLAY("Error: not enough arguments\n"); - return 1; + const char* inFilename = stdinmark; + const char* outFilename = stdoutmark; + unsigned nextArgumentIsOutFilename = 0; + int argNum; + for (argNum=1; argNum Date: Wed, 5 Jul 2017 13:23:34 -0700 Subject: [PATCH 019/145] added tests to run.sh --- contrib/adaptive-compression/run.sh | 52 +++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/contrib/adaptive-compression/run.sh b/contrib/adaptive-compression/run.sh index ec2df038..e6cb2066 100755 --- a/contrib/adaptive-compression/run.sh +++ b/contrib/adaptive-compression/run.sh @@ -1,39 +1,79 @@ make clean multi +echo "running file tests" + ./multi tests/test2048.pdf -otmp.zst zstd -d tmp.zst diff tmp tests/test2048.pdf -echo "diff test complete" +echo "diff test complete: test2048.pdf" rm tmp* ./multi tests/test512.pdf -otmp.zst zstd -d tmp.zst diff tmp tests/test512.pdf -echo "diff test complete" +echo "diff test complete: test512.pdf" rm tmp* ./multi tests/test64.pdf -otmp.zst zstd -d tmp.zst diff tmp tests/test64.pdf -echo "diff test complete" +echo "diff test complete: test64.pdf" rm tmp* ./multi tests/test16.pdf -otmp.zst zstd -d tmp.zst diff tmp tests/test16.pdf -echo "diff test complete" +echo "diff test complete: test16.pdf" rm tmp* ./multi tests/test4.pdf -otmp.zst zstd -d tmp.zst diff tmp tests/test4.pdf -echo "diff test complete" +echo "diff test complete: test4.pdf" rm tmp* ./multi tests/test.pdf -otmp.zst zstd -d tmp.zst diff tmp tests/test.pdf -echo "diff test complete" +echo "diff test complete: test.pdf" +rm tmp* + +echo "Running std input/output tests" + +cat tests/test2048.pdf | ./multi -otmp.zst +zstd -d tmp.zst +diff tmp tests/test2048.pdf +echo "diff test complete: test2048.pdf" +rm tmp* + +cat tests/test512.pdf | ./multi -otmp.zst +zstd -d tmp.zst +diff tmp tests/test512.pdf +echo "diff test complete: test512.pdf" +rm tmp* + +cat tests/test64.pdf | ./multi -otmp.zst +zstd -d tmp.zst +diff tmp tests/test64.pdf +echo "diff test complete: test64.pdf" +rm tmp* + +cat tests/test16.pdf | ./multi -otmp.zst +zstd -d tmp.zst +diff tmp tests/test16.pdf +echo "diff test complete: test16.pdf" +rm tmp* + +cat tests/test4.pdf | ./multi -otmp.zst +zstd -d tmp.zst +diff tmp tests/test4.pdf +echo "diff test complete: test4.pdf" +rm tmp* + +cat tests/test.pdf | ./multi -otmp.zst +zstd -d tmp.zst +diff tmp tests/test.pdf +echo "diff test complete: test.pdf" rm tmp* make clean From faeb6e0b1b534c5f73a9282f49cf1209a1417148 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 14:19:56 -0700 Subject: [PATCH 020/145] added filenameTable for multiple files --- contrib/adaptive-compression/multi.c | 75 ++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 4eb3d3b1..a6fd64ce 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -3,6 +3,7 @@ #define MAX_NUM_JOBS 30; #define stdinmark "/*stdin*\\" #define stdoutmark "/*stdout*\\" +#define MAX_PATH 256 typedef unsigned char BYTE; #include /* fprintf */ @@ -342,31 +343,73 @@ cleanup: return ret; } +static int compressFilenames(const char** filenameTable, unsigned numFiles) +{ + int ret = 0; + unsigned fileNum; + char outFile[MAX_PATH]; + for (fileNum=0; fileNum MAX_PATH) { + DISPLAY("Error: output filename is too long\n"); + return 1; + } + ret |= compressFilename(filename, outFile); + } + return ret; +} + /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) { - const char* inFilename = stdinmark; - const char* outFilename = stdoutmark; - unsigned nextArgumentIsOutFilename = 0; + const char* outFilename = NULL; + const char** filenameTable = (const char**)malloc(argCount*sizeof(const char*)); + unsigned filenameIdx = 0; + filenameTable[0] = stdinmark; + int ret = 0; int argNum; + + if (filenameTable == NULL) { + DISPLAY("Error: could not allocate sapce for filename table.\n"); + return 1; + } + for (argNum=1; argNum 1 && argument[1] == 'o') { + argument += 2; + outFilename = argument; + continue; + } + else { + DISPLAY("Error: invalid argument provided\n"); + ret = 1; + goto _main_exit; } } - if (nextArgumentIsOutFilename) { - outFilename = argument; - } - else { - inFilename = argument; - } + /* regular files to be compressed */ + filenameTable[filenameIdx++] = argument; } - return compressFilename(inFilename, outFilename); + + /* error checking with number of files */ + if (filenameIdx > 1 && outFilename != NULL) { + DISPLAY("Error: multiple input files provided, cannot use specified output file\n"); + ret = 1; + goto _main_exit; + } + + /* compress files */ + if (filenameIdx <= 1) { + ret |= compressFilename(filenameTable[0], outFilename); + } + else { + ret |= compressFilenames(filenameTable, filenameIdx); + } +_main_exit: + free(filenameTable); + return ret; } From 3f52ca94bf8bef4c3378f440e18421245ac8e675 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 14:36:09 -0700 Subject: [PATCH 021/145] added more tests, changed makefile --- contrib/adaptive-compression/Makefile | 2 ++ contrib/adaptive-compression/multi.c | 4 ++-- contrib/adaptive-compression/run.sh | 30 +++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index c6862198..2ebec9ba 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -29,4 +29,6 @@ clean: @$(RM) -f single multi @$(RM) -rf *.dSYM @$(RM) -f tmp* + @$(RM) -f tests/*.zst + @$(RM) -f tests/tmp* @echo "finished cleaning" diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index a6fd64ce..089dfe5a 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -1,6 +1,6 @@ #define DISPLAY(...) fprintf(stderr, __VA_ARGS__) #define FILE_CHUNK_SIZE 4 << 20 -#define MAX_NUM_JOBS 30; +#define MAX_NUM_JOBS 50; #define stdinmark "/*stdin*\\" #define stdoutmark "/*stdout*\\" #define MAX_PATH 256 @@ -96,7 +96,7 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) } memset(ctx, 0, sizeof(adaptCCtx)); ctx->compressionLevel = 6; /* default */ - pthread_mutex_init(&ctx->jobCompleted_mutex, NULL); + pthread_mutex_init(&ctx->jobCompleted_mutex, NULL); /* TODO: add checks for errors on each mutex */ pthread_cond_init(&ctx->jobCompleted_cond, NULL); pthread_mutex_init(&ctx->jobReady_mutex, NULL); pthread_cond_init(&ctx->jobReady_cond, NULL); diff --git a/contrib/adaptive-compression/run.sh b/contrib/adaptive-compression/run.sh index e6cb2066..b906ae7d 100755 --- a/contrib/adaptive-compression/run.sh +++ b/contrib/adaptive-compression/run.sh @@ -76,4 +76,34 @@ diff tmp tests/test.pdf echo "diff test complete: test.pdf" rm tmp* +echo "Running multi-file tests" +./multi tests/* +zstd -d tests/test.pdf.zst -o tests/tmp +zstd -d tests/test2.pdf.zst -o tests/tmp2 +zstd -d tests/test4.pdf.zst -o tests/tmp4 +zstd -d tests/test8.pdf.zst -o tests/tmp8 +zstd -d tests/test16.pdf.zst -o tests/tmp16 +zstd -d tests/test32.pdf.zst -o tests/tmp32 +zstd -d tests/test64.pdf.zst -o tests/tmp64 +zstd -d tests/test128.pdf.zst -o tests/tmp128 +zstd -d tests/test256.pdf.zst -o tests/tmp256 +zstd -d tests/test512.pdf.zst -o tests/tmp512 +zstd -d tests/test1024.pdf.zst -o tests/tmp1024 +zstd -d tests/test2048.pdf.zst -o tests/tmp2048 + +diff tests/test.pdf tests/tmp +diff tests/test2.pdf tests/tmp2 +diff tests/test4.pdf tests/tmp4 +diff tests/test8.pdf tests/tmp8 +diff tests/test16.pdf tests/tmp16 +diff tests/test32.pdf tests/tmp32 +diff tests/test64.pdf tests/tmp64 +diff tests/test128.pdf tests/tmp128 +diff tests/test256.pdf tests/tmp256 +diff tests/test512.pdf tests/tmp512 +diff tests/test1024.pdf tests/tmp1024 +diff tests/test2048.pdf tests/tmp2048 + +echo "finished with tests" + make clean From cc714f3bd3346fe62530ef0ae3c8179e4ef6423c Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 16:54:34 -0700 Subject: [PATCH 022/145] added print statements and debuglog --- contrib/adaptive-compression/multi.c | 23 ++++++++++++++++++++++- contrib/adaptive-compression/pipetests.sh | 2 ++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100755 contrib/adaptive-compression/pipetests.sh diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 089dfe5a..40045381 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -1,9 +1,11 @@ #define DISPLAY(...) fprintf(stderr, __VA_ARGS__) +#define DEBUGLOG(l, ...) { if (g_displayLevel>=l) { DISPLAY(__VA_ARGS__); } } #define FILE_CHUNK_SIZE 4 << 20 #define MAX_NUM_JOBS 50; #define stdinmark "/*stdin*\\" #define stdoutmark "/*stdout*\\" #define MAX_PATH 256 +#define DEFAULT_DISPLAY_LEVEL 1 typedef unsigned char BYTE; #include /* fprintf */ @@ -12,7 +14,7 @@ typedef unsigned char BYTE; #include /* memset */ #include "zstd.h" - +static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; typedef struct { void* start; @@ -158,11 +160,13 @@ static void* compressionThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; + // DEBUGLOG(2, "compressionThread(): waiting on job ready\n"); pthread_mutex_lock(job->jobReady_mutex); while(job->jobReady == 0) { pthread_cond_wait(job->jobReady_cond, job->jobReady_mutex); } pthread_mutex_unlock(job->jobReady_mutex); + // DEBUGLOG(2, "compressionThread(): continuing after job ready\n"); /* compress the data */ { size_t const compressedSize = ZSTD_compress(job->dst.start, job->dst.size, job->src.start, job->src.size, job->compressionLevel); @@ -175,11 +179,13 @@ static void* compressionThread(void* arg) } pthread_mutex_lock(job->jobCompleted_mutex); job->jobCompleted = 1; + DEBUGLOG(2, "signaling for job %u\n", currJob); pthread_cond_signal(job->jobCompleted_cond); pthread_mutex_unlock(job->jobCompleted_mutex); currJob++; if (currJob >= ctx->lastJobID || ctx->threadError) { /* finished compressing all jobs */ + DEBUGLOG(2, "all jobs finished compressing\n"); break; } } @@ -194,11 +200,14 @@ static void* outputThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; + DEBUGLOG(2, "outputThread(): waiting on job completed\n"); pthread_mutex_lock(job->jobCompleted_mutex); while (job->jobCompleted == 0) { + DEBUGLOG(2, "inside job completed wait loop waiting on %u\n", currJob); pthread_cond_wait(job->jobCompleted_cond, job->jobCompleted_mutex); } pthread_mutex_unlock(job->jobCompleted_mutex); + DEBUGLOG(2, "outputThread(): continuing after job completed\n"); { size_t const compressedSize = job->compressedSize; if (ZSTD_isError(compressedSize)) { @@ -214,12 +223,17 @@ static void* outputThread(void* arg) } } currJob++; + DEBUGLOG(2, "locking job write mutex\n"); pthread_mutex_lock(job->jobWrite_mutex); job->jobWritten = 1; pthread_cond_signal(job->jobWrite_cond); pthread_mutex_unlock(job->jobWrite_mutex); + DEBUGLOG(2, "unlocking job write mutex\n"); + + DEBUGLOG(2, "checking if done: %u/%u\n", currJob, ctx->lastJobID); if (currJob >= ctx->lastJobID || ctx->threadError) { /* finished with all jobs */ + DEBUGLOG(2, "all jobs finished writing\n"); pthread_mutex_lock(&ctx->allJobsCompleted_mutex); ctx->allJobsCompleted = 1; pthread_cond_signal(&ctx->allJobsCompleted_cond); @@ -235,11 +249,13 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) unsigned const nextJob = ctx->nextJobID; unsigned const nextJobIndex = nextJob % ctx->numJobs; jobDescription* job = &ctx->jobs[nextJobIndex]; + // DEBUGLOG(2, "createCompressionJob(): wait for job write\n"); pthread_mutex_lock(job->jobWrite_mutex); while (job->jobWritten == 0) { pthread_cond_wait(job->jobWrite_cond, job->jobWrite_mutex); } pthread_mutex_unlock(job->jobWrite_mutex); + // DEBUGLOG(2, "createCompressionJob(): continuing after job write\n"); job->compressionLevel = ctx->compressionLevel; job->src.start = malloc(srcSize); job->src.size = srcSize; @@ -329,6 +345,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst } } if (feof(srcFile)) { + DEBUGLOG(2, "THE STREAM OF DATA ENDED %u\n", ctx->nextJobID); ctx->lastJobID = ctx->nextJobID; break; } @@ -384,6 +401,10 @@ int main(int argCount, const char* argv[]) outFilename = argument; continue; } + else if (strlen(argument) > 1 && argument[1] == 'v') { + g_displayLevel++; + continue; + } else { DISPLAY("Error: invalid argument provided\n"); ret = 1; diff --git a/contrib/adaptive-compression/pipetests.sh b/contrib/adaptive-compression/pipetests.sh new file mode 100755 index 00000000..2924cd5a --- /dev/null +++ b/contrib/adaptive-compression/pipetests.sh @@ -0,0 +1,2 @@ +make clean multi +pv -q -L 50m tests/test2048.pdf | ./multi -v -otmp.zst From 6f3ad1b22e034cf27f34d63838c6f7cdfe55b015 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 17:24:21 -0700 Subject: [PATCH 023/145] fixed the problem with pipeline tests by changing how jobs move through the threads --- contrib/adaptive-compression/multi.c | 81 +++++++++++----------------- 1 file changed, 30 insertions(+), 51 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 40045381..fd04a53b 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -26,15 +26,6 @@ typedef struct { buffer_t dst; unsigned compressionLevel; unsigned jobID; - unsigned jobCompleted; - unsigned jobReady; - unsigned jobWritten; - pthread_mutex_t* jobCompleted_mutex; - pthread_cond_t* jobCompleted_cond; - pthread_mutex_t* jobReady_mutex; - pthread_cond_t* jobReady_cond; - pthread_mutex_t* jobWrite_mutex; - pthread_cond_t* jobWrite_cond; size_t compressedSize; } jobDescription; @@ -45,6 +36,9 @@ typedef struct { unsigned lastJobID; unsigned nextJobID; unsigned threadError; + unsigned jobReadyID; + unsigned jobCompletedID; + unsigned jobWrittenID; unsigned allJobsCompleted; pthread_mutex_t jobCompleted_mutex; pthread_cond_t jobCompleted_cond; @@ -107,20 +101,11 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) pthread_mutex_init(&ctx->jobWrite_mutex, NULL); pthread_cond_init(&ctx->jobWrite_cond, NULL); ctx->numJobs = numJobs; + ctx->jobReadyID = 0; + ctx->jobCompletedID = 0; + ctx->jobWrittenID = 0; ctx->lastJobID = -1; /* intentional underflow */ ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); - { - unsigned u; - for (u=0; ujobs[u].jobCompleted_mutex = &ctx->jobCompleted_mutex; - ctx->jobs[u].jobCompleted_cond = &ctx->jobCompleted_cond; - ctx->jobs[u].jobReady_mutex = &ctx->jobReady_mutex; - ctx->jobs[u].jobReady_cond = &ctx->jobReady_cond; - ctx->jobs[u].jobWrite_mutex = &ctx->jobWrite_mutex; - ctx->jobs[u].jobWrite_cond = &ctx->jobWrite_cond; - ctx->jobs[u].jobWritten = 1; - } - } ctx->nextJobID = 0; ctx->threadError = 0; ctx->allJobsCompleted = 0; @@ -161,11 +146,11 @@ static void* compressionThread(void* arg) unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; // DEBUGLOG(2, "compressionThread(): waiting on job ready\n"); - pthread_mutex_lock(job->jobReady_mutex); - while(job->jobReady == 0) { - pthread_cond_wait(job->jobReady_cond, job->jobReady_mutex); + pthread_mutex_lock(&ctx->jobReady_mutex); + while(currJob + 1 > ctx->jobReadyID) { + pthread_cond_wait(&ctx->jobReady_cond, &ctx->jobReady_mutex); } - pthread_mutex_unlock(job->jobReady_mutex); + pthread_mutex_unlock(&ctx->jobReady_mutex); // DEBUGLOG(2, "compressionThread(): continuing after job ready\n"); /* compress the data */ { @@ -177,11 +162,11 @@ static void* compressionThread(void* arg) } job->compressedSize = compressedSize; } - pthread_mutex_lock(job->jobCompleted_mutex); - job->jobCompleted = 1; + pthread_mutex_lock(&ctx->jobCompleted_mutex); + ctx->jobCompletedID++; DEBUGLOG(2, "signaling for job %u\n", currJob); - pthread_cond_signal(job->jobCompleted_cond); - pthread_mutex_unlock(job->jobCompleted_mutex); + pthread_cond_signal(&ctx->jobCompleted_cond); + pthread_mutex_unlock(&ctx->jobCompleted_mutex); currJob++; if (currJob >= ctx->lastJobID || ctx->threadError) { /* finished compressing all jobs */ @@ -201,12 +186,12 @@ static void* outputThread(void* arg) unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; DEBUGLOG(2, "outputThread(): waiting on job completed\n"); - pthread_mutex_lock(job->jobCompleted_mutex); - while (job->jobCompleted == 0) { + pthread_mutex_lock(&ctx->jobCompleted_mutex); + while (currJob + 1 > ctx->jobCompletedID) { DEBUGLOG(2, "inside job completed wait loop waiting on %u\n", currJob); - pthread_cond_wait(job->jobCompleted_cond, job->jobCompleted_mutex); + pthread_cond_wait(&ctx->jobCompleted_cond, &ctx->jobCompleted_mutex); } - pthread_mutex_unlock(job->jobCompleted_mutex); + pthread_mutex_unlock(&ctx->jobCompleted_mutex); DEBUGLOG(2, "outputThread(): continuing after job completed\n"); { size_t const compressedSize = job->compressedSize; @@ -224,10 +209,10 @@ static void* outputThread(void* arg) } currJob++; DEBUGLOG(2, "locking job write mutex\n"); - pthread_mutex_lock(job->jobWrite_mutex); - job->jobWritten = 1; - pthread_cond_signal(job->jobWrite_cond); - pthread_mutex_unlock(job->jobWrite_mutex); + pthread_mutex_lock(&ctx->jobWrite_mutex); + ctx->jobWrittenID++; + pthread_cond_signal(&ctx->jobWrite_cond); + pthread_mutex_unlock(&ctx->jobWrite_mutex); DEBUGLOG(2, "unlocking job write mutex\n"); DEBUGLOG(2, "checking if done: %u/%u\n", currJob, ctx->lastJobID); @@ -250,23 +235,17 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) unsigned const nextJobIndex = nextJob % ctx->numJobs; jobDescription* job = &ctx->jobs[nextJobIndex]; // DEBUGLOG(2, "createCompressionJob(): wait for job write\n"); - pthread_mutex_lock(job->jobWrite_mutex); - while (job->jobWritten == 0) { - pthread_cond_wait(job->jobWrite_cond, job->jobWrite_mutex); + pthread_mutex_lock(&ctx->jobWrite_mutex); + while (nextJob - ctx->jobWrittenID >= ctx->numJobs) { + pthread_cond_wait(&ctx->jobWrite_cond, &ctx->jobWrite_mutex); } - pthread_mutex_unlock(job->jobWrite_mutex); + pthread_mutex_unlock(&ctx->jobWrite_mutex); // DEBUGLOG(2, "createCompressionJob(): continuing after job write\n"); job->compressionLevel = ctx->compressionLevel; job->src.start = malloc(srcSize); job->src.size = srcSize; job->dst.size = ZSTD_compressBound(srcSize); job->dst.start = malloc(job->dst.size); - job->jobCompleted = 0; - job->jobWritten = 0; - job->jobCompleted_cond = &ctx->jobCompleted_cond; - job->jobCompleted_mutex = &ctx->jobCompleted_mutex; - job->jobReady_cond = &ctx->jobReady_cond; - job->jobReady_mutex = &ctx->jobReady_mutex; job->jobID = nextJob; if (!job->src.start || !job->dst.start) { /* problem occurred, free things then return */ @@ -276,10 +255,10 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) return 1; } memcpy(job->src.start, data, srcSize); - pthread_mutex_lock(job->jobReady_mutex); - job->jobReady = 1; - pthread_cond_signal(job->jobReady_cond); - pthread_mutex_unlock(job->jobReady_mutex); + pthread_mutex_lock(&ctx->jobReady_mutex); + ctx->jobReadyID++; + pthread_cond_signal(&ctx->jobReady_cond); + pthread_mutex_unlock(&ctx->jobReady_mutex); ctx->nextJobID++; return 0; } From 79d4657ce5232cf7900407087962bb4ca978572e Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 5 Jul 2017 17:44:36 -0700 Subject: [PATCH 024/145] small changes --- contrib/adaptive-compression/multi.c | 9 ++++++--- contrib/adaptive-compression/pipetests.sh | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index fd04a53b..208a0a79 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -148,6 +148,7 @@ static void* compressionThread(void* arg) // DEBUGLOG(2, "compressionThread(): waiting on job ready\n"); pthread_mutex_lock(&ctx->jobReady_mutex); while(currJob + 1 > ctx->jobReadyID) { + DEBUGLOG(2, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond, &ctx->jobReady_mutex); } pthread_mutex_unlock(&ctx->jobReady_mutex); @@ -185,14 +186,14 @@ static void* outputThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; - DEBUGLOG(2, "outputThread(): waiting on job completed\n"); + // DEBUGLOG(2, "outputThread(): waiting on job completed\n"); pthread_mutex_lock(&ctx->jobCompleted_mutex); while (currJob + 1 > ctx->jobCompletedID) { - DEBUGLOG(2, "inside job completed wait loop waiting on %u\n", currJob); + DEBUGLOG(2, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompleted_cond, &ctx->jobCompleted_mutex); } pthread_mutex_unlock(&ctx->jobCompleted_mutex); - DEBUGLOG(2, "outputThread(): continuing after job completed\n"); + // DEBUGLOG(2, "outputThread(): continuing after job completed\n"); { size_t const compressedSize = job->compressedSize; if (ZSTD_isError(compressedSize)) { @@ -236,7 +237,9 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) jobDescription* job = &ctx->jobs[nextJobIndex]; // DEBUGLOG(2, "createCompressionJob(): wait for job write\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); + DISPLAY("Creating new compression job -- nextJob: %u, jobWrittenID: %u, numJObs: %u\n", nextJob, ctx->jobWrittenID, ctx->numJobs); while (nextJob - ctx->jobWrittenID >= ctx->numJobs) { + DEBUGLOG(2, "waiting on job writtten, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond, &ctx->jobWrite_mutex); } pthread_mutex_unlock(&ctx->jobWrite_mutex); diff --git a/contrib/adaptive-compression/pipetests.sh b/contrib/adaptive-compression/pipetests.sh index 2924cd5a..743ce381 100755 --- a/contrib/adaptive-compression/pipetests.sh +++ b/contrib/adaptive-compression/pipetests.sh @@ -1,2 +1,2 @@ make clean multi -pv -q -L 50m tests/test2048.pdf | ./multi -v -otmp.zst +pv -q -L 500m tests/test2048.pdf | ./multi -v -otmp.zst From 94fe291b831ec9d0a631b052dda2aec69b01a111 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 6 Jul 2017 10:29:16 -0700 Subject: [PATCH 025/145] small changes --- contrib/adaptive-compression/multi.c | 11 +++++++---- contrib/adaptive-compression/pipetests.sh | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 208a0a79..8371a0bc 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -1,7 +1,7 @@ #define DISPLAY(...) fprintf(stderr, __VA_ARGS__) #define DEBUGLOG(l, ...) { if (g_displayLevel>=l) { DISPLAY(__VA_ARGS__); } } #define FILE_CHUNK_SIZE 4 << 20 -#define MAX_NUM_JOBS 50; +#define MAX_NUM_JOBS 100; #define stdinmark "/*stdin*\\" #define stdoutmark "/*stdout*\\" #define MAX_PATH 256 @@ -168,6 +168,7 @@ static void* compressionThread(void* arg) DEBUGLOG(2, "signaling for job %u\n", currJob); pthread_cond_signal(&ctx->jobCompleted_cond); pthread_mutex_unlock(&ctx->jobCompleted_mutex); + DEBUGLOG(2, "finished job compression %u\n", currJob); currJob++; if (currJob >= ctx->lastJobID || ctx->threadError) { /* finished compressing all jobs */ @@ -189,7 +190,7 @@ static void* outputThread(void* arg) // DEBUGLOG(2, "outputThread(): waiting on job completed\n"); pthread_mutex_lock(&ctx->jobCompleted_mutex); while (currJob + 1 > ctx->jobCompletedID) { - DEBUGLOG(2, "waiting on job ready, nextJob: %u\n", currJob); + DEBUGLOG(2, "waiting on job completed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompleted_cond, &ctx->jobCompleted_mutex); } pthread_mutex_unlock(&ctx->jobCompleted_mutex); @@ -208,6 +209,7 @@ static void* outputThread(void* arg) } } } + DEBUGLOG(2, "finished job write %u\n", currJob); currJob++; DEBUGLOG(2, "locking job write mutex\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); @@ -237,9 +239,9 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) jobDescription* job = &ctx->jobs[nextJobIndex]; // DEBUGLOG(2, "createCompressionJob(): wait for job write\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); - DISPLAY("Creating new compression job -- nextJob: %u, jobWrittenID: %u, numJObs: %u\n", nextJob, ctx->jobWrittenID, ctx->numJobs); + // DEBUGLOG(2, "Creating new compression job -- nextJob: %u, jobCompletedID: %u, jobWrittenID: %u, numJObs: %u\n", nextJob,ctx->jobCompletedID, ctx->jobWrittenID, ctx->numJobs); while (nextJob - ctx->jobWrittenID >= ctx->numJobs) { - DEBUGLOG(2, "waiting on job writtten, nextJob: %u\n", nextJob); + DEBUGLOG(2, "waiting on job written, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond, &ctx->jobWrite_mutex); } pthread_mutex_unlock(&ctx->jobWrite_mutex); @@ -262,6 +264,7 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) ctx->jobReadyID++; pthread_cond_signal(&ctx->jobReady_cond); pthread_mutex_unlock(&ctx->jobReady_mutex); + DEBUGLOG(2, "finished job creation %u\n", nextJob); ctx->nextJobID++; return 0; } diff --git a/contrib/adaptive-compression/pipetests.sh b/contrib/adaptive-compression/pipetests.sh index 743ce381..71f123a1 100755 --- a/contrib/adaptive-compression/pipetests.sh +++ b/contrib/adaptive-compression/pipetests.sh @@ -1,2 +1,2 @@ make clean multi -pv -q -L 500m tests/test2048.pdf | ./multi -v -otmp.zst +pv -q -L 100m tests/test2048.pdf | ./multi -v -otmp.zst From 592a0d9495319e7eef455282674020b422f7254d Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 6 Jul 2017 10:49:26 -0700 Subject: [PATCH 026/145] changed to work with std out --- contrib/adaptive-compression/multi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 8371a0bc..6459e245 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -274,20 +274,21 @@ static int compressFilename(const char* const srcFilename, const char* const dst BYTE* const src = malloc(FILE_CHUNK_SIZE); unsigned const stdinUsed = !strcmp(srcFilename, stdinmark); FILE* const srcFile = stdinUsed ? stdin : fopen(srcFilename, "rb"); + const char* const outFilename = (stdinUsed && !dstFilename) ? stdoutmark : dstFilename; size_t const numJobs = MAX_NUM_JOBS; int ret = 0; adaptCCtx* ctx = NULL; /* checking for errors */ - if (!srcFilename || !dstFilename || !src || !srcFile) { + if (!srcFilename || !outFilename || !src || !srcFile) { DISPLAY("Error: initial variables could not be allocated\n"); ret = 1; goto cleanup; } /* creating context */ - ctx = createCCtx(numJobs, dstFilename); + ctx = createCCtx(numJobs, outFilename); if (ctx == NULL) { ret = 1; goto cleanup; From f57849b9c61da16f27347704d58c2db969b08a1e Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 6 Jul 2017 11:05:51 -0700 Subject: [PATCH 027/145] added ability to set initial compression level --- contrib/adaptive-compression/multi.c | 30 +++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 6459e245..da182b17 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -6,6 +6,7 @@ #define stdoutmark "/*stdout*\\" #define MAX_PATH 256 #define DEFAULT_DISPLAY_LEVEL 1 +#define DEFAULT_COMPRESSION_LEVEL 6 typedef unsigned char BYTE; #include /* fprintf */ @@ -15,6 +16,7 @@ typedef unsigned char BYTE; #include "zstd.h" static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; +static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; typedef struct { void* start; @@ -91,7 +93,7 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) return NULL; } memset(ctx, 0, sizeof(adaptCCtx)); - ctx->compressionLevel = 6; /* default */ + ctx->compressionLevel = g_compressionLevel; pthread_mutex_init(&ctx->jobCompleted_mutex, NULL); /* TODO: add checks for errors on each mutex */ pthread_cond_init(&ctx->jobCompleted_cond, NULL); pthread_mutex_init(&ctx->jobReady_mutex, NULL); @@ -362,6 +364,26 @@ static int compressFilenames(const char** filenameTable, unsigned numFiles) return ret; } +/*! readU32FromChar() : + @return : unsigned integer value read from input in `char` format + allows and interprets K, KB, KiB, M, MB and MiB suffix. + Will also modify `*stringPtr`, advancing it to position where it stopped reading. + Note : function result can overflow if digit string > MAX_UINT */ +static unsigned readU32FromChar(const char** stringPtr) +{ + unsigned result = 0; + while ((**stringPtr >='0') && (**stringPtr <='9')) + result *= 10, result += **stringPtr - '0', (*stringPtr)++ ; + if ((**stringPtr=='K') || (**stringPtr=='M')) { + result <<= 10; + if (**stringPtr=='M') result <<= 10; + (*stringPtr)++ ; + if (**stringPtr=='i') (*stringPtr)++; + if (**stringPtr=='B') (*stringPtr)++; + } + return result; +} + /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) { @@ -391,6 +413,12 @@ int main(int argCount, const char* argv[]) g_displayLevel++; continue; } + else if (strlen(argument) > 1 && argument[1] == 'i') { + argument += 2; + g_compressionLevel = readU32FromChar(&argument); + DEBUGLOG(2, "g_compressionLevel: %u\n", g_compressionLevel); + continue; + } else { DISPLAY("Error: invalid argument provided\n"); ret = 1; From a407ccc215fa826d84ea9760b8bfe426489d406e Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 6 Jul 2017 13:09:17 -0700 Subject: [PATCH 028/145] added ability to congregate statistics into single print statement rather than using debug --- contrib/adaptive-compression/multi.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index da182b17..6aa60315 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -17,12 +17,19 @@ typedef unsigned char BYTE; static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; +static unsigned g_displayStats = 0; typedef struct { void* start; size_t size; } buffer_t; +typedef struct { + unsigned waitCompleted; + unsigned waitReady; + unsigned waitWritten; +} stat_t; + typedef struct { buffer_t src; buffer_t dst; @@ -50,6 +57,7 @@ typedef struct { pthread_cond_t allJobsCompleted_cond; pthread_mutex_t jobWrite_mutex; pthread_cond_t jobWrite_cond; + stat_t stats; jobDescription* jobs; FILE* dstFile; } adaptCCtx; @@ -150,6 +158,7 @@ static void* compressionThread(void* arg) // DEBUGLOG(2, "compressionThread(): waiting on job ready\n"); pthread_mutex_lock(&ctx->jobReady_mutex); while(currJob + 1 > ctx->jobReadyID) { + ctx->stats.waitReady++; DEBUGLOG(2, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond, &ctx->jobReady_mutex); } @@ -192,6 +201,7 @@ static void* outputThread(void* arg) // DEBUGLOG(2, "outputThread(): waiting on job completed\n"); pthread_mutex_lock(&ctx->jobCompleted_mutex); while (currJob + 1 > ctx->jobCompletedID) { + ctx->stats.waitCompleted++; DEBUGLOG(2, "waiting on job completed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompleted_cond, &ctx->jobCompleted_mutex); } @@ -243,6 +253,7 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) pthread_mutex_lock(&ctx->jobWrite_mutex); // DEBUGLOG(2, "Creating new compression job -- nextJob: %u, jobCompletedID: %u, jobWrittenID: %u, numJObs: %u\n", nextJob,ctx->jobCompletedID, ctx->jobWrittenID, ctx->numJobs); while (nextJob - ctx->jobWrittenID >= ctx->numJobs) { + ctx->stats.waitWritten++; DEBUGLOG(2, "waiting on job written, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond, &ctx->jobWrite_mutex); } @@ -271,6 +282,14 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) return 0; } +static void printStats(stat_t stats) +{ + DISPLAY("========STATISTICS========\n"); + DISPLAY("# times waited on job ready: %u\n", stats.waitReady); + DISPLAY("# times waited on job completed: %u\n", stats.waitCompleted); + DISPLAY("# times waited on job written: %u\n\n", stats.waitWritten); +} + static int compressFilename(const char* const srcFilename, const char* const dstFilename) { BYTE* const src = malloc(FILE_CHUNK_SIZE); @@ -341,6 +360,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst cleanup: waitUntilAllJobsCompleted(ctx); + if (g_displayStats) printStats(ctx->stats); /* file compression completed */ ret |= (srcFile != NULL) ? fclose(srcFile) : 0; ret |= (ctx != NULL) ? freeCCtx(ctx) : 0; @@ -419,6 +439,10 @@ int main(int argCount, const char* argv[]) DEBUGLOG(2, "g_compressionLevel: %u\n", g_compressionLevel); continue; } + else if (strlen(argument) > 1 && argument[1] == 's') { + g_displayStats = 1; + continue; + } else { DISPLAY("Error: invalid argument provided\n"); ret = 1; From ff9f2cd057041543851489c1ce8cc179efc2dacc Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 6 Jul 2017 16:06:53 -0700 Subject: [PATCH 029/145] added some basic logic for altering compression level --- contrib/adaptive-compression/multi.c | 71 +++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 11 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 6aa60315..2b2aacab 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -1,12 +1,13 @@ #define DISPLAY(...) fprintf(stderr, __VA_ARGS__) #define DEBUGLOG(l, ...) { if (g_displayLevel>=l) { DISPLAY(__VA_ARGS__); } } #define FILE_CHUNK_SIZE 4 << 20 -#define MAX_NUM_JOBS 100; +#define MAX_NUM_JOBS 2; #define stdinmark "/*stdin*\\" #define stdoutmark "/*stdout*\\" #define MAX_PATH 256 #define DEFAULT_DISPLAY_LEVEL 1 #define DEFAULT_COMPRESSION_LEVEL 6 +#define DEFAULT_ADAPT_PARAM 2 typedef unsigned char BYTE; #include /* fprintf */ @@ -27,7 +28,10 @@ typedef struct { typedef struct { unsigned waitCompleted; unsigned waitReady; - unsigned waitWritten; + unsigned waitWrite; + unsigned readyCounter; + unsigned completedCounter; + unsigned writeCounter; } stat_t; typedef struct { @@ -47,8 +51,9 @@ typedef struct { unsigned threadError; unsigned jobReadyID; unsigned jobCompletedID; - unsigned jobWrittenID; + unsigned jobWriteID; unsigned allJobsCompleted; + unsigned adaptParam; pthread_mutex_t jobCompleted_mutex; pthread_cond_t jobCompleted_cond; pthread_mutex_t jobReady_mutex; @@ -113,12 +118,13 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->numJobs = numJobs; ctx->jobReadyID = 0; ctx->jobCompletedID = 0; - ctx->jobWrittenID = 0; + ctx->jobWriteID = 0; ctx->lastJobID = -1; /* intentional underflow */ ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); ctx->nextJobID = 0; ctx->threadError = 0; ctx->allJobsCompleted = 0; + ctx->adaptParam = DEFAULT_ADAPT_PARAM; if (!ctx->jobs) { DISPLAY("Error: could not allocate space for jobs during context creation\n"); freeCCtx(ctx); @@ -148,6 +154,41 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) pthread_mutex_unlock(&ctx->allJobsCompleted_mutex); } +static unsigned adaptCompressionLevel(adaptCCtx* ctx) +{ + unsigned reset = 0; + unsigned const allSlow = ctx->adaptParam < ctx->stats.completedCounter && ctx->adaptParam < ctx->stats.writeCounter && ctx->adaptParam < ctx->stats.readyCounter ? 1 : 0; + unsigned const compressWaiting = ctx->adaptParam < ctx->stats.readyCounter ? 1 : 0; + unsigned const writeWaiting = ctx->adaptParam < ctx->stats.completedCounter ? 1 : 0; + unsigned const createWaiting = ctx->adaptParam < ctx->stats.writeCounter ? 1 : 0; + unsigned const writeSlow = ((compressWaiting && createWaiting) || (createWaiting && !writeWaiting)) ? 1 : 0; + unsigned const compressSlow = ((writeWaiting && createWaiting) || (writeWaiting && !compressWaiting)) ? 1 : 0; + unsigned const createSlow = ((compressWaiting && writeWaiting) || (compressWaiting && !createWaiting)) ? 1 : 0; + // unsigned const writeSlow = ((compressWaiting && createWaiting)) ? 1 : 0; + // unsigned const compressSlow = ((writeWaiting && createWaiting)) ? 1 : 0; + // unsigned const createSlow = ((compressWaiting && writeWaiting)) ? 1 : 0; + DEBUGLOG(2, "ready: %u completed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.completedCounter, ctx->stats.writeCounter); + if (allSlow) { + reset = 1; + } + else if ((writeSlow || createSlow) && ctx->compressionLevel < (unsigned)ZSTD_maxCLevel()) { + DEBUGLOG(2, "increasing compression level %u\n", ctx->compressionLevel); + ctx->compressionLevel++; + reset = 1; + } + else if (compressSlow && ctx->compressionLevel > 1) { + DEBUGLOG(2, "decreasing compression level %u\n", ctx->compressionLevel); + ctx->compressionLevel--; + reset = 1; + } + if (reset) { + ctx->stats.readyCounter = 0; + ctx->stats.writeCounter = 0; + ctx->stats.completedCounter = 0; + } + return ctx->compressionLevel; +} + static void* compressionThread(void* arg) { adaptCCtx* ctx = (adaptCCtx*)arg; @@ -159,6 +200,7 @@ static void* compressionThread(void* arg) pthread_mutex_lock(&ctx->jobReady_mutex); while(currJob + 1 > ctx->jobReadyID) { ctx->stats.waitReady++; + ctx->stats.readyCounter++; DEBUGLOG(2, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond, &ctx->jobReady_mutex); } @@ -166,7 +208,10 @@ static void* compressionThread(void* arg) // DEBUGLOG(2, "compressionThread(): continuing after job ready\n"); /* compress the data */ { - size_t const compressedSize = ZSTD_compress(job->dst.start, job->dst.size, job->src.start, job->src.size, job->compressionLevel); + unsigned const cLevel = adaptCompressionLevel(ctx); + // unsigned const cLevel = job->compressionLevel; + DEBUGLOG(2, "cLevel used: %u\n", cLevel); + size_t const compressedSize = ZSTD_compress(job->dst.start, job->dst.size, job->src.start, job->src.size, cLevel); if (ZSTD_isError(compressedSize)) { ctx->threadError = 1; DISPLAY("Error: something went wrong during compression: %s\n", ZSTD_getErrorName(compressedSize)); @@ -202,6 +247,7 @@ static void* outputThread(void* arg) pthread_mutex_lock(&ctx->jobCompleted_mutex); while (currJob + 1 > ctx->jobCompletedID) { ctx->stats.waitCompleted++; + ctx->stats.completedCounter++; DEBUGLOG(2, "waiting on job completed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompleted_cond, &ctx->jobCompleted_mutex); } @@ -225,7 +271,7 @@ static void* outputThread(void* arg) currJob++; DEBUGLOG(2, "locking job write mutex\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); - ctx->jobWrittenID++; + ctx->jobWriteID++; pthread_cond_signal(&ctx->jobWrite_cond); pthread_mutex_unlock(&ctx->jobWrite_mutex); DEBUGLOG(2, "unlocking job write mutex\n"); @@ -251,14 +297,17 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) jobDescription* job = &ctx->jobs[nextJobIndex]; // DEBUGLOG(2, "createCompressionJob(): wait for job write\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); - // DEBUGLOG(2, "Creating new compression job -- nextJob: %u, jobCompletedID: %u, jobWrittenID: %u, numJObs: %u\n", nextJob,ctx->jobCompletedID, ctx->jobWrittenID, ctx->numJobs); - while (nextJob - ctx->jobWrittenID >= ctx->numJobs) { - ctx->stats.waitWritten++; - DEBUGLOG(2, "waiting on job written, nextJob: %u\n", nextJob); + // DEBUGLOG(2, "Creating new compression job -- nextJob: %u, jobCompletedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompletedID, ctx->jobWriteID, ctx->numJobs); + while (nextJob - ctx->jobWriteID >= ctx->numJobs) { + ctx->stats.waitWrite++; + ctx->stats.writeCounter++; + DEBUGLOG(2, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond, &ctx->jobWrite_mutex); } pthread_mutex_unlock(&ctx->jobWrite_mutex); // DEBUGLOG(2, "createCompressionJob(): continuing after job write\n"); + + job->compressionLevel = ctx->compressionLevel; job->src.start = malloc(srcSize); job->src.size = srcSize; @@ -287,7 +336,7 @@ static void printStats(stat_t stats) DISPLAY("========STATISTICS========\n"); DISPLAY("# times waited on job ready: %u\n", stats.waitReady); DISPLAY("# times waited on job completed: %u\n", stats.waitCompleted); - DISPLAY("# times waited on job written: %u\n\n", stats.waitWritten); + DISPLAY("# times waited on job Write: %u\n\n", stats.waitWrite); } static int compressFilename(const char* const srcFilename, const char* const dstFilename) From b6cc0847162ff6eb7e7398f92736ecf4941b614c Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 6 Jul 2017 17:48:18 -0700 Subject: [PATCH 030/145] added really simple progress update in the corner --- contrib/adaptive-compression/multi.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 2b2aacab..324a570b 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -14,11 +14,14 @@ typedef unsigned char BYTE; #include /* malloc, free */ #include /* pthread functions */ #include /* memset */ +#include /* clock(), CLOCKS_PER_SEC */ #include "zstd.h" static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; static unsigned g_displayStats = 0; +static clock_t g_time = 0; +static clock_t const refreshRate = CLOCKS_PER_SEC / 60; /* 60 Hz */ typedef struct { void* start; @@ -235,6 +238,16 @@ static void* compressionThread(void* arg) return arg; } +static void displayProgress(unsigned jobDoneID) +{ + clock_t currTime = clock(); + unsigned const refresh = currTime - g_time > refreshRate ? 1 : 0; + if (refresh) { + fprintf(stdout, "%u jobs completed\r", jobDoneID+1); + fflush(stdout); + } +} + static void* outputThread(void* arg) { adaptCCtx* ctx = (adaptCCtx*)arg; @@ -268,6 +281,7 @@ static void* outputThread(void* arg) } } DEBUGLOG(2, "finished job write %u\n", currJob); + displayProgress(currJob); currJob++; DEBUGLOG(2, "locking job write mutex\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); @@ -348,6 +362,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst size_t const numJobs = MAX_NUM_JOBS; int ret = 0; adaptCCtx* ctx = NULL; + g_time = clock(); /* checking for errors */ From 57ec0232a832ad93c1b9b57145e6a21c6c41da84 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 6 Jul 2017 18:09:10 -0700 Subject: [PATCH 031/145] added help menu --- contrib/adaptive-compression/multi.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 324a570b..4e33a051 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -1,4 +1,5 @@ #define DISPLAY(...) fprintf(stderr, __VA_ARGS__) +#define PRINT(...) fprintf(stdout, __VA_ARGS__) #define DEBUGLOG(l, ...) { if (g_displayLevel>=l) { DISPLAY(__VA_ARGS__); } } #define FILE_CHUNK_SIZE 4 << 20 #define MAX_NUM_JOBS 2; @@ -243,7 +244,7 @@ static void displayProgress(unsigned jobDoneID) clock_t currTime = clock(); unsigned const refresh = currTime - g_time > refreshRate ? 1 : 0; if (refresh) { - fprintf(stdout, "%u jobs completed\r", jobDoneID+1); + fprintf(stdout, "\r%u jobs completed", jobDoneID+1); fflush(stdout); } } @@ -468,6 +469,18 @@ static unsigned readU32FromChar(const char** stringPtr) return result; } +static void help() +{ + PRINT("Usage:\n"); + PRINT(" ./multi [options] [file(s)]\n"); + PRINT("\n"); + PRINT("Options:\n"); + PRINT(" -oFILE : specify the output file name\n"); + PRINT(" -v : display debug information\n"); + PRINT(" -i# : provide initial compression level\n"); + PRINT(" -s : display information stats\n"); + PRINT(" -h : display help/information\n"); +} /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) { @@ -507,6 +520,10 @@ int main(int argCount, const char* argv[]) g_displayStats = 1; continue; } + else if (strlen(argument) > 1 && argument[1] == 'h') { + help(); + return 0; + } else { DISPLAY("Error: invalid argument provided\n"); ret = 1; From 2939301023cdda52bd6e7883206a58722f861463 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 6 Jul 2017 20:30:20 -0700 Subject: [PATCH 032/145] fixed problem with progress bar not persisting, added time elapsed --- contrib/adaptive-compression/multi.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 4e33a051..814018d6 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -22,6 +22,7 @@ static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; static unsigned g_displayStats = 0; static clock_t g_time = 0; +static clock_t g_startTime = 0; static clock_t const refreshRate = CLOCKS_PER_SEC / 60; /* 60 Hz */ typedef struct { @@ -239,13 +240,19 @@ static void* compressionThread(void* arg) return arg; } -static void displayProgress(unsigned jobDoneID) +static void displayProgress(unsigned jobDoneID, unsigned cLevel, unsigned last) { clock_t currTime = clock(); unsigned const refresh = currTime - g_time > refreshRate ? 1 : 0; + double timeElapsed = (double)((currTime - g_startTime) * 1000 / CLOCKS_PER_SEC); if (refresh) { - fprintf(stdout, "\r%u jobs completed", jobDoneID+1); - fflush(stdout); + fprintf(stdout, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0fms |", jobDoneID, cLevel, timeElapsed); + if (last) { + fprintf(stdout, "\n"); + } + else { + fflush(stdout); + } } } @@ -282,8 +289,8 @@ static void* outputThread(void* arg) } } DEBUGLOG(2, "finished job write %u\n", currJob); - displayProgress(currJob); currJob++; + displayProgress(currJob, ctx->compressionLevel, currJob >= ctx->lastJobID); DEBUGLOG(2, "locking job write mutex\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); ctx->jobWriteID++; @@ -364,6 +371,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst int ret = 0; adaptCCtx* ctx = NULL; g_time = clock(); + g_startTime = clock(); /* checking for errors */ From f351848b76d6c3f780f1679556eb269dec81d4d8 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 6 Jul 2017 20:40:00 -0700 Subject: [PATCH 033/145] added data amount --- contrib/adaptive-compression/multi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 814018d6..7c769e49 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -24,6 +24,7 @@ static unsigned g_displayStats = 0; static clock_t g_time = 0; static clock_t g_startTime = 0; static clock_t const refreshRate = CLOCKS_PER_SEC / 60; /* 60 Hz */ +static size_t g_streamedSize = 0; typedef struct { void* start; @@ -244,9 +245,10 @@ static void displayProgress(unsigned jobDoneID, unsigned cLevel, unsigned last) { clock_t currTime = clock(); unsigned const refresh = currTime - g_time > refreshRate ? 1 : 0; - double timeElapsed = (double)((currTime - g_startTime) * 1000 / CLOCKS_PER_SEC); + double const timeElapsed = (double)((currTime - g_startTime) * 1000 / CLOCKS_PER_SEC); + double const sizeMB = (double)g_streamedSize / (1 << 20); if (refresh) { - fprintf(stdout, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0fms |", jobDoneID, cLevel, timeElapsed); + fprintf(stdout, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB |", jobDoneID, cLevel, timeElapsed, sizeMB); if (last) { fprintf(stdout, "\n"); } @@ -372,6 +374,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst adaptCCtx* ctx = NULL; g_time = clock(); g_startTime = clock(); + g_streamedSize = 0; /* checking for errors */ @@ -416,6 +419,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst ret = 1; goto cleanup; } + g_streamedSize += readSize; /* reading was fine, now create the compression job */ { int const error = createCompressionJob(ctx, src, readSize); From 00bc5df4e0de9811a3579052b0fbeaa949aecb93 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 09:35:39 -0700 Subject: [PATCH 034/145] added compression rate to status bar --- contrib/adaptive-compression/multi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 7c769e49..f8568c42 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -247,8 +247,9 @@ static void displayProgress(unsigned jobDoneID, unsigned cLevel, unsigned last) unsigned const refresh = currTime - g_time > refreshRate ? 1 : 0; double const timeElapsed = (double)((currTime - g_startTime) * 1000 / CLOCKS_PER_SEC); double const sizeMB = (double)g_streamedSize / (1 << 20); + double const avgCompRate = sizeMB / timeElapsed; if (refresh) { - fprintf(stdout, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB |", jobDoneID, cLevel, timeElapsed, sizeMB); + fprintf(stdout, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB | Avg Compression Rate: %6.2f MB/s |", jobDoneID, cLevel, timeElapsed, sizeMB, avgCompRate); if (last) { fprintf(stdout, "\n"); } From 4679132f59f02aa2c8fc3a3a3bf7a561a22e58f4 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 10:25:38 -0700 Subject: [PATCH 035/145] updated avg compression rate, also hiding progress bar behind a flag now --- contrib/adaptive-compression/multi.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index f8568c42..5223d706 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -25,6 +25,7 @@ static clock_t g_time = 0; static clock_t g_startTime = 0; static clock_t const refreshRate = CLOCKS_PER_SEC / 60; /* 60 Hz */ static size_t g_streamedSize = 0; +static unsigned g_useProgressBar = 0; typedef struct { void* start; @@ -243,11 +244,12 @@ static void* compressionThread(void* arg) static void displayProgress(unsigned jobDoneID, unsigned cLevel, unsigned last) { + if (!g_useProgressBar) return; clock_t currTime = clock(); unsigned const refresh = currTime - g_time > refreshRate ? 1 : 0; double const timeElapsed = (double)((currTime - g_startTime) * 1000 / CLOCKS_PER_SEC); double const sizeMB = (double)g_streamedSize / (1 << 20); - double const avgCompRate = sizeMB / timeElapsed; + double const avgCompRate = sizeMB * 1000 / timeElapsed; if (refresh) { fprintf(stdout, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB | Avg Compression Rate: %6.2f MB/s |", jobDoneID, cLevel, timeElapsed, sizeMB, avgCompRate); if (last) { @@ -537,6 +539,10 @@ int main(int argCount, const char* argv[]) help(); return 0; } + else if (strlen(argument) > 1 && argument[1] == 'p') { + g_useProgressBar = 1; + continue; + } else { DISPLAY("Error: invalid argument provided\n"); ret = 1; From f7e6b358d04d8b1da2599ed039c1c12d301db6f2 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 10:29:06 -0700 Subject: [PATCH 036/145] added tests that check to ensure stdout is working --- contrib/adaptive-compression/run.sh | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/contrib/adaptive-compression/run.sh b/contrib/adaptive-compression/run.sh index b906ae7d..f030be55 100755 --- a/contrib/adaptive-compression/run.sh +++ b/contrib/adaptive-compression/run.sh @@ -76,6 +76,42 @@ diff tmp tests/test.pdf echo "diff test complete: test.pdf" rm tmp* +cat tests/test2048.pdf | ./multi > tmp.zst +zstd -d tmp.zst +diff tmp tests/test2048.pdf +echo "diff test complete: test2048.pdf" +rm tmp* + +cat tests/test512.pdf | ./multi > tmp.zst +zstd -d tmp.zst +diff tmp tests/test512.pdf +echo "diff test complete: test512.pdf" +rm tmp* + +cat tests/test64.pdf | ./multi > tmp.zst +zstd -d tmp.zst +diff tmp tests/test64.pdf +echo "diff test complete: test64.pdf" +rm tmp* + +cat tests/test16.pdf | ./multi > tmp.zst +zstd -d tmp.zst +diff tmp tests/test16.pdf +echo "diff test complete: test16.pdf" +rm tmp* + +cat tests/test4.pdf | ./multi > tmp.zst +zstd -d tmp.zst +diff tmp tests/test4.pdf +echo "diff test complete: test4.pdf" +rm tmp* + +cat tests/test.pdf | ./multi > tmp.zst +zstd -d tmp.zst +diff tmp tests/test.pdf +echo "diff test complete: test.pdf" +rm tmp* + echo "Running multi-file tests" ./multi tests/* zstd -d tests/test.pdf.zst -o tests/tmp From 532f439961747c60d13d8dde0dee4aafb1342426 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 10:58:43 -0700 Subject: [PATCH 037/145] cleaned up code for arguments a bit --- contrib/adaptive-compression/multi.c | 60 +++++++++++++--------------- contrib/adaptive-compression/run.sh | 3 ++ 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 5223d706..d88d3787 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -515,39 +515,35 @@ int main(int argCount, const char* argv[]) const char* argument = argv[argNum]; /* output filename designated with "-o" */ - if (argument[0]=='-') { - if (strlen(argument) > 1 && argument[1] == 'o') { - argument += 2; - outFilename = argument; - continue; - } - else if (strlen(argument) > 1 && argument[1] == 'v') { - g_displayLevel++; - continue; - } - else if (strlen(argument) > 1 && argument[1] == 'i') { - argument += 2; - g_compressionLevel = readU32FromChar(&argument); - DEBUGLOG(2, "g_compressionLevel: %u\n", g_compressionLevel); - continue; - } - else if (strlen(argument) > 1 && argument[1] == 's') { - g_displayStats = 1; - continue; - } - else if (strlen(argument) > 1 && argument[1] == 'h') { - help(); - return 0; - } - else if (strlen(argument) > 1 && argument[1] == 'p') { - g_useProgressBar = 1; - continue; - } - else { - DISPLAY("Error: invalid argument provided\n"); - ret = 1; - goto _main_exit; + if (argument[0]=='-' && strlen(argument) > 1) { + switch (argument[1]) { + case 'o': + argument += 2; + outFilename = argument; + break; + case 'v': + g_displayLevel++; + break; + case 'i': + argument += 2; + g_compressionLevel = readU32FromChar(&argument); + DEBUGLOG(2, "g_compressionLevel: %u\n", g_compressionLevel); + break; + case 's': + g_displayStats = 1; + break; + case 'h': + help(); + goto _main_exit; + case 'p': + g_useProgressBar = 1; + break; + default: + DISPLAY("Error: invalid argument provided\n"); + ret = 1; + goto _main_exit; } + continue; } /* regular files to be compressed */ diff --git a/contrib/adaptive-compression/run.sh b/contrib/adaptive-compression/run.sh index f030be55..bcd8c718 100755 --- a/contrib/adaptive-compression/run.sh +++ b/contrib/adaptive-compression/run.sh @@ -140,6 +140,9 @@ diff tests/test512.pdf tests/tmp512 diff tests/test1024.pdf tests/tmp1024 diff tests/test2048.pdf tests/tmp2048 +echo "Running Args Tests" +./multi -h +./multi -i22 -p -s -otmp.zst tests/test2048.pdf echo "finished with tests" make clean From 70a4153bd38ddf2f44067193c5fcdf7b1273073b Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 11:32:14 -0700 Subject: [PATCH 038/145] added ability to force output to stdout, wrote an additional test for this functionality --- contrib/adaptive-compression/multi.c | 20 +++++++++++++++----- contrib/adaptive-compression/run.sh | 8 ++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index d88d3787..7802c429 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -95,7 +95,7 @@ static int freeCCtx(adaptCCtx* ctx) int const allJobsCondError = pthread_cond_destroy(&ctx->allJobsCompleted_cond); int const jobWriteMutexError = pthread_mutex_destroy(&ctx->jobWrite_mutex); int const jobWriteCondError = pthread_cond_destroy(&ctx->jobWrite_cond); - int const fileCloseError = ctx->dstFile != NULL ? fclose(ctx->dstFile) : 0; + int const fileCloseError = (ctx->dstFile != NULL && ctx->dstFile != stdout) ? fclose(ctx->dstFile) : 0; if (ctx->jobs){ freeCompressionJobs(ctx); free(ctx->jobs); @@ -448,7 +448,7 @@ cleanup: return ret; } -static int compressFilenames(const char** filenameTable, unsigned numFiles) +static int compressFilenames(const char** filenameTable, unsigned numFiles, unsigned forceStdout) { int ret = 0; unsigned fileNum; @@ -459,7 +459,13 @@ static int compressFilenames(const char** filenameTable, unsigned numFiles) DISPLAY("Error: output filename is too long\n"); return 1; } - ret |= compressFilename(filename, outFile); + if (!forceStdout) { + ret |= compressFilename(filename, outFile); + } + else { + ret |= compressFilename(filename, stdoutmark); + } + } return ret; } @@ -503,6 +509,7 @@ int main(int argCount, const char* argv[]) const char** filenameTable = (const char**)malloc(argCount*sizeof(const char*)); unsigned filenameIdx = 0; filenameTable[0] = stdinmark; + unsigned forceStdout = 0; int ret = 0; int argNum; @@ -538,6 +545,9 @@ int main(int argCount, const char* argv[]) case 'p': g_useProgressBar = 1; break; + case 'c': + forceStdout = 1; + break; default: DISPLAY("Error: invalid argument provided\n"); ret = 1; @@ -551,7 +561,7 @@ int main(int argCount, const char* argv[]) } /* error checking with number of files */ - if (filenameIdx > 1 && outFilename != NULL) { + if (filenameIdx > 1 && (outFilename != NULL && strcmp(outFilename, stdoutmark))) { DISPLAY("Error: multiple input files provided, cannot use specified output file\n"); ret = 1; goto _main_exit; @@ -562,7 +572,7 @@ int main(int argCount, const char* argv[]) ret |= compressFilename(filenameTable[0], outFilename); } else { - ret |= compressFilenames(filenameTable, filenameIdx); + ret |= compressFilenames(filenameTable, filenameIdx, forceStdout); } _main_exit: free(filenameTable); diff --git a/contrib/adaptive-compression/run.sh b/contrib/adaptive-compression/run.sh index bcd8c718..67814ec1 100755 --- a/contrib/adaptive-compression/run.sh +++ b/contrib/adaptive-compression/run.sh @@ -140,9 +140,17 @@ diff tests/test512.pdf tests/tmp512 diff tests/test1024.pdf tests/tmp1024 diff tests/test2048.pdf tests/tmp2048 +rm -f tests/*.zst tests/tmp* echo "Running Args Tests" ./multi -h ./multi -i22 -p -s -otmp.zst tests/test2048.pdf +rm tmp* + +echo "Running Tests With Multiple Files > stdout" +./multi tests/* -c > tmp.zst +zstd -d tmp.zst +rm tmp* + echo "finished with tests" make clean From 8c0eb62920f0badddabedd4804b79806c1bf2a73 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 11:47:16 -0700 Subject: [PATCH 039/145] removed unnecessary comments, uncommented DEBUGLOG for later use --- contrib/adaptive-compression/multi.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 7802c429..acb06f07 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -171,9 +171,6 @@ static unsigned adaptCompressionLevel(adaptCCtx* ctx) unsigned const writeSlow = ((compressWaiting && createWaiting) || (createWaiting && !writeWaiting)) ? 1 : 0; unsigned const compressSlow = ((writeWaiting && createWaiting) || (writeWaiting && !compressWaiting)) ? 1 : 0; unsigned const createSlow = ((compressWaiting && writeWaiting) || (compressWaiting && !createWaiting)) ? 1 : 0; - // unsigned const writeSlow = ((compressWaiting && createWaiting)) ? 1 : 0; - // unsigned const compressSlow = ((writeWaiting && createWaiting)) ? 1 : 0; - // unsigned const createSlow = ((compressWaiting && writeWaiting)) ? 1 : 0; DEBUGLOG(2, "ready: %u completed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.completedCounter, ctx->stats.writeCounter); if (allSlow) { reset = 1; @@ -203,7 +200,7 @@ static void* compressionThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; - // DEBUGLOG(2, "compressionThread(): waiting on job ready\n"); + DEBUGLOG(2, "compressionThread(): waiting on job ready\n"); pthread_mutex_lock(&ctx->jobReady_mutex); while(currJob + 1 > ctx->jobReadyID) { ctx->stats.waitReady++; @@ -212,11 +209,10 @@ static void* compressionThread(void* arg) pthread_cond_wait(&ctx->jobReady_cond, &ctx->jobReady_mutex); } pthread_mutex_unlock(&ctx->jobReady_mutex); - // DEBUGLOG(2, "compressionThread(): continuing after job ready\n"); + DEBUGLOG(2, "compressionThread(): continuing after job ready\n"); /* compress the data */ { unsigned const cLevel = adaptCompressionLevel(ctx); - // unsigned const cLevel = job->compressionLevel; DEBUGLOG(2, "cLevel used: %u\n", cLevel); size_t const compressedSize = ZSTD_compress(job->dst.start, job->dst.size, job->src.start, job->src.size, cLevel); if (ZSTD_isError(compressedSize)) { @@ -269,7 +265,7 @@ static void* outputThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; - // DEBUGLOG(2, "outputThread(): waiting on job completed\n"); + DEBUGLOG(2, "outputThread(): waiting on job completed\n"); pthread_mutex_lock(&ctx->jobCompleted_mutex); while (currJob + 1 > ctx->jobCompletedID) { ctx->stats.waitCompleted++; @@ -278,7 +274,7 @@ static void* outputThread(void* arg) pthread_cond_wait(&ctx->jobCompleted_cond, &ctx->jobCompleted_mutex); } pthread_mutex_unlock(&ctx->jobCompleted_mutex); - // DEBUGLOG(2, "outputThread(): continuing after job completed\n"); + DEBUGLOG(2, "outputThread(): continuing after job completed\n"); { size_t const compressedSize = job->compressedSize; if (ZSTD_isError(compressedSize)) { @@ -322,9 +318,9 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) unsigned const nextJob = ctx->nextJobID; unsigned const nextJobIndex = nextJob % ctx->numJobs; jobDescription* job = &ctx->jobs[nextJobIndex]; - // DEBUGLOG(2, "createCompressionJob(): wait for job write\n"); + DEBUGLOG(2, "createCompressionJob(): wait for job write\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); - // DEBUGLOG(2, "Creating new compression job -- nextJob: %u, jobCompletedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompletedID, ctx->jobWriteID, ctx->numJobs); + DEBUGLOG(2, "Creating new compression job -- nextJob: %u, jobCompletedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompletedID, ctx->jobWriteID, ctx->numJobs); while (nextJob - ctx->jobWriteID >= ctx->numJobs) { ctx->stats.waitWrite++; ctx->stats.writeCounter++; @@ -332,7 +328,7 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) pthread_cond_wait(&ctx->jobWrite_cond, &ctx->jobWrite_mutex); } pthread_mutex_unlock(&ctx->jobWrite_mutex); - // DEBUGLOG(2, "createCompressionJob(): continuing after job write\n"); + DEBUGLOG(2, "createCompressionJob(): continuing after job write\n"); job->compressionLevel = ctx->compressionLevel; From 09d7c6a994d5f7a67a7442ad92d4dfc2e0a06bda Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 13:18:55 -0700 Subject: [PATCH 040/145] changed completed variables to compressed for clarity --- contrib/adaptive-compression/multi.c | 54 ++++++++++++++-------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index acb06f07..03667d48 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -33,11 +33,11 @@ typedef struct { } buffer_t; typedef struct { - unsigned waitCompleted; + unsigned waitCompressed; unsigned waitReady; unsigned waitWrite; unsigned readyCounter; - unsigned completedCounter; + unsigned compressedCounter; unsigned writeCounter; } stat_t; @@ -57,12 +57,12 @@ typedef struct { unsigned nextJobID; unsigned threadError; unsigned jobReadyID; - unsigned jobCompletedID; + unsigned jobCompressedID; unsigned jobWriteID; unsigned allJobsCompleted; unsigned adaptParam; - pthread_mutex_t jobCompleted_mutex; - pthread_cond_t jobCompleted_cond; + pthread_mutex_t jobCompressed_mutex; + pthread_cond_t jobCompressed_cond; pthread_mutex_t jobReady_mutex; pthread_cond_t jobReady_cond; pthread_mutex_t allJobsCompleted_mutex; @@ -87,8 +87,8 @@ static void freeCompressionJobs(adaptCCtx* ctx) static int freeCCtx(adaptCCtx* ctx) { { - int const completedMutexError = pthread_mutex_destroy(&ctx->jobCompleted_mutex); - int const completedCondError = pthread_cond_destroy(&ctx->jobCompleted_cond); + int const compressedMutexError = pthread_mutex_destroy(&ctx->jobCompressed_mutex); + int const compressedCondError = pthread_cond_destroy(&ctx->jobCompressed_cond); int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); int const allJobsMutexError = pthread_mutex_destroy(&ctx->allJobsCompleted_mutex); @@ -100,7 +100,7 @@ static int freeCCtx(adaptCCtx* ctx) freeCompressionJobs(ctx); free(ctx->jobs); } - return completedMutexError | completedCondError | readyMutexError | readyCondError | fileCloseError | allJobsMutexError | allJobsCondError | jobWriteMutexError | jobWriteCondError; + return compressedMutexError | compressedCondError | readyMutexError | readyCondError | fileCloseError | allJobsMutexError | allJobsCondError | jobWriteMutexError | jobWriteCondError; } } @@ -114,8 +114,8 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) } memset(ctx, 0, sizeof(adaptCCtx)); ctx->compressionLevel = g_compressionLevel; - pthread_mutex_init(&ctx->jobCompleted_mutex, NULL); /* TODO: add checks for errors on each mutex */ - pthread_cond_init(&ctx->jobCompleted_cond, NULL); + pthread_mutex_init(&ctx->jobCompressed_mutex, NULL); /* TODO: add checks for errors on each mutex */ + pthread_cond_init(&ctx->jobCompressed_cond, NULL); pthread_mutex_init(&ctx->jobReady_mutex, NULL); pthread_cond_init(&ctx->jobReady_cond, NULL); pthread_mutex_init(&ctx->allJobsCompleted_mutex, NULL); @@ -124,7 +124,7 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) pthread_cond_init(&ctx->jobWrite_cond, NULL); ctx->numJobs = numJobs; ctx->jobReadyID = 0; - ctx->jobCompletedID = 0; + ctx->jobCompressedID = 0; ctx->jobWriteID = 0; ctx->lastJobID = -1; /* intentional underflow */ ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); @@ -164,14 +164,14 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) static unsigned adaptCompressionLevel(adaptCCtx* ctx) { unsigned reset = 0; - unsigned const allSlow = ctx->adaptParam < ctx->stats.completedCounter && ctx->adaptParam < ctx->stats.writeCounter && ctx->adaptParam < ctx->stats.readyCounter ? 1 : 0; + unsigned const allSlow = ctx->adaptParam < ctx->stats.compressedCounter && ctx->adaptParam < ctx->stats.writeCounter && ctx->adaptParam < ctx->stats.readyCounter ? 1 : 0; unsigned const compressWaiting = ctx->adaptParam < ctx->stats.readyCounter ? 1 : 0; - unsigned const writeWaiting = ctx->adaptParam < ctx->stats.completedCounter ? 1 : 0; + unsigned const writeWaiting = ctx->adaptParam < ctx->stats.compressedCounter ? 1 : 0; unsigned const createWaiting = ctx->adaptParam < ctx->stats.writeCounter ? 1 : 0; unsigned const writeSlow = ((compressWaiting && createWaiting) || (createWaiting && !writeWaiting)) ? 1 : 0; unsigned const compressSlow = ((writeWaiting && createWaiting) || (writeWaiting && !compressWaiting)) ? 1 : 0; unsigned const createSlow = ((compressWaiting && writeWaiting) || (compressWaiting && !createWaiting)) ? 1 : 0; - DEBUGLOG(2, "ready: %u completed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.completedCounter, ctx->stats.writeCounter); + DEBUGLOG(2, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); if (allSlow) { reset = 1; } @@ -188,7 +188,7 @@ static unsigned adaptCompressionLevel(adaptCCtx* ctx) if (reset) { ctx->stats.readyCounter = 0; ctx->stats.writeCounter = 0; - ctx->stats.completedCounter = 0; + ctx->stats.compressedCounter = 0; } return ctx->compressionLevel; } @@ -222,11 +222,11 @@ static void* compressionThread(void* arg) } job->compressedSize = compressedSize; } - pthread_mutex_lock(&ctx->jobCompleted_mutex); - ctx->jobCompletedID++; + pthread_mutex_lock(&ctx->jobCompressed_mutex); + ctx->jobCompressedID++; DEBUGLOG(2, "signaling for job %u\n", currJob); - pthread_cond_signal(&ctx->jobCompleted_cond); - pthread_mutex_unlock(&ctx->jobCompleted_mutex); + pthread_cond_signal(&ctx->jobCompressed_cond); + pthread_mutex_unlock(&ctx->jobCompressed_mutex); DEBUGLOG(2, "finished job compression %u\n", currJob); currJob++; if (currJob >= ctx->lastJobID || ctx->threadError) { @@ -266,14 +266,14 @@ static void* outputThread(void* arg) unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; DEBUGLOG(2, "outputThread(): waiting on job completed\n"); - pthread_mutex_lock(&ctx->jobCompleted_mutex); - while (currJob + 1 > ctx->jobCompletedID) { - ctx->stats.waitCompleted++; - ctx->stats.completedCounter++; + pthread_mutex_lock(&ctx->jobCompressed_mutex); + while (currJob + 1 > ctx->jobCompressedID) { + ctx->stats.waitCompressed++; + ctx->stats.compressedCounter++; DEBUGLOG(2, "waiting on job completed, nextJob: %u\n", currJob); - pthread_cond_wait(&ctx->jobCompleted_cond, &ctx->jobCompleted_mutex); + pthread_cond_wait(&ctx->jobCompressed_cond, &ctx->jobCompressed_mutex); } - pthread_mutex_unlock(&ctx->jobCompleted_mutex); + pthread_mutex_unlock(&ctx->jobCompressed_mutex); DEBUGLOG(2, "outputThread(): continuing after job completed\n"); { size_t const compressedSize = job->compressedSize; @@ -320,7 +320,7 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) jobDescription* job = &ctx->jobs[nextJobIndex]; DEBUGLOG(2, "createCompressionJob(): wait for job write\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); - DEBUGLOG(2, "Creating new compression job -- nextJob: %u, jobCompletedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompletedID, ctx->jobWriteID, ctx->numJobs); + DEBUGLOG(2, "Creating new compression job -- nextJob: %u, jobCompressedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompressedID, ctx->jobWriteID, ctx->numJobs); while (nextJob - ctx->jobWriteID >= ctx->numJobs) { ctx->stats.waitWrite++; ctx->stats.writeCounter++; @@ -358,7 +358,7 @@ static void printStats(stat_t stats) { DISPLAY("========STATISTICS========\n"); DISPLAY("# times waited on job ready: %u\n", stats.waitReady); - DISPLAY("# times waited on job completed: %u\n", stats.waitCompleted); + DISPLAY("# times waited on job compressed: %u\n", stats.waitCompressed); DISPLAY("# times waited on job Write: %u\n\n", stats.waitWrite); } From 11fc0f41197f885def5744e50dfe5418364ca724 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 13:55:38 -0700 Subject: [PATCH 041/145] changed completed -> compressed --- contrib/adaptive-compression/multi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 03667d48..647d54ba 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -265,16 +265,16 @@ static void* outputThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; - DEBUGLOG(2, "outputThread(): waiting on job completed\n"); + DEBUGLOG(2, "outputThread(): waiting on job compressed\n"); pthread_mutex_lock(&ctx->jobCompressed_mutex); while (currJob + 1 > ctx->jobCompressedID) { ctx->stats.waitCompressed++; ctx->stats.compressedCounter++; - DEBUGLOG(2, "waiting on job completed, nextJob: %u\n", currJob); + DEBUGLOG(2, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond, &ctx->jobCompressed_mutex); } pthread_mutex_unlock(&ctx->jobCompressed_mutex); - DEBUGLOG(2, "outputThread(): continuing after job completed\n"); + DEBUGLOG(2, "outputThread(): continuing after job compressed\n"); { size_t const compressedSize = job->compressedSize; if (ZSTD_isError(compressedSize)) { From c0c236a28b13ccbc71465eba4c7a52bae96a8e8a Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 15:13:40 -0700 Subject: [PATCH 042/145] changed to using compressCCtx --- contrib/adaptive-compression/multi.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 647d54ba..943bd856 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -72,6 +72,7 @@ typedef struct { stat_t stats; jobDescription* jobs; FILE* dstFile; + ZSTD_CCtx* cctx; } adaptCCtx; static void freeCompressionJobs(adaptCCtx* ctx) @@ -96,11 +97,12 @@ static int freeCCtx(adaptCCtx* ctx) int const jobWriteMutexError = pthread_mutex_destroy(&ctx->jobWrite_mutex); int const jobWriteCondError = pthread_cond_destroy(&ctx->jobWrite_cond); int const fileCloseError = (ctx->dstFile != NULL && ctx->dstFile != stdout) ? fclose(ctx->dstFile) : 0; + int const cctxError = ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)) ? 1 : 0; if (ctx->jobs){ freeCompressionJobs(ctx); free(ctx->jobs); } - return compressedMutexError | compressedCondError | readyMutexError | readyCondError | fileCloseError | allJobsMutexError | allJobsCondError | jobWriteMutexError | jobWriteCondError; + return compressedMutexError | compressedCondError | readyMutexError | readyCondError | fileCloseError | allJobsMutexError | allJobsCondError | jobWriteMutexError | jobWriteCondError | cctxError; } } @@ -132,6 +134,12 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->threadError = 0; ctx->allJobsCompleted = 0; ctx->adaptParam = DEFAULT_ADAPT_PARAM; + ctx->cctx = ZSTD_createCCtx(); + if (!ctx->cctx) { + DISPLAY("Error: could not allocate ZSTD_CCtx\n"); + freeCCtx(ctx); + return NULL; + } if (!ctx->jobs) { DISPLAY("Error: could not allocate space for jobs during context creation\n"); freeCCtx(ctx); @@ -214,7 +222,7 @@ static void* compressionThread(void* arg) { unsigned const cLevel = adaptCompressionLevel(ctx); DEBUGLOG(2, "cLevel used: %u\n", cLevel); - size_t const compressedSize = ZSTD_compress(job->dst.start, job->dst.size, job->src.start, job->src.size, cLevel); + size_t const compressedSize = ZSTD_compressCCtx(ctx->cctx, job->dst.start, job->dst.size, job->src.start, job->src.size, cLevel); if (ZSTD_isError(compressedSize)) { ctx->threadError = 1; DISPLAY("Error: something went wrong during compression: %s\n", ZSTD_getErrorName(compressedSize)); From 1c9d6b2c6b26f0c916218d5ecfad8a155273c403 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 15:42:20 -0700 Subject: [PATCH 043/145] rewrote time elapsed with UTIL --- contrib/adaptive-compression/Makefile | 1 + contrib/adaptive-compression/multi.c | 38 +++++++++++++-------------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index 2ebec9ba..2d3de33e 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -1,5 +1,6 @@ ZSTDDIR = ../../lib +PRGDIR = ../../programs ZSTDCOMMON_FILES := $(ZSTDDIR)/common/*.c ZSTDCOMP_FILES := $(ZSTDDIR)/compress/*.c ZSTDDECOMP_FILES := $(ZSTDDIR)/decompress/*.c diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 943bd856..9f981f81 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -15,17 +15,16 @@ typedef unsigned char BYTE; #include /* malloc, free */ #include /* pthread functions */ #include /* memset */ -#include /* clock(), CLOCKS_PER_SEC */ #include "zstd.h" +#include "util.h" static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; static unsigned g_displayStats = 0; -static clock_t g_time = 0; -static clock_t g_startTime = 0; -static clock_t const refreshRate = CLOCKS_PER_SEC / 60; /* 60 Hz */ +static UTIL_time_t g_startTime; static size_t g_streamedSize = 0; static unsigned g_useProgressBar = 0; +static UTIL_freq_t g_ticksPerSecond; typedef struct { void* start; @@ -39,7 +38,7 @@ typedef struct { unsigned readyCounter; unsigned compressedCounter; unsigned writeCounter; -} stat_t; +} cStat_t; typedef struct { buffer_t src; @@ -69,7 +68,7 @@ typedef struct { pthread_cond_t allJobsCompleted_cond; pthread_mutex_t jobWrite_mutex; pthread_cond_t jobWrite_cond; - stat_t stats; + cStat_t stats; jobDescription* jobs; FILE* dstFile; ZSTD_CCtx* cctx; @@ -249,19 +248,17 @@ static void* compressionThread(void* arg) static void displayProgress(unsigned jobDoneID, unsigned cLevel, unsigned last) { if (!g_useProgressBar) return; - clock_t currTime = clock(); - unsigned const refresh = currTime - g_time > refreshRate ? 1 : 0; - double const timeElapsed = (double)((currTime - g_startTime) * 1000 / CLOCKS_PER_SEC); + UTIL_time_t currTime; + UTIL_getTime(&currTime); + double const timeElapsed = (double)(UTIL_getSpanTimeMicro(g_ticksPerSecond, g_startTime, currTime) / 1000.0); double const sizeMB = (double)g_streamedSize / (1 << 20); double const avgCompRate = sizeMB * 1000 / timeElapsed; - if (refresh) { - fprintf(stdout, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB | Avg Compression Rate: %6.2f MB/s |", jobDoneID, cLevel, timeElapsed, sizeMB, avgCompRate); - if (last) { - fprintf(stdout, "\n"); - } - else { - fflush(stdout); - } + fprintf(stdout, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB | Avg Compression Rate: %6.2f MB/s |", jobDoneID, cLevel, timeElapsed, sizeMB, avgCompRate); + if (last) { + fprintf(stdout, "\n"); + } + else { + fflush(stdout); } } @@ -362,7 +359,7 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) return 0; } -static void printStats(stat_t stats) +static void printStats(cStat_t stats) { DISPLAY("========STATISTICS========\n"); DISPLAY("# times waited on job ready: %u\n", stats.waitReady); @@ -379,8 +376,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst size_t const numJobs = MAX_NUM_JOBS; int ret = 0; adaptCCtx* ctx = NULL; - g_time = clock(); - g_startTime = clock(); + UTIL_getTime(&g_startTime); g_streamedSize = 0; @@ -517,6 +513,8 @@ int main(int argCount, const char* argv[]) int ret = 0; int argNum; + UTIL_initTimer(&g_ticksPerSecond); + if (filenameTable == NULL) { DISPLAY("Error: could not allocate sapce for filename table.\n"); return 1; From 7163ffafde9fdc2eac50ae12056b2cf66daa2945 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 15:56:00 -0700 Subject: [PATCH 044/145] playing around with adapt param --- contrib/adaptive-compression/multi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 9f981f81..41fd9af4 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -8,7 +8,7 @@ #define MAX_PATH 256 #define DEFAULT_DISPLAY_LEVEL 1 #define DEFAULT_COMPRESSION_LEVEL 6 -#define DEFAULT_ADAPT_PARAM 2 +#define DEFAULT_ADAPT_PARAM 1 typedef unsigned char BYTE; #include /* fprintf */ From c3ae23d459611438ede914036a6ace1b37849ad6 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 7 Jul 2017 17:07:05 -0700 Subject: [PATCH 045/145] added ability to compress without specifying out filename --- contrib/adaptive-compression/multi.c | 21 +++++++++++++-------- contrib/adaptive-compression/run.sh | 6 ++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 41fd9af4..78d9ca15 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -367,18 +367,28 @@ static void printStats(cStat_t stats) DISPLAY("# times waited on job Write: %u\n\n", stats.waitWrite); } -static int compressFilename(const char* const srcFilename, const char* const dstFilename) +static int compressFilename(const char* const srcFilename, const char* const dstFilenameOrNull) { BYTE* const src = malloc(FILE_CHUNK_SIZE); unsigned const stdinUsed = !strcmp(srcFilename, stdinmark); FILE* const srcFile = stdinUsed ? stdin : fopen(srcFilename, "rb"); - const char* const outFilename = (stdinUsed && !dstFilename) ? stdoutmark : dstFilename; + const char* const outFilenameIntermediate = (stdinUsed && !dstFilenameOrNull) ? stdoutmark : dstFilenameOrNull; + const char* outFilename = outFilenameIntermediate; + char fileAndSuffix[MAX_PATH]; size_t const numJobs = MAX_NUM_JOBS; int ret = 0; adaptCCtx* ctx = NULL; UTIL_getTime(&g_startTime); g_streamedSize = 0; + if (!outFilenameIntermediate) { + if (snprintf(fileAndSuffix, MAX_PATH, "%s.zst", srcFilename) + 1 > MAX_PATH) { + DISPLAY("Error: output filename is too long\n"); + ret = 1; + goto cleanup; + } + outFilename = fileAndSuffix; + } /* checking for errors */ if (!srcFilename || !outFilename || !src || !srcFile) { @@ -452,15 +462,10 @@ static int compressFilenames(const char** filenameTable, unsigned numFiles, unsi { int ret = 0; unsigned fileNum; - char outFile[MAX_PATH]; for (fileNum=0; fileNum MAX_PATH) { - DISPLAY("Error: output filename is too long\n"); - return 1; - } if (!forceStdout) { - ret |= compressFilename(filename, outFile); + ret |= compressFilename(filename, NULL); } else { ret |= compressFilename(filename, stdoutmark); diff --git a/contrib/adaptive-compression/run.sh b/contrib/adaptive-compression/run.sh index 67814ec1..3c40969e 100755 --- a/contrib/adaptive-compression/run.sh +++ b/contrib/adaptive-compression/run.sh @@ -151,6 +151,12 @@ echo "Running Tests With Multiple Files > stdout" zstd -d tmp.zst rm tmp* +echo "Running single test without output filename" +./multi tests/test2048.pdf -p +zstd -d tests/test2048.pdf.zst -o tmp +diff tmp tests/test2048.pdf +rm tmp* + echo "finished with tests" make clean From 62ebbabd32dfccb5363cb245c06f7affb7535727 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 09:36:22 -0700 Subject: [PATCH 046/145] updated error checking in each thread --- contrib/adaptive-compression/multi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/multi.c index 78d9ca15..4c3f584e 100644 --- a/contrib/adaptive-compression/multi.c +++ b/contrib/adaptive-compression/multi.c @@ -284,12 +284,14 @@ static void* outputThread(void* arg) size_t const compressedSize = job->compressedSize; if (ZSTD_isError(compressedSize)) { DISPLAY("Error: an error occurred during compression\n"); + ctx->threadError = 1; return arg; } { size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, ctx->dstFile); if (writeSize != compressedSize) { DISPLAY("Error: an error occurred during file write operation\n"); + ctx->threadError = 1; return arg; } } @@ -429,6 +431,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst size_t const readSize = fread(src, 1, FILE_CHUNK_SIZE, srcFile); if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); + ctx->threadError = 1; ret = 1; goto cleanup; } @@ -438,6 +441,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst int const error = createCompressionJob(ctx, src, readSize); if (error != 0) { ret = error; + ctx->threadError = 1; goto cleanup; } } From 82f0d64beefb4d6c7041ab131602b22c23e320ae Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 10:51:50 -0700 Subject: [PATCH 047/145] removed single.c --- contrib/adaptive-compression/single.c | 73 --------------------------- 1 file changed, 73 deletions(-) delete mode 100644 contrib/adaptive-compression/single.c diff --git a/contrib/adaptive-compression/single.c b/contrib/adaptive-compression/single.c deleted file mode 100644 index ef8062d8..00000000 --- a/contrib/adaptive-compression/single.c +++ /dev/null @@ -1,73 +0,0 @@ -#define DISPLAY(...) fprintf(stderr, __VA_ARGS__) -#define FILE_CHUNK_SIZE 4 << 20 -typedef unsigned char BYTE; - -#include -#include -#include "zstd.h" - - - -/* return 0 if successful, else return error */ -int main(int argCount, const char* argv[]) -{ - const char* const srcFilename = argv[1]; - const char* const dstFilename = argv[2]; - FILE* const srcFile = fopen(srcFilename, "rb"); - FILE* const dstFile = fopen(dstFilename, "wb"); - BYTE* const src = malloc(FILE_CHUNK_SIZE); - size_t const dstSize = ZSTD_compressBound(FILE_CHUNK_SIZE); - BYTE* const dst = malloc(dstSize); - int ret = 0; - - /* checking for errors */ - if (!srcFilename || !dstFilename || !src || !dst) { - DISPLAY("Error: initial variables could not be allocated\n"); - ret = 1; - goto cleanup; - } - - /* compressing in blocks */ - for ( ; ; ) { - size_t const readSize = fread(src, 1, FILE_CHUNK_SIZE, srcFile); - if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { - DISPLAY("Error: could not read %d bytes\n", FILE_CHUNK_SIZE); - ret = 1; - goto cleanup; - } - { - size_t const compressedSize = ZSTD_compress(dst, dstSize, src, readSize, 6); - if (ZSTD_isError(compressedSize)) { - DISPLAY("Error: something went wrong during compression\n"); - ret = 1; - goto cleanup; - } - { - size_t const writeSize = fwrite(dst, 1, compressedSize, dstFile); - if (writeSize != compressedSize) { - DISPLAY("Error: could not write compressed data to file\n"); - ret = 1; - goto cleanup; - } - } - } - if (feof(srcFile)) { - /* reached end of file */ - break; - } - } - - /* file compression completed */ - { - int const error = fclose(srcFile); - if (ret != 0) { - DISPLAY("Error: could not close the file\n"); - ret = error; - goto cleanup; - } - } -cleanup: - if (src != NULL) free(src); - if (dst != NULL) free(dst); - return ret; -} From ced3ec5714073fd17a4711d132a6da876617833a Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 10:53:02 -0700 Subject: [PATCH 048/145] removed scripts --- contrib/adaptive-compression/pipetests.sh | 2 - contrib/adaptive-compression/run.sh | 162 ---------------------- 2 files changed, 164 deletions(-) delete mode 100755 contrib/adaptive-compression/pipetests.sh delete mode 100755 contrib/adaptive-compression/run.sh diff --git a/contrib/adaptive-compression/pipetests.sh b/contrib/adaptive-compression/pipetests.sh deleted file mode 100755 index 71f123a1..00000000 --- a/contrib/adaptive-compression/pipetests.sh +++ /dev/null @@ -1,2 +0,0 @@ -make clean multi -pv -q -L 100m tests/test2048.pdf | ./multi -v -otmp.zst diff --git a/contrib/adaptive-compression/run.sh b/contrib/adaptive-compression/run.sh deleted file mode 100755 index 3c40969e..00000000 --- a/contrib/adaptive-compression/run.sh +++ /dev/null @@ -1,162 +0,0 @@ -make clean multi - -echo "running file tests" - -./multi tests/test2048.pdf -otmp.zst -zstd -d tmp.zst -diff tmp tests/test2048.pdf -echo "diff test complete: test2048.pdf" -rm tmp* - -./multi tests/test512.pdf -otmp.zst -zstd -d tmp.zst -diff tmp tests/test512.pdf -echo "diff test complete: test512.pdf" -rm tmp* - -./multi tests/test64.pdf -otmp.zst -zstd -d tmp.zst -diff tmp tests/test64.pdf -echo "diff test complete: test64.pdf" -rm tmp* - -./multi tests/test16.pdf -otmp.zst -zstd -d tmp.zst -diff tmp tests/test16.pdf -echo "diff test complete: test16.pdf" -rm tmp* - -./multi tests/test4.pdf -otmp.zst -zstd -d tmp.zst -diff tmp tests/test4.pdf -echo "diff test complete: test4.pdf" -rm tmp* - -./multi tests/test.pdf -otmp.zst -zstd -d tmp.zst -diff tmp tests/test.pdf -echo "diff test complete: test.pdf" -rm tmp* - -echo "Running std input/output tests" - -cat tests/test2048.pdf | ./multi -otmp.zst -zstd -d tmp.zst -diff tmp tests/test2048.pdf -echo "diff test complete: test2048.pdf" -rm tmp* - -cat tests/test512.pdf | ./multi -otmp.zst -zstd -d tmp.zst -diff tmp tests/test512.pdf -echo "diff test complete: test512.pdf" -rm tmp* - -cat tests/test64.pdf | ./multi -otmp.zst -zstd -d tmp.zst -diff tmp tests/test64.pdf -echo "diff test complete: test64.pdf" -rm tmp* - -cat tests/test16.pdf | ./multi -otmp.zst -zstd -d tmp.zst -diff tmp tests/test16.pdf -echo "diff test complete: test16.pdf" -rm tmp* - -cat tests/test4.pdf | ./multi -otmp.zst -zstd -d tmp.zst -diff tmp tests/test4.pdf -echo "diff test complete: test4.pdf" -rm tmp* - -cat tests/test.pdf | ./multi -otmp.zst -zstd -d tmp.zst -diff tmp tests/test.pdf -echo "diff test complete: test.pdf" -rm tmp* - -cat tests/test2048.pdf | ./multi > tmp.zst -zstd -d tmp.zst -diff tmp tests/test2048.pdf -echo "diff test complete: test2048.pdf" -rm tmp* - -cat tests/test512.pdf | ./multi > tmp.zst -zstd -d tmp.zst -diff tmp tests/test512.pdf -echo "diff test complete: test512.pdf" -rm tmp* - -cat tests/test64.pdf | ./multi > tmp.zst -zstd -d tmp.zst -diff tmp tests/test64.pdf -echo "diff test complete: test64.pdf" -rm tmp* - -cat tests/test16.pdf | ./multi > tmp.zst -zstd -d tmp.zst -diff tmp tests/test16.pdf -echo "diff test complete: test16.pdf" -rm tmp* - -cat tests/test4.pdf | ./multi > tmp.zst -zstd -d tmp.zst -diff tmp tests/test4.pdf -echo "diff test complete: test4.pdf" -rm tmp* - -cat tests/test.pdf | ./multi > tmp.zst -zstd -d tmp.zst -diff tmp tests/test.pdf -echo "diff test complete: test.pdf" -rm tmp* - -echo "Running multi-file tests" -./multi tests/* -zstd -d tests/test.pdf.zst -o tests/tmp -zstd -d tests/test2.pdf.zst -o tests/tmp2 -zstd -d tests/test4.pdf.zst -o tests/tmp4 -zstd -d tests/test8.pdf.zst -o tests/tmp8 -zstd -d tests/test16.pdf.zst -o tests/tmp16 -zstd -d tests/test32.pdf.zst -o tests/tmp32 -zstd -d tests/test64.pdf.zst -o tests/tmp64 -zstd -d tests/test128.pdf.zst -o tests/tmp128 -zstd -d tests/test256.pdf.zst -o tests/tmp256 -zstd -d tests/test512.pdf.zst -o tests/tmp512 -zstd -d tests/test1024.pdf.zst -o tests/tmp1024 -zstd -d tests/test2048.pdf.zst -o tests/tmp2048 - -diff tests/test.pdf tests/tmp -diff tests/test2.pdf tests/tmp2 -diff tests/test4.pdf tests/tmp4 -diff tests/test8.pdf tests/tmp8 -diff tests/test16.pdf tests/tmp16 -diff tests/test32.pdf tests/tmp32 -diff tests/test64.pdf tests/tmp64 -diff tests/test128.pdf tests/tmp128 -diff tests/test256.pdf tests/tmp256 -diff tests/test512.pdf tests/tmp512 -diff tests/test1024.pdf tests/tmp1024 -diff tests/test2048.pdf tests/tmp2048 - -rm -f tests/*.zst tests/tmp* -echo "Running Args Tests" -./multi -h -./multi -i22 -p -s -otmp.zst tests/test2048.pdf -rm tmp* - -echo "Running Tests With Multiple Files > stdout" -./multi tests/* -c > tmp.zst -zstd -d tmp.zst -rm tmp* - -echo "Running single test without output filename" -./multi tests/test2048.pdf -p -zstd -d tests/test2048.pdf.zst -o tmp -diff tmp tests/test2048.pdf -rm tmp* - -echo "finished with tests" - -make clean From ed72ea54380dc732fe9c648f9cf5ee683fad17d0 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 10:58:03 -0700 Subject: [PATCH 049/145] removed single from Makefile --- contrib/adaptive-compression/Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index 2d3de33e..3027b88c 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -19,15 +19,13 @@ CFLAGS += $(DEBUGFLAGS) CFLAGS += $(MOREFLAGS) FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -all: clean single multi -single: $(ZSTD_FILES) single.c - $(CC) $(FLAGS) $^ -o $@ +all: clean multi multi: $(ZSTD_FILES) multi.c $(CC) $(FLAGS) $^ -o $@ clean: - @$(RM) -f single multi + @$(RM) -f multi @$(RM) -rf *.dSYM @$(RM) -f tmp* @$(RM) -f tests/*.zst From 7e09b508ff5e58fb1c4272b1616d808d10110392 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 11:05:37 -0700 Subject: [PATCH 050/145] changed name --- contrib/adaptive-compression/Makefile | 6 +++--- contrib/adaptive-compression/{multi.c => adapt.c} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename contrib/adaptive-compression/{multi.c => adapt.c} (100%) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index 3027b88c..38e3a778 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -19,13 +19,13 @@ CFLAGS += $(DEBUGFLAGS) CFLAGS += $(MOREFLAGS) FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -all: clean multi +all: clean adapt -multi: $(ZSTD_FILES) multi.c +adapt: $(ZSTD_FILES) adapt.c $(CC) $(FLAGS) $^ -o $@ clean: - @$(RM) -f multi + @$(RM) -f adapt @$(RM) -rf *.dSYM @$(RM) -f tmp* @$(RM) -f tests/*.zst diff --git a/contrib/adaptive-compression/multi.c b/contrib/adaptive-compression/adapt.c similarity index 100% rename from contrib/adaptive-compression/multi.c rename to contrib/adaptive-compression/adapt.c From cc7f8e4d71bb2bb355f0c0a89de42ba176d83d64 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 11:10:11 -0700 Subject: [PATCH 051/145] small changes --- contrib/adaptive-compression/adapt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 4c3f584e..61fb3142 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -115,7 +115,7 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) } memset(ctx, 0, sizeof(adaptCCtx)); ctx->compressionLevel = g_compressionLevel; - pthread_mutex_init(&ctx->jobCompressed_mutex, NULL); /* TODO: add checks for errors on each mutex */ + pthread_mutex_init(&ctx->jobCompressed_mutex, NULL); pthread_cond_init(&ctx->jobCompressed_cond, NULL); pthread_mutex_init(&ctx->jobReady_mutex, NULL); pthread_cond_init(&ctx->jobReady_cond, NULL); From e410d63d458142c3930341637f05bcc3c9397e78 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 15:37:14 -0700 Subject: [PATCH 052/145] made input buffer an internal part of the compression context --- contrib/adaptive-compression/adapt.c | 29 ++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 61fb3142..f87bdf8c 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -31,6 +31,11 @@ typedef struct { size_t size; } buffer_t; +typedef struct { + size_t filled; + buffer_t buffer; +} inBuff_t; + typedef struct { unsigned waitCompressed; unsigned waitReady; @@ -68,6 +73,7 @@ typedef struct { pthread_cond_t allJobsCompleted_cond; pthread_mutex_t jobWrite_mutex; pthread_cond_t jobWrite_cond; + inBuff_t input; cStat_t stats; jobDescription* jobs; FILE* dstFile; @@ -97,6 +103,7 @@ static int freeCCtx(adaptCCtx* ctx) int const jobWriteCondError = pthread_cond_destroy(&ctx->jobWrite_cond); int const fileCloseError = (ctx->dstFile != NULL && ctx->dstFile != stdout) ? fclose(ctx->dstFile) : 0; int const cctxError = ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)) ? 1 : 0; + free(ctx->input.buffer.start); if (ctx->jobs){ freeCompressionJobs(ctx); free(ctx->jobs); @@ -115,7 +122,7 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) } memset(ctx, 0, sizeof(adaptCCtx)); ctx->compressionLevel = g_compressionLevel; - pthread_mutex_init(&ctx->jobCompressed_mutex, NULL); + pthread_mutex_init(&ctx->jobCompressed_mutex, NULL); pthread_cond_init(&ctx->jobCompressed_cond, NULL); pthread_mutex_init(&ctx->jobReady_mutex, NULL); pthread_cond_init(&ctx->jobReady_cond, NULL); @@ -134,6 +141,14 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->allJobsCompleted = 0; ctx->adaptParam = DEFAULT_ADAPT_PARAM; ctx->cctx = ZSTD_createCCtx(); + ctx->input.filled = 0; + ctx->input.buffer.size = 2 * FILE_CHUNK_SIZE; + ctx->input.buffer.start = malloc(ctx->input.buffer.size); + if (!ctx->input.buffer.start) { + DISPLAY("Error: could not allocate input buffer\n"); + freeCCtx(ctx); + return NULL; + } if (!ctx->cctx) { DISPLAY("Error: could not allocate ZSTD_CCtx\n"); freeCCtx(ctx); @@ -320,7 +335,7 @@ static void* outputThread(void* arg) return arg; } -static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) +static int createCompressionJob(adaptCCtx* ctx, size_t srcSize) { unsigned const nextJob = ctx->nextJobID; unsigned const nextJobIndex = nextJob % ctx->numJobs; @@ -351,7 +366,7 @@ static int createCompressionJob(adaptCCtx* ctx, BYTE* data, size_t srcSize) free(job->dst.start); return 1; } - memcpy(job->src.start, data, srcSize); + memcpy(job->src.start, ctx->input.buffer.start, srcSize); pthread_mutex_lock(&ctx->jobReady_mutex); ctx->jobReadyID++; pthread_cond_signal(&ctx->jobReady_cond); @@ -371,7 +386,6 @@ static void printStats(cStat_t stats) static int compressFilename(const char* const srcFilename, const char* const dstFilenameOrNull) { - BYTE* const src = malloc(FILE_CHUNK_SIZE); unsigned const stdinUsed = !strcmp(srcFilename, stdinmark); FILE* const srcFile = stdinUsed ? stdin : fopen(srcFilename, "rb"); const char* const outFilenameIntermediate = (stdinUsed && !dstFilenameOrNull) ? stdoutmark : dstFilenameOrNull; @@ -393,7 +407,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst } /* checking for errors */ - if (!srcFilename || !outFilename || !src || !srcFile) { + if (!srcFilename || !outFilename || !srcFile) { DISPLAY("Error: initial variables could not be allocated\n"); ret = 1; goto cleanup; @@ -428,7 +442,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst /* creating jobs */ for ( ; ; ) { - size_t const readSize = fread(src, 1, FILE_CHUNK_SIZE, srcFile); + size_t const readSize = fread(ctx->input.buffer.start, 1, FILE_CHUNK_SIZE, srcFile); if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); ctx->threadError = 1; @@ -438,7 +452,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst g_streamedSize += readSize; /* reading was fine, now create the compression job */ { - int const error = createCompressionJob(ctx, src, readSize); + int const error = createCompressionJob(ctx, readSize); if (error != 0) { ret = error; ctx->threadError = 1; @@ -458,7 +472,6 @@ cleanup: /* file compression completed */ ret |= (srcFile != NULL) ? fclose(srcFile) : 0; ret |= (ctx != NULL) ? freeCCtx(ctx) : 0; - free(src); return ret; } From 7aa36df6df018bf7611b5c469d6cbfb8f3f05a6e Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 16:03:09 -0700 Subject: [PATCH 053/145] fixed memory leak that was happening when creating jobs --- contrib/adaptive-compression/adapt.c | 57 +++++++++++++++------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index f87bdf8c..1299264d 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -92,24 +92,24 @@ static void freeCompressionJobs(adaptCCtx* ctx) static int freeCCtx(adaptCCtx* ctx) { - { - int const compressedMutexError = pthread_mutex_destroy(&ctx->jobCompressed_mutex); - int const compressedCondError = pthread_cond_destroy(&ctx->jobCompressed_cond); - int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); - int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); - int const allJobsMutexError = pthread_mutex_destroy(&ctx->allJobsCompleted_mutex); - int const allJobsCondError = pthread_cond_destroy(&ctx->allJobsCompleted_cond); - int const jobWriteMutexError = pthread_mutex_destroy(&ctx->jobWrite_mutex); - int const jobWriteCondError = pthread_cond_destroy(&ctx->jobWrite_cond); - int const fileCloseError = (ctx->dstFile != NULL && ctx->dstFile != stdout) ? fclose(ctx->dstFile) : 0; - int const cctxError = ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)) ? 1 : 0; - free(ctx->input.buffer.start); - if (ctx->jobs){ - freeCompressionJobs(ctx); - free(ctx->jobs); - } - return compressedMutexError | compressedCondError | readyMutexError | readyCondError | fileCloseError | allJobsMutexError | allJobsCondError | jobWriteMutexError | jobWriteCondError | cctxError; + if (!ctx) return 0; + int const compressedMutexError = pthread_mutex_destroy(&ctx->jobCompressed_mutex); + int const compressedCondError = pthread_cond_destroy(&ctx->jobCompressed_cond); + int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); + int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); + int const allJobsMutexError = pthread_mutex_destroy(&ctx->allJobsCompleted_mutex); + int const allJobsCondError = pthread_cond_destroy(&ctx->allJobsCompleted_cond); + int const jobWriteMutexError = pthread_mutex_destroy(&ctx->jobWrite_mutex); + int const jobWriteCondError = pthread_cond_destroy(&ctx->jobWrite_cond); + int const fileCloseError = (ctx->dstFile != NULL && ctx->dstFile != stdout) ? fclose(ctx->dstFile) : 0; + int const cctxError = ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)) ? 1 : 0; + free(ctx->input.buffer.start); + if (ctx->jobs){ + freeCompressionJobs(ctx); + free(ctx->jobs); } + free(ctx); + return compressedMutexError | compressedCondError | readyMutexError | readyCondError | fileCloseError | allJobsMutexError | allJobsCondError | jobWriteMutexError | jobWriteCondError | cctxError; } static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) @@ -136,6 +136,20 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->jobWriteID = 0; ctx->lastJobID = -1; /* intentional underflow */ ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); + /* allocating buffers for jobs */ + { + unsigned jobNum; + for (jobNum=0; jobNumjobs[jobNum]; + job->src.start = malloc(FILE_CHUNK_SIZE); + job->dst.start = malloc(FILE_CHUNK_SIZE); + if (!job->src.start || !job->dst.start) { + DISPLAY("Could not allocate buffers for jobs\n"); + freeCCtx(ctx); + return NULL; + } + } + } ctx->nextJobID = 0; ctx->threadError = 0; ctx->allJobsCompleted = 0; @@ -354,18 +368,9 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize) job->compressionLevel = ctx->compressionLevel; - job->src.start = malloc(srcSize); job->src.size = srcSize; job->dst.size = ZSTD_compressBound(srcSize); - job->dst.start = malloc(job->dst.size); job->jobID = nextJob; - if (!job->src.start || !job->dst.start) { - /* problem occurred, free things then return */ - DISPLAY("Error: problem occurred during job creation\n"); - free(job->src.start); - free(job->dst.start); - return 1; - } memcpy(job->src.start, ctx->input.buffer.start, srcSize); pthread_mutex_lock(&ctx->jobReady_mutex); ctx->jobReadyID++; From c36552ef8a8aef2c097cba517346e8a7d5f0582c Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 16:10:19 -0700 Subject: [PATCH 054/145] dst buffer should use ZSTD_compressBound to determine how much space it needs --- contrib/adaptive-compression/adapt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 1299264d..e1ac0aaf 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -142,7 +142,7 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) for (jobNum=0; jobNumjobs[jobNum]; job->src.start = malloc(FILE_CHUNK_SIZE); - job->dst.start = malloc(FILE_CHUNK_SIZE); + job->dst.start = malloc(ZSTD_compressBound(FILE_CHUNK_SIZE)); if (!job->src.start || !job->dst.start) { DISPLAY("Could not allocate buffers for jobs\n"); freeCCtx(ctx); From 01fc7c42441ef75ead51f4289bdf13a799993ad9 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 16:27:58 -0700 Subject: [PATCH 055/145] changed how the detection of the last job works --- contrib/adaptive-compression/adapt.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index e1ac0aaf..8f3acd5c 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -50,6 +50,7 @@ typedef struct { buffer_t dst; unsigned compressionLevel; unsigned jobID; + unsigned lastJob; size_t compressedSize; } jobDescription; @@ -57,7 +58,6 @@ typedef struct { unsigned compressionLevel; unsigned numActiveThreads; unsigned numJobs; - unsigned lastJobID; unsigned nextJobID; unsigned threadError; unsigned jobReadyID; @@ -134,15 +134,15 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->jobReadyID = 0; ctx->jobCompressedID = 0; ctx->jobWriteID = 0; - ctx->lastJobID = -1; /* intentional underflow */ ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); - /* allocating buffers for jobs */ + /* initializing jobs */ { unsigned jobNum; for (jobNum=0; jobNumjobs[jobNum]; job->src.start = malloc(FILE_CHUNK_SIZE); job->dst.start = malloc(ZSTD_compressBound(FILE_CHUNK_SIZE)); + job->lastJob = 0; if (!job->src.start || !job->dst.start) { DISPLAY("Could not allocate buffers for jobs\n"); freeCCtx(ctx); @@ -265,7 +265,7 @@ static void* compressionThread(void* arg) pthread_mutex_unlock(&ctx->jobCompressed_mutex); DEBUGLOG(2, "finished job compression %u\n", currJob); currJob++; - if (currJob >= ctx->lastJobID || ctx->threadError) { + if (job->lastJob || ctx->threadError) { /* finished compressing all jobs */ DEBUGLOG(2, "all jobs finished compressing\n"); break; @@ -327,7 +327,7 @@ static void* outputThread(void* arg) } DEBUGLOG(2, "finished job write %u\n", currJob); currJob++; - displayProgress(currJob, ctx->compressionLevel, currJob >= ctx->lastJobID); + displayProgress(currJob, ctx->compressionLevel, job->lastJob); DEBUGLOG(2, "locking job write mutex\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); ctx->jobWriteID++; @@ -335,8 +335,7 @@ static void* outputThread(void* arg) pthread_mutex_unlock(&ctx->jobWrite_mutex); DEBUGLOG(2, "unlocking job write mutex\n"); - DEBUGLOG(2, "checking if done: %u/%u\n", currJob, ctx->lastJobID); - if (currJob >= ctx->lastJobID || ctx->threadError) { + if (job->lastJob || ctx->threadError) { /* finished with all jobs */ DEBUGLOG(2, "all jobs finished writing\n"); pthread_mutex_lock(&ctx->allJobsCompleted_mutex); @@ -349,7 +348,7 @@ static void* outputThread(void* arg) return arg; } -static int createCompressionJob(adaptCCtx* ctx, size_t srcSize) +static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) { unsigned const nextJob = ctx->nextJobID; unsigned const nextJobIndex = nextJob % ctx->numJobs; @@ -371,6 +370,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize) job->src.size = srcSize; job->dst.size = ZSTD_compressBound(srcSize); job->jobID = nextJob; + job->lastJob = last; memcpy(job->src.start, ctx->input.buffer.start, srcSize); pthread_mutex_lock(&ctx->jobReady_mutex); ctx->jobReadyID++; @@ -457,7 +457,8 @@ static int compressFilename(const char* const srcFilename, const char* const dst g_streamedSize += readSize; /* reading was fine, now create the compression job */ { - int const error = createCompressionJob(ctx, readSize); + int const last = feof(srcFile); + int const error = createCompressionJob(ctx, readSize, last); if (error != 0) { ret = error; ctx->threadError = 1; @@ -466,7 +467,6 @@ static int compressFilename(const char* const srcFilename, const char* const dst } if (feof(srcFile)) { DEBUGLOG(2, "THE STREAM OF DATA ENDED %u\n", ctx->nextJobID); - ctx->lastJobID = ctx->nextJobID; break; } } From f91854549111eb90ba5f7f15d142b8687e4ab74f Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 10 Jul 2017 18:16:42 -0700 Subject: [PATCH 056/145] made some progress on improving compression ratio, but problems exist with speed limits, and for some reason higher compression levels are really slow --- contrib/adaptive-compression/adapt.c | 102 ++++++++++++++++++--------- 1 file changed, 69 insertions(+), 33 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 8f3acd5c..2bf8bb81 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -1,6 +1,6 @@ #define DISPLAY(...) fprintf(stderr, __VA_ARGS__) #define PRINT(...) fprintf(stdout, __VA_ARGS__) -#define DEBUGLOG(l, ...) { if (g_displayLevel>=l) { DISPLAY(__VA_ARGS__); } } +#define DEBUG(l, ...) { if (g_displayLevel>=l) { DISPLAY(__VA_ARGS__); } } #define FILE_CHUNK_SIZE 4 << 20 #define MAX_NUM_JOBS 2; #define stdinmark "/*stdin*\\" @@ -15,7 +15,7 @@ typedef unsigned char BYTE; #include /* malloc, free */ #include /* pthread functions */ #include /* memset */ -#include "zstd.h" +#include "zstd_internal.h" #include "util.h" static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; @@ -48,6 +48,7 @@ typedef struct { typedef struct { buffer_t src; buffer_t dst; + buffer_t dict; unsigned compressionLevel; unsigned jobID; unsigned lastJob; @@ -87,6 +88,7 @@ static void freeCompressionJobs(adaptCCtx* ctx) jobDescription job = ctx->jobs[u]; free(job.dst.start); free(job.src.start); + free(job.dict.start); } } @@ -142,8 +144,9 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) jobDescription* job = &ctx->jobs[jobNum]; job->src.start = malloc(FILE_CHUNK_SIZE); job->dst.start = malloc(ZSTD_compressBound(FILE_CHUNK_SIZE)); + job->dict.start = malloc(FILE_CHUNK_SIZE); job->lastJob = 0; - if (!job->src.start || !job->dst.start) { + if (!job->src.start || !job->dst.start || !job->dict.start) { DISPLAY("Could not allocate buffers for jobs\n"); freeCCtx(ctx); return NULL; @@ -207,17 +210,17 @@ static unsigned adaptCompressionLevel(adaptCCtx* ctx) unsigned const writeSlow = ((compressWaiting && createWaiting) || (createWaiting && !writeWaiting)) ? 1 : 0; unsigned const compressSlow = ((writeWaiting && createWaiting) || (writeWaiting && !compressWaiting)) ? 1 : 0; unsigned const createSlow = ((compressWaiting && writeWaiting) || (compressWaiting && !createWaiting)) ? 1 : 0; - DEBUGLOG(2, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); + DEBUG(2, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); if (allSlow) { reset = 1; } else if ((writeSlow || createSlow) && ctx->compressionLevel < (unsigned)ZSTD_maxCLevel()) { - DEBUGLOG(2, "increasing compression level %u\n", ctx->compressionLevel); + DEBUG(2, "increasing compression level %u\n", ctx->compressionLevel); ctx->compressionLevel++; reset = 1; } else if (compressSlow && ctx->compressionLevel > 1) { - DEBUGLOG(2, "decreasing compression level %u\n", ctx->compressionLevel); + DEBUG(2, "decreasing compression level %u\n", ctx->compressionLevel); ctx->compressionLevel--; reset = 1; } @@ -236,38 +239,63 @@ static void* compressionThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; - DEBUGLOG(2, "compressionThread(): waiting on job ready\n"); + DEBUG(2, "compressionThread(): waiting on job ready\n"); pthread_mutex_lock(&ctx->jobReady_mutex); while(currJob + 1 > ctx->jobReadyID) { ctx->stats.waitReady++; ctx->stats.readyCounter++; - DEBUGLOG(2, "waiting on job ready, nextJob: %u\n", currJob); + DEBUG(2, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond, &ctx->jobReady_mutex); } pthread_mutex_unlock(&ctx->jobReady_mutex); - DEBUGLOG(2, "compressionThread(): continuing after job ready\n"); + DEBUG(2, "compressionThread(): continuing after job ready\n"); /* compress the data */ { unsigned const cLevel = adaptCompressionLevel(ctx); - DEBUGLOG(2, "cLevel used: %u\n", cLevel); - size_t const compressedSize = ZSTD_compressCCtx(ctx->cctx, job->dst.start, job->dst.size, job->src.start, job->src.size, cLevel); - if (ZSTD_isError(compressedSize)) { + ZSTD_parameters params = ZSTD_getParams(cLevel, job->src.size, 0); + DEBUG(2, "cLevel used: %u\n", cLevel); + + /* begin compression */ + { + size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); + size_t const initError = ZSTD_compressBegin_advanced(ctx->cctx, job->dict.start, job->dict.size, params, 0); + size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); + if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { + DISPLAY("Error: something went wrong while starting compression\n"); + ctx->threadError = 1; + return arg; + } + } + + /* continue compression */ + if (currJob != 0) { /* not first job */ + size_t const hSize = ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.size, job->src.start, 0); + if (ZSTD_isError(hSize)) { + job->compressedSize = hSize; + ctx->threadError = 1; + return arg; + } + ZSTD_invalidateRepCodes(ctx->cctx); + } + job->compressedSize = (job->lastJob) ? + ZSTD_compressEnd (ctx->cctx, job->dst.start, job->dst.size, job->src.start, job->src.size) : + ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.size, job->src.start, job->src.size); + if (ZSTD_isError(job->compressedSize)) { + DISPLAY("Error: something went wrong during compression: %s\n", ZSTD_getErrorName(job->compressedSize)); ctx->threadError = 1; - DISPLAY("Error: something went wrong during compression: %s\n", ZSTD_getErrorName(compressedSize)); return arg; } - job->compressedSize = compressedSize; } pthread_mutex_lock(&ctx->jobCompressed_mutex); ctx->jobCompressedID++; - DEBUGLOG(2, "signaling for job %u\n", currJob); + DEBUG(2, "signaling for job %u\n", currJob); pthread_cond_signal(&ctx->jobCompressed_cond); pthread_mutex_unlock(&ctx->jobCompressed_mutex); - DEBUGLOG(2, "finished job compression %u\n", currJob); + DEBUG(2, "finished job compression %u\n", currJob); currJob++; if (job->lastJob || ctx->threadError) { /* finished compressing all jobs */ - DEBUGLOG(2, "all jobs finished compressing\n"); + DEBUG(2, "all jobs finished compressing\n"); break; } } @@ -299,16 +327,16 @@ static void* outputThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; - DEBUGLOG(2, "outputThread(): waiting on job compressed\n"); + DEBUG(2, "outputThread(): waiting on job compressed\n"); pthread_mutex_lock(&ctx->jobCompressed_mutex); while (currJob + 1 > ctx->jobCompressedID) { ctx->stats.waitCompressed++; ctx->stats.compressedCounter++; - DEBUGLOG(2, "waiting on job compressed, nextJob: %u\n", currJob); + DEBUG(2, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond, &ctx->jobCompressed_mutex); } pthread_mutex_unlock(&ctx->jobCompressed_mutex); - DEBUGLOG(2, "outputThread(): continuing after job compressed\n"); + DEBUG(2, "outputThread(): continuing after job compressed\n"); { size_t const compressedSize = job->compressedSize; if (ZSTD_isError(compressedSize)) { @@ -325,19 +353,19 @@ static void* outputThread(void* arg) } } } - DEBUGLOG(2, "finished job write %u\n", currJob); + DEBUG(2, "finished job write %u\n", currJob); currJob++; displayProgress(currJob, ctx->compressionLevel, job->lastJob); - DEBUGLOG(2, "locking job write mutex\n"); + DEBUG(2, "locking job write mutex\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); ctx->jobWriteID++; pthread_cond_signal(&ctx->jobWrite_cond); pthread_mutex_unlock(&ctx->jobWrite_mutex); - DEBUGLOG(2, "unlocking job write mutex\n"); + DEBUG(2, "unlocking job write mutex\n"); if (job->lastJob || ctx->threadError) { /* finished with all jobs */ - DEBUGLOG(2, "all jobs finished writing\n"); + DEBUG(2, "all jobs finished writing\n"); pthread_mutex_lock(&ctx->allJobsCompleted_mutex); ctx->allJobsCompleted = 1; pthread_cond_signal(&ctx->allJobsCompleted_cond); @@ -353,17 +381,17 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) unsigned const nextJob = ctx->nextJobID; unsigned const nextJobIndex = nextJob % ctx->numJobs; jobDescription* job = &ctx->jobs[nextJobIndex]; - DEBUGLOG(2, "createCompressionJob(): wait for job write\n"); + DEBUG(2, "createCompressionJob(): wait for job write\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); - DEBUGLOG(2, "Creating new compression job -- nextJob: %u, jobCompressedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompressedID, ctx->jobWriteID, ctx->numJobs); + DEBUG(2, "Creating new compression job -- nextJob: %u, jobCompressedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompressedID, ctx->jobWriteID, ctx->numJobs); while (nextJob - ctx->jobWriteID >= ctx->numJobs) { ctx->stats.waitWrite++; ctx->stats.writeCounter++; - DEBUGLOG(2, "waiting on job Write, nextJob: %u\n", nextJob); + DEBUG(2, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond, &ctx->jobWrite_mutex); } pthread_mutex_unlock(&ctx->jobWrite_mutex); - DEBUGLOG(2, "createCompressionJob(): continuing after job write\n"); + DEBUG(2, "createCompressionJob(): continuing after job write\n"); job->compressionLevel = ctx->compressionLevel; @@ -371,13 +399,21 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) job->dst.size = ZSTD_compressBound(srcSize); job->jobID = nextJob; job->lastJob = last; - memcpy(job->src.start, ctx->input.buffer.start, srcSize); + memcpy(job->src.start, ctx->input.buffer.start + ctx->input.filled, srcSize); + job->dict.size = ctx->input.filled; + memcpy(job->dict.start, ctx->input.buffer.start, ctx->input.filled); pthread_mutex_lock(&ctx->jobReady_mutex); ctx->jobReadyID++; pthread_cond_signal(&ctx->jobReady_cond); pthread_mutex_unlock(&ctx->jobReady_mutex); - DEBUGLOG(2, "finished job creation %u\n", nextJob); + DEBUG(2, "finished job creation %u\n", nextJob); ctx->nextJobID++; + + /* if not on the last job, reuse data as dictionary in next job */ + if (!last) { + ctx->input.filled = srcSize; + memmove(ctx->input.buffer.start, ctx->input.buffer.start + ctx->input.filled, srcSize); + } return 0; } @@ -447,7 +483,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst /* creating jobs */ for ( ; ; ) { - size_t const readSize = fread(ctx->input.buffer.start, 1, FILE_CHUNK_SIZE, srcFile); + size_t const readSize = fread(ctx->input.buffer.start + ctx->input.filled, 1, FILE_CHUNK_SIZE, srcFile); if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); ctx->threadError = 1; @@ -466,7 +502,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst } } if (feof(srcFile)) { - DEBUGLOG(2, "THE STREAM OF DATA ENDED %u\n", ctx->nextJobID); + DEBUG(2, "THE STREAM OF DATA ENDED %u\n", ctx->nextJobID); break; } } @@ -563,7 +599,7 @@ int main(int argCount, const char* argv[]) case 'i': argument += 2; g_compressionLevel = readU32FromChar(&argument); - DEBUGLOG(2, "g_compressionLevel: %u\n", g_compressionLevel); + DEBUG(2, "g_compressionLevel: %u\n", g_compressionLevel); break; case 's': g_displayStats = 1; From 7ec5928626b74f9f91b3de09026584ab41b7d4ad Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 11 Jul 2017 10:23:25 -0700 Subject: [PATCH 057/145] fixed an error where -c argument wasn't working for single files --- contrib/adaptive-compression/adapt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 2bf8bb81..e03bcc8e 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -2,7 +2,7 @@ #define PRINT(...) fprintf(stdout, __VA_ARGS__) #define DEBUG(l, ...) { if (g_displayLevel>=l) { DISPLAY(__VA_ARGS__); } } #define FILE_CHUNK_SIZE 4 << 20 -#define MAX_NUM_JOBS 2; +#define MAX_NUM_JOBS 2 #define stdinmark "/*stdin*\\" #define stdoutmark "/*stdout*\\" #define MAX_PATH 256 @@ -612,6 +612,7 @@ int main(int argCount, const char* argv[]) break; case 'c': forceStdout = 1; + outFilename = stdoutmark; break; default: DISPLAY("Error: invalid argument provided\n"); From 34afb9b23e0df74e8a9261d8e7e05b8f762d5738 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 11 Jul 2017 11:50:00 -0700 Subject: [PATCH 058/145] changed to using ZSTD_compressBegin_usingDict() and fixed strange issue with ZSTD_compressContinue() --- contrib/adaptive-compression/adapt.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index e03bcc8e..9a527ff5 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -252,13 +252,12 @@ static void* compressionThread(void* arg) /* compress the data */ { unsigned const cLevel = adaptCompressionLevel(ctx); - ZSTD_parameters params = ZSTD_getParams(cLevel, job->src.size, 0); DEBUG(2, "cLevel used: %u\n", cLevel); /* begin compression */ { size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); - size_t const initError = ZSTD_compressBegin_advanced(ctx->cctx, job->dict.start, job->dict.size, params, 0); + size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->dict.start, job->dict.size, cLevel); size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { DISPLAY("Error: something went wrong while starting compression\n"); @@ -269,7 +268,7 @@ static void* compressionThread(void* arg) /* continue compression */ if (currJob != 0) { /* not first job */ - size_t const hSize = ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.size, job->src.start, 0); + size_t const hSize = ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.size, job->src.start, job->src.size); if (ZSTD_isError(hSize)) { job->compressedSize = hSize; ctx->threadError = 1; From a3c077b8c6a88e5c1f3bcfe61cc010c3f1721760 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 11 Jul 2017 15:00:52 -0700 Subject: [PATCH 059/145] added error message, updated copying dictionary into the input buffer --- contrib/adaptive-compression/adapt.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 9a527ff5..2bd1c4f9 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -270,6 +270,7 @@ static void* compressionThread(void* arg) if (currJob != 0) { /* not first job */ size_t const hSize = ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.size, job->src.start, job->src.size); if (ZSTD_isError(hSize)) { + DISPLAY("Error: something went wrong while continuing compression\n"); job->compressedSize = hSize; ctx->threadError = 1; return arg; @@ -410,8 +411,10 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) /* if not on the last job, reuse data as dictionary in next job */ if (!last) { - ctx->input.filled = srcSize; - memmove(ctx->input.buffer.start, ctx->input.buffer.start + ctx->input.filled, srcSize); + size_t const newDictSize = srcSize; + size_t const oldDictSize = ctx->input.filled; + memmove(ctx->input.buffer.start, ctx->input.buffer.start + oldDictSize + srcSize - newDictSize, newDictSize); + ctx->input.filled = newDictSize; } return 0; } From 7c54e09347e47807b5c037cb5d90441560dc80ff Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 11 Jul 2017 15:15:41 -0700 Subject: [PATCH 060/145] updated DEBUG statements --- contrib/adaptive-compression/adapt.c | 48 ++++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 2bd1c4f9..89a20a50 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -210,17 +210,17 @@ static unsigned adaptCompressionLevel(adaptCCtx* ctx) unsigned const writeSlow = ((compressWaiting && createWaiting) || (createWaiting && !writeWaiting)) ? 1 : 0; unsigned const compressSlow = ((writeWaiting && createWaiting) || (writeWaiting && !compressWaiting)) ? 1 : 0; unsigned const createSlow = ((compressWaiting && writeWaiting) || (compressWaiting && !createWaiting)) ? 1 : 0; - DEBUG(2, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); + DEBUG(3, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); if (allSlow) { reset = 1; } else if ((writeSlow || createSlow) && ctx->compressionLevel < (unsigned)ZSTD_maxCLevel()) { - DEBUG(2, "increasing compression level %u\n", ctx->compressionLevel); + DEBUG(3, "increasing compression level %u\n", ctx->compressionLevel); ctx->compressionLevel++; reset = 1; } else if (compressSlow && ctx->compressionLevel > 1) { - DEBUG(2, "decreasing compression level %u\n", ctx->compressionLevel); + DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); ctx->compressionLevel--; reset = 1; } @@ -239,20 +239,20 @@ static void* compressionThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; - DEBUG(2, "compressionThread(): waiting on job ready\n"); + DEBUG(3, "compressionThread(): waiting on job ready\n"); pthread_mutex_lock(&ctx->jobReady_mutex); while(currJob + 1 > ctx->jobReadyID) { ctx->stats.waitReady++; ctx->stats.readyCounter++; - DEBUG(2, "waiting on job ready, nextJob: %u\n", currJob); + DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond, &ctx->jobReady_mutex); } pthread_mutex_unlock(&ctx->jobReady_mutex); - DEBUG(2, "compressionThread(): continuing after job ready\n"); + DEBUG(3, "compressionThread(): continuing after job ready\n"); /* compress the data */ { unsigned const cLevel = adaptCompressionLevel(ctx); - DEBUG(2, "cLevel used: %u\n", cLevel); + DEBUG(3, "cLevel used: %u\n", cLevel); /* begin compression */ { @@ -288,14 +288,14 @@ static void* compressionThread(void* arg) } pthread_mutex_lock(&ctx->jobCompressed_mutex); ctx->jobCompressedID++; - DEBUG(2, "signaling for job %u\n", currJob); + DEBUG(3, "signaling for job %u\n", currJob); pthread_cond_signal(&ctx->jobCompressed_cond); pthread_mutex_unlock(&ctx->jobCompressed_mutex); - DEBUG(2, "finished job compression %u\n", currJob); + DEBUG(3, "finished job compression %u\n", currJob); currJob++; if (job->lastJob || ctx->threadError) { /* finished compressing all jobs */ - DEBUG(2, "all jobs finished compressing\n"); + DEBUG(3, "all jobs finished compressing\n"); break; } } @@ -327,16 +327,16 @@ static void* outputThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; - DEBUG(2, "outputThread(): waiting on job compressed\n"); + DEBUG(3, "outputThread(): waiting on job compressed\n"); pthread_mutex_lock(&ctx->jobCompressed_mutex); while (currJob + 1 > ctx->jobCompressedID) { ctx->stats.waitCompressed++; ctx->stats.compressedCounter++; - DEBUG(2, "waiting on job compressed, nextJob: %u\n", currJob); + DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond, &ctx->jobCompressed_mutex); } pthread_mutex_unlock(&ctx->jobCompressed_mutex); - DEBUG(2, "outputThread(): continuing after job compressed\n"); + DEBUG(3, "outputThread(): continuing after job compressed\n"); { size_t const compressedSize = job->compressedSize; if (ZSTD_isError(compressedSize)) { @@ -353,19 +353,19 @@ static void* outputThread(void* arg) } } } - DEBUG(2, "finished job write %u\n", currJob); + DEBUG(3, "finished job write %u\n", currJob); currJob++; displayProgress(currJob, ctx->compressionLevel, job->lastJob); - DEBUG(2, "locking job write mutex\n"); + DEBUG(3, "locking job write mutex\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); ctx->jobWriteID++; pthread_cond_signal(&ctx->jobWrite_cond); pthread_mutex_unlock(&ctx->jobWrite_mutex); - DEBUG(2, "unlocking job write mutex\n"); + DEBUG(3, "unlocking job write mutex\n"); if (job->lastJob || ctx->threadError) { /* finished with all jobs */ - DEBUG(2, "all jobs finished writing\n"); + DEBUG(3, "all jobs finished writing\n"); pthread_mutex_lock(&ctx->allJobsCompleted_mutex); ctx->allJobsCompleted = 1; pthread_cond_signal(&ctx->allJobsCompleted_cond); @@ -381,17 +381,17 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) unsigned const nextJob = ctx->nextJobID; unsigned const nextJobIndex = nextJob % ctx->numJobs; jobDescription* job = &ctx->jobs[nextJobIndex]; - DEBUG(2, "createCompressionJob(): wait for job write\n"); + DEBUG(3, "createCompressionJob(): wait for job write\n"); pthread_mutex_lock(&ctx->jobWrite_mutex); - DEBUG(2, "Creating new compression job -- nextJob: %u, jobCompressedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompressedID, ctx->jobWriteID, ctx->numJobs); + DEBUG(3, "Creating new compression job -- nextJob: %u, jobCompressedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompressedID, ctx->jobWriteID, ctx->numJobs); while (nextJob - ctx->jobWriteID >= ctx->numJobs) { ctx->stats.waitWrite++; ctx->stats.writeCounter++; - DEBUG(2, "waiting on job Write, nextJob: %u\n", nextJob); + DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond, &ctx->jobWrite_mutex); } pthread_mutex_unlock(&ctx->jobWrite_mutex); - DEBUG(2, "createCompressionJob(): continuing after job write\n"); + DEBUG(3, "createCompressionJob(): continuing after job write\n"); job->compressionLevel = ctx->compressionLevel; @@ -406,7 +406,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) ctx->jobReadyID++; pthread_cond_signal(&ctx->jobReady_cond); pthread_mutex_unlock(&ctx->jobReady_mutex); - DEBUG(2, "finished job creation %u\n", nextJob); + DEBUG(3, "finished job creation %u\n", nextJob); ctx->nextJobID++; /* if not on the last job, reuse data as dictionary in next job */ @@ -504,7 +504,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst } } if (feof(srcFile)) { - DEBUG(2, "THE STREAM OF DATA ENDED %u\n", ctx->nextJobID); + DEBUG(3, "THE STREAM OF DATA ENDED %u\n", ctx->nextJobID); break; } } @@ -601,7 +601,7 @@ int main(int argCount, const char* argv[]) case 'i': argument += 2; g_compressionLevel = readU32FromChar(&argument); - DEBUG(2, "g_compressionLevel: %u\n", g_compressionLevel); + DEBUG(3, "g_compressionLevel: %u\n", g_compressionLevel); break; case 's': g_displayStats = 1; From 72a183efad16ace8a3fb48e9fc40ff6df56135c9 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 11 Jul 2017 15:49:52 -0700 Subject: [PATCH 061/145] changed dictionary size, added debugging statements --- contrib/adaptive-compression/adapt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 89a20a50..ad3d7f7a 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -253,7 +253,7 @@ static void* compressionThread(void* arg) { unsigned const cLevel = adaptCompressionLevel(ctx); DEBUG(3, "cLevel used: %u\n", cLevel); - + DEBUG(2, "dictSize: %zu, srcSize: %zu\n", job->dict.size, job->src.size); /* begin compression */ { size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); @@ -408,10 +408,10 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) pthread_mutex_unlock(&ctx->jobReady_mutex); DEBUG(3, "finished job creation %u\n", nextJob); ctx->nextJobID++; - + DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); /* if not on the last job, reuse data as dictionary in next job */ if (!last) { - size_t const newDictSize = srcSize; + size_t const newDictSize = srcSize/16; size_t const oldDictSize = ctx->input.filled; memmove(ctx->input.buffer.start, ctx->input.buffer.start + oldDictSize + srcSize - newDictSize, newDictSize); ctx->input.filled = newDictSize; From 0a401852c46b3ab238f20e3e775e0e1966afc05f Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 11 Jul 2017 16:50:50 -0700 Subject: [PATCH 062/145] added debug statement --- contrib/adaptive-compression/adapt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index ad3d7f7a..b0bcb74f 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -254,6 +254,7 @@ static void* compressionThread(void* arg) unsigned const cLevel = adaptCompressionLevel(ctx); DEBUG(3, "cLevel used: %u\n", cLevel); DEBUG(2, "dictSize: %zu, srcSize: %zu\n", job->dict.size, job->src.size); + DEBUG(2, "compression level used: %u\n", cLevel); /* begin compression */ { size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); From 356ddb649f6c199cb087bf4a74d028c5e80c0f38 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 12 Jul 2017 12:21:21 -0700 Subject: [PATCH 063/145] working with flush job->src.size and fixed cLevel --- contrib/adaptive-compression/adapt.c | 46 ++++++++++++++++++---------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index b0bcb74f..b841cefa 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -29,6 +29,7 @@ static UTIL_freq_t g_ticksPerSecond; typedef struct { void* start; size_t size; + size_t capacity; } buffer_t; typedef struct { @@ -74,6 +75,8 @@ typedef struct { pthread_cond_t allJobsCompleted_cond; pthread_mutex_t jobWrite_mutex; pthread_cond_t jobWrite_cond; + size_t lastDictSize; + size_t targetDictSize; inBuff_t input; cStat_t stats; jobDescription* jobs; @@ -136,6 +139,8 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->jobReadyID = 0; ctx->jobCompressedID = 0; ctx->jobWriteID = 0; + ctx->targetDictSize = FILE_CHUNK_SIZE >> 1; + ctx->lastDictSize = 0; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); /* initializing jobs */ { @@ -151,6 +156,9 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) freeCCtx(ctx); return NULL; } + job->src.capacity = FILE_CHUNK_SIZE; + job->dst.capacity = ZSTD_compressBound(FILE_CHUNK_SIZE); + job->dict.capacity = FILE_CHUNK_SIZE; } } ctx->nextJobID = 0; @@ -159,8 +167,8 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->adaptParam = DEFAULT_ADAPT_PARAM; ctx->cctx = ZSTD_createCCtx(); ctx->input.filled = 0; - ctx->input.buffer.size = 2 * FILE_CHUNK_SIZE; - ctx->input.buffer.start = malloc(ctx->input.buffer.size); + ctx->input.buffer.capacity = 2 * FILE_CHUNK_SIZE; + ctx->input.buffer.start = malloc(ctx->input.buffer.capacity); if (!ctx->input.buffer.start) { DISPLAY("Error: could not allocate input buffer\n"); freeCCtx(ctx); @@ -249,16 +257,19 @@ static void* compressionThread(void* arg) } pthread_mutex_unlock(&ctx->jobReady_mutex); DEBUG(3, "compressionThread(): continuing after job ready\n"); + DEBUG(3, "%.*s\n", (int)job->dict.size, (char*)job->dict.start); + DEBUG(3, "DICTIONARY ENDED\n"); + DEBUG(2, "%.*s", (int)job->src.size, (char*)job->src.start); /* compress the data */ { unsigned const cLevel = adaptCompressionLevel(ctx); DEBUG(3, "cLevel used: %u\n", cLevel); - DEBUG(2, "dictSize: %zu, srcSize: %zu\n", job->dict.size, job->src.size); - DEBUG(2, "compression level used: %u\n", cLevel); + DEBUG(3, "dictSize: %zu, srcSize: %zu\n", job->dict.size, job->src.size); + DEBUG(3, "compression level used: %u\n", cLevel); /* begin compression */ { size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); - size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->dict.start, job->dict.size, cLevel); + size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->dict.start, job->dict.size, 6); size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { DISPLAY("Error: something went wrong while starting compression\n"); @@ -268,8 +279,8 @@ static void* compressionThread(void* arg) } /* continue compression */ - if (currJob != 0) { /* not first job */ - size_t const hSize = ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.size, job->src.start, job->src.size); + if (currJob != 0) { /* not first job flush/overwrite the frame header */ + size_t const hSize = ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.capacity, job->src.start, job->src.size); if (ZSTD_isError(hSize)) { DISPLAY("Error: something went wrong while continuing compression\n"); job->compressedSize = hSize; @@ -279,13 +290,14 @@ static void* compressionThread(void* arg) ZSTD_invalidateRepCodes(ctx->cctx); } job->compressedSize = (job->lastJob) ? - ZSTD_compressEnd (ctx->cctx, job->dst.start, job->dst.size, job->src.start, job->src.size) : - ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.size, job->src.start, job->src.size); + ZSTD_compressEnd (ctx->cctx, job->dst.start, job->dst.capacity, job->src.start, job->src.size) : + ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.capacity, job->src.start, job->src.size); if (ZSTD_isError(job->compressedSize)) { DISPLAY("Error: something went wrong during compression: %s\n", ZSTD_getErrorName(job->compressedSize)); ctx->threadError = 1; return arg; } + job->dst.size = job->compressedSize; } pthread_mutex_lock(&ctx->jobCompressed_mutex); ctx->jobCompressedID++; @@ -394,15 +406,15 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) pthread_mutex_unlock(&ctx->jobWrite_mutex); DEBUG(3, "createCompressionJob(): continuing after job write\n"); - + DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); job->compressionLevel = ctx->compressionLevel; job->src.size = srcSize; - job->dst.size = ZSTD_compressBound(srcSize); job->jobID = nextJob; job->lastJob = last; - memcpy(job->src.start, ctx->input.buffer.start + ctx->input.filled, srcSize); - job->dict.size = ctx->input.filled; - memcpy(job->dict.start, ctx->input.buffer.start, ctx->input.filled); + memcpy(job->src.start, ctx->input.buffer.start + ctx->lastDictSize, srcSize); + job->dict.size = ctx->lastDictSize; + DEBUG(3, "copied %zu bytes\n", ctx->lastDictSize); + memcpy(job->dict.start, ctx->input.buffer.start, ctx->lastDictSize); pthread_mutex_lock(&ctx->jobReady_mutex); ctx->jobReadyID++; pthread_cond_signal(&ctx->jobReady_cond); @@ -412,9 +424,11 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); /* if not on the last job, reuse data as dictionary in next job */ if (!last) { - size_t const newDictSize = srcSize/16; - size_t const oldDictSize = ctx->input.filled; + size_t const newDictSize = ctx->targetDictSize; + size_t const oldDictSize = ctx->lastDictSize; + DEBUG(3, "newDictSize %zu oldDictSize %zu\n", newDictSize, oldDictSize); memmove(ctx->input.buffer.start, ctx->input.buffer.start + oldDictSize + srcSize - newDictSize, newDictSize); + ctx->lastDictSize = newDictSize; ctx->input.filled = newDictSize; } return 0; From 5353d350aea592e87825129c86d57edeaf0a082e Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 12 Jul 2017 16:02:20 -0700 Subject: [PATCH 064/145] working with fixed compression level and fixed dictionary size --- contrib/adaptive-compression/adapt.c | 29 +++++++++++----------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index b841cefa..eb1e24a7 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -49,11 +49,11 @@ typedef struct { typedef struct { buffer_t src; buffer_t dst; - buffer_t dict; unsigned compressionLevel; unsigned jobID; unsigned lastJob; size_t compressedSize; + size_t dictSize; } jobDescription; typedef struct { @@ -91,7 +91,6 @@ static void freeCompressionJobs(adaptCCtx* ctx) jobDescription job = ctx->jobs[u]; free(job.dst.start); free(job.src.start); - free(job.dict.start); } } @@ -139,7 +138,7 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->jobReadyID = 0; ctx->jobCompressedID = 0; ctx->jobWriteID = 0; - ctx->targetDictSize = FILE_CHUNK_SIZE >> 1; + ctx->targetDictSize = FILE_CHUNK_SIZE >> 15; ctx->lastDictSize = 0; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); /* initializing jobs */ @@ -147,18 +146,16 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) unsigned jobNum; for (jobNum=0; jobNumjobs[jobNum]; - job->src.start = malloc(FILE_CHUNK_SIZE); + job->src.start = malloc(2 * FILE_CHUNK_SIZE); job->dst.start = malloc(ZSTD_compressBound(FILE_CHUNK_SIZE)); - job->dict.start = malloc(FILE_CHUNK_SIZE); job->lastJob = 0; - if (!job->src.start || !job->dst.start || !job->dict.start) { + if (!job->src.start || !job->dst.start) { DISPLAY("Could not allocate buffers for jobs\n"); freeCCtx(ctx); return NULL; } job->src.capacity = FILE_CHUNK_SIZE; job->dst.capacity = ZSTD_compressBound(FILE_CHUNK_SIZE); - job->dict.capacity = FILE_CHUNK_SIZE; } } ctx->nextJobID = 0; @@ -257,19 +254,17 @@ static void* compressionThread(void* arg) } pthread_mutex_unlock(&ctx->jobReady_mutex); DEBUG(3, "compressionThread(): continuing after job ready\n"); - DEBUG(3, "%.*s\n", (int)job->dict.size, (char*)job->dict.start); DEBUG(3, "DICTIONARY ENDED\n"); - DEBUG(2, "%.*s", (int)job->src.size, (char*)job->src.start); + DEBUG(3, "%.*s", (int)job->src.size, (char*)job->src.start); /* compress the data */ { unsigned const cLevel = adaptCompressionLevel(ctx); DEBUG(3, "cLevel used: %u\n", cLevel); - DEBUG(3, "dictSize: %zu, srcSize: %zu\n", job->dict.size, job->src.size); DEBUG(3, "compression level used: %u\n", cLevel); /* begin compression */ { size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); - size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->dict.start, job->dict.size, 6); + size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start, job->dictSize, 6); size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { DISPLAY("Error: something went wrong while starting compression\n"); @@ -280,7 +275,7 @@ static void* compressionThread(void* arg) /* continue compression */ if (currJob != 0) { /* not first job flush/overwrite the frame header */ - size_t const hSize = ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.capacity, job->src.start, job->src.size); + size_t const hSize = ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.capacity, job->src.start + job->dictSize, 0); if (ZSTD_isError(hSize)) { DISPLAY("Error: something went wrong while continuing compression\n"); job->compressedSize = hSize; @@ -290,8 +285,8 @@ static void* compressionThread(void* arg) ZSTD_invalidateRepCodes(ctx->cctx); } job->compressedSize = (job->lastJob) ? - ZSTD_compressEnd (ctx->cctx, job->dst.start, job->dst.capacity, job->src.start, job->src.size) : - ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.capacity, job->src.start, job->src.size); + ZSTD_compressEnd (ctx->cctx, job->dst.start, job->dst.capacity, job->src.start + job->dictSize, job->src.size) : + ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.capacity, job->src.start + job->dictSize, job->src.size); if (ZSTD_isError(job->compressedSize)) { DISPLAY("Error: something went wrong during compression: %s\n", ZSTD_getErrorName(job->compressedSize)); ctx->threadError = 1; @@ -411,10 +406,8 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) job->src.size = srcSize; job->jobID = nextJob; job->lastJob = last; - memcpy(job->src.start, ctx->input.buffer.start + ctx->lastDictSize, srcSize); - job->dict.size = ctx->lastDictSize; - DEBUG(3, "copied %zu bytes\n", ctx->lastDictSize); - memcpy(job->dict.start, ctx->input.buffer.start, ctx->lastDictSize); + memcpy(job->src.start, ctx->input.buffer.start, ctx->lastDictSize + srcSize); + job->dictSize = ctx->lastDictSize; pthread_mutex_lock(&ctx->jobReady_mutex); ctx->jobReadyID++; pthread_cond_signal(&ctx->jobReady_cond); From 74d3a6f5aefaf7d35a7af87e66b3046c1ead2593 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 12 Jul 2017 16:18:41 -0700 Subject: [PATCH 065/145] passes tests with adaptive compression level --- contrib/adaptive-compression/adapt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index eb1e24a7..e9230ddb 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -264,7 +264,7 @@ static void* compressionThread(void* arg) /* begin compression */ { size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); - size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start, job->dictSize, 6); + size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start, job->dictSize, cLevel); size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { DISPLAY("Error: something went wrong while starting compression\n"); From 3c16edd26ac531d358cad5031ac3f12bed12f45b Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 12 Jul 2017 16:40:24 -0700 Subject: [PATCH 066/145] added copyright header, removed clean from makefile --- contrib/adaptive-compression/Makefile | 2 +- contrib/adaptive-compression/adapt.c | 25 +++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index 38e3a778..ed1a55ad 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -19,7 +19,7 @@ CFLAGS += $(DEBUGFLAGS) CFLAGS += $(MOREFLAGS) FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -all: clean adapt +all: adapt adapt: $(ZSTD_FILES) adapt.c $(CC) $(FLAGS) $^ -o $@ diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index e9230ddb..67fb7564 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -1,3 +1,19 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#include /* fprintf */ +#include /* malloc, free */ +#include /* pthread functions */ +#include /* memset */ +#include "zstd_internal.h" +#include "util.h" + #define DISPLAY(...) fprintf(stderr, __VA_ARGS__) #define PRINT(...) fprintf(stdout, __VA_ARGS__) #define DEBUG(l, ...) { if (g_displayLevel>=l) { DISPLAY(__VA_ARGS__); } } @@ -11,13 +27,6 @@ #define DEFAULT_ADAPT_PARAM 1 typedef unsigned char BYTE; -#include /* fprintf */ -#include /* malloc, free */ -#include /* pthread functions */ -#include /* memset */ -#include "zstd_internal.h" -#include "util.h" - static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; static unsigned g_displayStats = 0; @@ -138,7 +147,7 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->jobReadyID = 0; ctx->jobCompressedID = 0; ctx->jobWriteID = 0; - ctx->targetDictSize = FILE_CHUNK_SIZE >> 15; + ctx->targetDictSize = 1 << 12; ctx->lastDictSize = 0; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); /* initializing jobs */ From 954d999abfbf7dc22cb4afe3e7a1cc19c8edd865 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 12 Jul 2017 16:50:43 -0700 Subject: [PATCH 067/145] fixed up freeCCtx() removed BYTE since it wasn't being used --- contrib/adaptive-compression/adapt.c | 36 +++++++++++++++------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 67fb7564..3cafbca3 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -25,7 +25,6 @@ #define DEFAULT_DISPLAY_LEVEL 1 #define DEFAULT_COMPRESSION_LEVEL 6 #define DEFAULT_ADAPT_PARAM 1 -typedef unsigned char BYTE; static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; @@ -106,23 +105,26 @@ static void freeCompressionJobs(adaptCCtx* ctx) static int freeCCtx(adaptCCtx* ctx) { if (!ctx) return 0; - int const compressedMutexError = pthread_mutex_destroy(&ctx->jobCompressed_mutex); - int const compressedCondError = pthread_cond_destroy(&ctx->jobCompressed_cond); - int const readyMutexError = pthread_mutex_destroy(&ctx->jobReady_mutex); - int const readyCondError = pthread_cond_destroy(&ctx->jobReady_cond); - int const allJobsMutexError = pthread_mutex_destroy(&ctx->allJobsCompleted_mutex); - int const allJobsCondError = pthread_cond_destroy(&ctx->allJobsCompleted_cond); - int const jobWriteMutexError = pthread_mutex_destroy(&ctx->jobWrite_mutex); - int const jobWriteCondError = pthread_cond_destroy(&ctx->jobWrite_cond); - int const fileCloseError = (ctx->dstFile != NULL && ctx->dstFile != stdout) ? fclose(ctx->dstFile) : 0; - int const cctxError = ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)) ? 1 : 0; - free(ctx->input.buffer.start); - if (ctx->jobs){ - freeCompressionJobs(ctx); - free(ctx->jobs); + { + int error = 0; + error |= pthread_mutex_destroy(&ctx->jobCompressed_mutex); + error |= pthread_cond_destroy(&ctx->jobCompressed_cond); + error |= pthread_mutex_destroy(&ctx->jobReady_mutex); + error |= pthread_cond_destroy(&ctx->jobReady_cond); + error |= pthread_mutex_destroy(&ctx->allJobsCompleted_mutex); + error |= pthread_cond_destroy(&ctx->allJobsCompleted_cond); + error |= pthread_mutex_destroy(&ctx->jobWrite_mutex); + error |= pthread_cond_destroy(&ctx->jobWrite_cond); + error |= (ctx->dstFile != NULL && ctx->dstFile != stdout) ? fclose(ctx->dstFile) : 0; + error |= ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)); + free(ctx->input.buffer.start); + if (ctx->jobs){ + freeCompressionJobs(ctx); + free(ctx->jobs); + } + free(ctx); + return error; } - free(ctx); - return compressedMutexError | compressedCondError | readyMutexError | readyCondError | fileCloseError | allJobsMutexError | allJobsCondError | jobWriteMutexError | jobWriteCondError | cctxError; } static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) From b5b18cf66477fecca9ba42982baa0bd9779c9957 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 12 Jul 2017 17:10:58 -0700 Subject: [PATCH 068/145] changed to malloc, added comment about adaptive compression level, and changed ternary operators --- contrib/adaptive-compression/adapt.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 3cafbca3..bcac46de 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -130,12 +130,11 @@ static int freeCCtx(adaptCCtx* ctx) static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) { - adaptCCtx* ctx = malloc(sizeof(adaptCCtx)); + adaptCCtx* ctx = calloc(1, sizeof(adaptCCtx)); if (ctx == NULL) { DISPLAY("Error: could not allocate space for context\n"); return NULL; } - memset(ctx, 0, sizeof(adaptCCtx)); ctx->compressionLevel = g_compressionLevel; pthread_mutex_init(&ctx->jobCompressed_mutex, NULL); pthread_cond_init(&ctx->jobCompressed_cond, NULL); @@ -216,16 +215,24 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) pthread_mutex_unlock(&ctx->allJobsCompleted_mutex); } +/* + * Compression level is changed depending on which part of the compression process is lagging + * Currently, three theads exist for job creation, compression, and file writing respectively. + * adaptCompressionLevel() increments or decrements compression level based on which of the threads is lagging + * job creation or file writing lag => increased compression level + * compression thread lag => decreased compression level + * detecting which thread is lagging is done by keeping track of how many calls each thread makes to pthread_cond_wait + */ static unsigned adaptCompressionLevel(adaptCCtx* ctx) { unsigned reset = 0; - unsigned const allSlow = ctx->adaptParam < ctx->stats.compressedCounter && ctx->adaptParam < ctx->stats.writeCounter && ctx->adaptParam < ctx->stats.readyCounter ? 1 : 0; - unsigned const compressWaiting = ctx->adaptParam < ctx->stats.readyCounter ? 1 : 0; - unsigned const writeWaiting = ctx->adaptParam < ctx->stats.compressedCounter ? 1 : 0; - unsigned const createWaiting = ctx->adaptParam < ctx->stats.writeCounter ? 1 : 0; - unsigned const writeSlow = ((compressWaiting && createWaiting) || (createWaiting && !writeWaiting)) ? 1 : 0; - unsigned const compressSlow = ((writeWaiting && createWaiting) || (writeWaiting && !compressWaiting)) ? 1 : 0; - unsigned const createSlow = ((compressWaiting && writeWaiting) || (compressWaiting && !createWaiting)) ? 1 : 0; + unsigned const allSlow = ctx->adaptParam < ctx->stats.compressedCounter && ctx->adaptParam < ctx->stats.writeCounter && ctx->adaptParam < ctx->stats.readyCounter; + unsigned const compressWaiting = ctx->adaptParam < ctx->stats.readyCounter; + unsigned const writeWaiting = ctx->adaptParam < ctx->stats.compressedCounter; + unsigned const createWaiting = ctx->adaptParam < ctx->stats.writeCounter; + unsigned const writeSlow = ((compressWaiting && createWaiting) || (createWaiting && !writeWaiting)); + unsigned const compressSlow = ((writeWaiting && createWaiting) || (writeWaiting && !compressWaiting)); + unsigned const createSlow = ((compressWaiting && writeWaiting) || (compressWaiting && !createWaiting)); DEBUG(3, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); if (allSlow) { reset = 1; From 7c886db0a8ff8ef8941c2e4a24ea75a3e2756849 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 12 Jul 2017 17:28:53 -0700 Subject: [PATCH 069/145] changed to stderr --- contrib/adaptive-compression/adapt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index bcac46de..9c698644 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -336,12 +336,12 @@ static void displayProgress(unsigned jobDoneID, unsigned cLevel, unsigned last) double const timeElapsed = (double)(UTIL_getSpanTimeMicro(g_ticksPerSecond, g_startTime, currTime) / 1000.0); double const sizeMB = (double)g_streamedSize / (1 << 20); double const avgCompRate = sizeMB * 1000 / timeElapsed; - fprintf(stdout, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB | Avg Compression Rate: %6.2f MB/s |", jobDoneID, cLevel, timeElapsed, sizeMB, avgCompRate); + fprintf(stderr, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB | Avg Compression Rate: %6.2f MB/s |", jobDoneID, cLevel, timeElapsed, sizeMB, avgCompRate); if (last) { - fprintf(stdout, "\n"); + fprintf(stderr, "\n"); } else { - fflush(stdout); + fflush(stderr); } } From 766663f1f12bb8d7db9740114c7c33ce52f85d9c Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 13 Jul 2017 10:15:27 -0700 Subject: [PATCH 070/145] added altering dictionary size depending on compression level --- contrib/adaptive-compression/adapt.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 9c698644..81027666 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -84,7 +84,6 @@ typedef struct { pthread_mutex_t jobWrite_mutex; pthread_cond_t jobWrite_cond; size_t lastDictSize; - size_t targetDictSize; inBuff_t input; cStat_t stats; jobDescription* jobs; @@ -148,7 +147,6 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->jobReadyID = 0; ctx->jobCompressedID = 0; ctx->jobWriteID = 0; - ctx->targetDictSize = 1 << 12; ctx->lastDictSize = 0; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); /* initializing jobs */ @@ -255,6 +253,14 @@ static unsigned adaptCompressionLevel(adaptCCtx* ctx) return ctx->compressionLevel; } +static size_t getUseableDictSize(unsigned compressionLevel) +{ + ZSTD_parameters params = ZSTD_getParams(compressionLevel, 0, 0); + unsigned overlapLog = compressionLevel >= (unsigned)ZSTD_maxCLevel() ? 0 : 3; + size_t overlapSize = 1 << (params.cParams.windowLog - overlapLog); + return overlapSize; +} + static void* compressionThread(void* arg) { adaptCCtx* ctx = (adaptCCtx*)arg; @@ -281,8 +287,10 @@ static void* compressionThread(void* arg) DEBUG(3, "compression level used: %u\n", cLevel); /* begin compression */ { + size_t useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); + DEBUG(2, "useDictSize: %zu, job->dictSize: %zu\n", useDictSize, job->dictSize); size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); - size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start, job->dictSize, cLevel); + size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start + job->dictSize - useDictSize, useDictSize, cLevel); size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { DISPLAY("Error: something went wrong while starting compression\n"); @@ -435,12 +443,11 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); /* if not on the last job, reuse data as dictionary in next job */ if (!last) { - size_t const newDictSize = ctx->targetDictSize; size_t const oldDictSize = ctx->lastDictSize; - DEBUG(3, "newDictSize %zu oldDictSize %zu\n", newDictSize, oldDictSize); - memmove(ctx->input.buffer.start, ctx->input.buffer.start + oldDictSize + srcSize - newDictSize, newDictSize); - ctx->lastDictSize = newDictSize; - ctx->input.filled = newDictSize; + DEBUG(3, "oldDictSize %zu\n", oldDictSize); + memmove(ctx->input.buffer.start, ctx->input.buffer.start + oldDictSize, srcSize); + ctx->lastDictSize = srcSize; + ctx->input.filled = srcSize; } return 0; } From 9165e97fc69d6a6f0476575b696d5f93f5070ea6 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 13 Jul 2017 13:50:23 -0700 Subject: [PATCH 071/145] added some tests for correctness, time, and compression ratio --- contrib/adaptive-compression/Makefile | 15 +- contrib/adaptive-compression/datagencli.c | 129 +++++++++++ .../adaptive-compression/test-correctness.sh | 205 ++++++++++++++++++ .../adaptive-compression/test-performance.sh | 34 +++ 4 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 contrib/adaptive-compression/datagencli.c create mode 100755 contrib/adaptive-compression/test-correctness.sh create mode 100755 contrib/adaptive-compression/test-performance.sh diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index ed1a55ad..f2059a19 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -19,13 +19,24 @@ CFLAGS += $(DEBUGFLAGS) CFLAGS += $(MOREFLAGS) FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -all: adapt +all: adapt datagen adapt: $(ZSTD_FILES) adapt.c $(CC) $(FLAGS) $^ -o $@ +datagen : $(PRGDIR)/datagen.c datagencli.c + $(CC) $(FLAGS) $^ -o $@$(EXT) + +test-adapt-correctness: datagen adapt + @./test-correctness.sh + @echo "test correctness complete" + +test-adapt-performance: datagen adapt + @./test-performance.sh + @echo "test performance complete" + clean: - @$(RM) -f adapt + @$(RM) -f adapt datagen @$(RM) -rf *.dSYM @$(RM) -f tmp* @$(RM) -f tests/*.zst diff --git a/contrib/adaptive-compression/datagencli.c b/contrib/adaptive-compression/datagencli.c new file mode 100644 index 00000000..8a81939d --- /dev/null +++ b/contrib/adaptive-compression/datagencli.c @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + + +/*-************************************ +* Dependencies +**************************************/ +#include "util.h" /* Compiler options */ +#include /* fprintf, stderr */ +#include "datagen.h" /* RDG_generate */ + + +/*-************************************ +* Constants +**************************************/ +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) + +#define SIZE_DEFAULT ((64 KB) + 1) +#define SEED_DEFAULT 0 +#define COMPRESSIBILITY_DEFAULT 50 + + +/*-************************************ +* Macros +**************************************/ +#define DISPLAY(...) fprintf(stderr, __VA_ARGS__) +#define DISPLAYLEVEL(l, ...) if (displayLevel>=l) { DISPLAY(__VA_ARGS__); } +static unsigned displayLevel = 2; + + +/*-******************************************************* +* Command line +*********************************************************/ +static int usage(const char* programName) +{ + DISPLAY( "Compressible data generator\n"); + DISPLAY( "Usage :\n"); + DISPLAY( " %s [args]\n", programName); + DISPLAY( "\n"); + DISPLAY( "Arguments :\n"); + DISPLAY( " -g# : generate # data (default:%i)\n", SIZE_DEFAULT); + DISPLAY( " -s# : Select seed (default:%i)\n", SEED_DEFAULT); + DISPLAY( " -P# : Select compressibility in %% (default:%i%%)\n", + COMPRESSIBILITY_DEFAULT); + DISPLAY( " -h : display help and exit\n"); + return 0; +} + + +int main(int argc, const char** argv) +{ + unsigned probaU32 = COMPRESSIBILITY_DEFAULT; + double litProba = 0.0; + U64 size = SIZE_DEFAULT; + U32 seed = SEED_DEFAULT; + const char* const programName = argv[0]; + + int argNb; + for(argNb=1; argNb='0') && (*argument<='9')) + size *= 10, size += *argument++ - '0'; + if (*argument=='K') { size <<= 10; argument++; } + if (*argument=='M') { size <<= 20; argument++; } + if (*argument=='G') { size <<= 30; argument++; } + if (*argument=='B') { argument++; } + break; + case 's': + argument++; + seed=0; + while ((*argument>='0') && (*argument<='9')) + seed *= 10, seed += *argument++ - '0'; + break; + case 'P': + argument++; + probaU32 = 0; + while ((*argument>='0') && (*argument<='9')) + probaU32 *= 10, probaU32 += *argument++ - '0'; + if (probaU32>100) probaU32 = 100; + break; + case 'L': /* hidden argument : Literal distribution probability */ + argument++; + litProba=0.; + while ((*argument>='0') && (*argument<='9')) + litProba *= 10, litProba += *argument++ - '0'; + if (litProba>100.) litProba=100.; + litProba /= 100.; + break; + case 'v': + displayLevel = 4; + argument++; + break; + default: + return usage(programName); + } + } } } /* for(argNb=1; argNb tmp +./adapt -otmp.zst tmp +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g500MB > tmp +./adapt -otmp.zst tmp +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g250MB > tmp +./adapt -otmp.zst tmp +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g125MB > tmp +./adapt -otmp.zst tmp +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g50MB > tmp +./adapt -otmp.zst tmp +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g25MB > tmp +./adapt -otmp.zst tmp +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g10MB > tmp +./adapt -otmp.zst tmp +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g5MB > tmp +./adapt -otmp.zst tmp +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g500KB > tmp +./adapt -otmp.zst tmp +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +echo -e "\ncorrectness tests -- streaming" +./datagen -g1GB > tmp +cat tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g100MB > tmp +cat tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g10MB > tmp +cat tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g1MB > tmp +cat tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g100KB > tmp +cat tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g10KB > tmp +cat tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +echo -e "\ncorrectness tests -- read limit" +./datagen -g1GB > tmp +pv -L 50m -q tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g100MB > tmp +pv -L 50m -q tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g10MB > tmp +pv -L 50m -q tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g1MB > tmp +pv -L 50m -q tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g100KB > tmp +pv -L 50m -q tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g10KB > tmp +pv -L 50m -q tmp | ./adapt > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +echo -e "\ncorrectness tests -- write limit" +./datagen -g1GB > tmp +pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g100MB > tmp +pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g10MB > tmp +pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g1MB > tmp +pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g100KB > tmp +pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g10KB > tmp +pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +echo -e "\ncorrectness tests -- read and write limits" +./datagen -g1GB > tmp +pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g100MB > tmp +pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g10MB > tmp +pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g1MB > tmp +pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g100KB > tmp +pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + +./datagen -g10KB > tmp +pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst +zstd -d tmp.zst -o tmp2 +diff -q tmp tmp2 +rm tmp* + + +make clean diff --git a/contrib/adaptive-compression/test-performance.sh b/contrib/adaptive-compression/test-performance.sh new file mode 100755 index 00000000..6a88325d --- /dev/null +++ b/contrib/adaptive-compression/test-performance.sh @@ -0,0 +1,34 @@ +echo "testing time" +./datagen -g1GB > tmp +time ./adapt -otmp1.zst tmp +time zstd -1 -o tmp2.zst tmp +rm tmp* + +./datagen -g2GB > tmp +time ./adapt -otmp1.zst tmp +time zstd -1 -o tmp2.zst tmp +rm tmp* + +./datagen -g4GB > tmp +time ./adapt -otmp1.zst tmp +time zstd -1 -o tmp2.zst tmp +rm tmp* + +echo -e "\ntesting compression ratio" +./datagen -g1GB > tmp +time ./adapt -otmp1.zst tmp +time zstd -1 -o tmp2.zst tmp +ls -l tmp1.zst tmp2.zst +rm tmp* + +./datagen -g2GB > tmp +time ./adapt -otmp1.zst tmp +time zstd -1 -o tmp2.zst tmp +ls -l tmp1.zst tmp2.zst +rm tmp* + +./datagen -g4GB > tmp +time ./adapt -otmp1.zst tmp +time zstd -1 -o tmp2.zst tmp +ls -l tmp1.zst tmp2.zst +rm tmp* From 0d9665cef5fb39a55acfe2069ae443501d7079cf Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 13 Jul 2017 14:46:54 -0700 Subject: [PATCH 072/145] added additional tests for performance, allowed force compression level for testing purposes --- contrib/adaptive-compression/adapt.c | 61 +++++++++++-------- .../adaptive-compression/test-performance.sh | 29 ++++++++- 2 files changed, 62 insertions(+), 28 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 81027666..c2160714 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -33,6 +33,7 @@ static UTIL_time_t g_startTime; static size_t g_streamedSize = 0; static unsigned g_useProgressBar = 0; static UTIL_freq_t g_ticksPerSecond; +static unsigned g_forceCompressionLevel = 0; typedef struct { void* start; @@ -223,34 +224,39 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) */ static unsigned adaptCompressionLevel(adaptCCtx* ctx) { - unsigned reset = 0; - unsigned const allSlow = ctx->adaptParam < ctx->stats.compressedCounter && ctx->adaptParam < ctx->stats.writeCounter && ctx->adaptParam < ctx->stats.readyCounter; - unsigned const compressWaiting = ctx->adaptParam < ctx->stats.readyCounter; - unsigned const writeWaiting = ctx->adaptParam < ctx->stats.compressedCounter; - unsigned const createWaiting = ctx->adaptParam < ctx->stats.writeCounter; - unsigned const writeSlow = ((compressWaiting && createWaiting) || (createWaiting && !writeWaiting)); - unsigned const compressSlow = ((writeWaiting && createWaiting) || (writeWaiting && !compressWaiting)); - unsigned const createSlow = ((compressWaiting && writeWaiting) || (compressWaiting && !createWaiting)); - DEBUG(3, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); - if (allSlow) { - reset = 1; + if (g_forceCompressionLevel) { + return g_compressionLevel; } - else if ((writeSlow || createSlow) && ctx->compressionLevel < (unsigned)ZSTD_maxCLevel()) { - DEBUG(3, "increasing compression level %u\n", ctx->compressionLevel); - ctx->compressionLevel++; - reset = 1; + else { + unsigned reset = 0; + unsigned const allSlow = ctx->adaptParam < ctx->stats.compressedCounter && ctx->adaptParam < ctx->stats.writeCounter && ctx->adaptParam < ctx->stats.readyCounter; + unsigned const compressWaiting = ctx->adaptParam < ctx->stats.readyCounter; + unsigned const writeWaiting = ctx->adaptParam < ctx->stats.compressedCounter; + unsigned const createWaiting = ctx->adaptParam < ctx->stats.writeCounter; + unsigned const writeSlow = ((compressWaiting && createWaiting) || (createWaiting && !writeWaiting)); + unsigned const compressSlow = ((writeWaiting && createWaiting) || (writeWaiting && !compressWaiting)); + unsigned const createSlow = ((compressWaiting && writeWaiting) || (compressWaiting && !createWaiting)); + DEBUG(3, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); + if (allSlow) { + reset = 1; + } + else if ((writeSlow || createSlow) && ctx->compressionLevel < (unsigned)ZSTD_maxCLevel()) { + DEBUG(3, "increasing compression level %u\n", ctx->compressionLevel); + ctx->compressionLevel++; + reset = 1; + } + else if (compressSlow && ctx->compressionLevel > 1) { + DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); + ctx->compressionLevel--; + reset = 1; + } + if (reset) { + ctx->stats.readyCounter = 0; + ctx->stats.writeCounter = 0; + ctx->stats.compressedCounter = 0; + } + return ctx->compressionLevel; } - else if (compressSlow && ctx->compressionLevel > 1) { - DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); - ctx->compressionLevel--; - reset = 1; - } - if (reset) { - ctx->stats.readyCounter = 0; - ctx->stats.writeCounter = 0; - ctx->stats.compressedCounter = 0; - } - return ctx->compressionLevel; } static size_t getUseableDictSize(unsigned compressionLevel) @@ -649,6 +655,9 @@ int main(int argCount, const char* argv[]) forceStdout = 1; outFilename = stdoutmark; break; + case 'f': + g_forceCompressionLevel = 1; + break; default: DISPLAY("Error: invalid argument provided\n"); ret = 1; diff --git a/contrib/adaptive-compression/test-performance.sh b/contrib/adaptive-compression/test-performance.sh index 6a88325d..6c4991c4 100755 --- a/contrib/adaptive-compression/test-performance.sh +++ b/contrib/adaptive-compression/test-performance.sh @@ -1,4 +1,4 @@ -echo "testing time" +echo "testing time -- no limits set" ./datagen -g1GB > tmp time ./adapt -otmp1.zst tmp time zstd -1 -o tmp2.zst tmp @@ -14,7 +14,7 @@ time ./adapt -otmp1.zst tmp time zstd -1 -o tmp2.zst tmp rm tmp* -echo -e "\ntesting compression ratio" +echo -e "\ntesting compression ratio -- no limits set" ./datagen -g1GB > tmp time ./adapt -otmp1.zst tmp time zstd -1 -o tmp2.zst tmp @@ -32,3 +32,28 @@ time ./adapt -otmp1.zst tmp time zstd -1 -o tmp2.zst tmp ls -l tmp1.zst tmp2.zst rm tmp* + +echo e "\ntesting performance at various compression levels -- no limits set" +./datagen -g1GB > tmp +echo "adapt" +time ./adapt -i5 -f tmp -otmp1.zst +echo "zstdcli" +time zstd -5 tmp -o tmp2.zst +ls -l tmp1.zst tmp2.zst +rm tmp* + +./datagen -g1GB > tmp +echo "adapt" +time ./adapt -i10 -f tmp -otmp1.zst +echo "zstdcli" +time zstd -10 tmp -o tmp2.zst +ls -l tmp1.zst tmp2.zst +rm tmp* + +./datagen -g1GB > tmp +echo "adapt" +time ./adapt -i15 -f tmp -otmp1.zst +echo "zstdcli" +time zstd -15 tmp -o tmp2.zst +ls -l tmp1.zst tmp2.zst +rm tmp* From 65a4ce2635f519f1db3e9f8027f1596429f789cf Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 13 Jul 2017 14:57:24 -0700 Subject: [PATCH 073/145] added tests for forced compression level --- .../adaptive-compression/test-correctness.sh | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/contrib/adaptive-compression/test-correctness.sh b/contrib/adaptive-compression/test-correctness.sh index 6277fafb..e6ac6dfb 100755 --- a/contrib/adaptive-compression/test-correctness.sh +++ b/contrib/adaptive-compression/test-correctness.sh @@ -201,5 +201,40 @@ zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* +echo -e "\ncorrectness tests -- forced compression level" +./datagen -g1GB > tmp +./adapt tmp -otmp.zst -i11 -f +zstd -d tmp.zst -o tmp2 +diff tmp tmp2 +rm tmp* +./datagen -g100MB > tmp +./adapt tmp -otmp.zst -i11 -f +zstd -d tmp.zst -o tmp2 +diff tmp tmp2 +rm tmp* + +./datagen -g10MB > tmp +./adapt tmp -otmp.zst -i11 -f +zstd -d tmp.zst -o tmp2 +diff tmp tmp2 +rm tmp* + +./datagen -g1MB > tmp +./adapt tmp -otmp.zst -i11 -f +zstd -d tmp.zst -o tmp2 +diff tmp tmp2 +rm tmp* + +./datagen -g100KB > tmp +./adapt tmp -otmp.zst -i11 -f +zstd -d tmp.zst -o tmp2 +diff tmp tmp2 +rm tmp* + +./datagen -g10KB > tmp +./adapt tmp -otmp.zst -i11 -f +zstd -d tmp.zst -o tmp2 +diff tmp tmp2 +rm tmp* make clean From 0c8b9436b78e5192650ba9d50e1c52e2f0110aec Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 13 Jul 2017 16:38:20 -0700 Subject: [PATCH 074/145] removed goto statements for the most part --- contrib/adaptive-compression/adapt.c | 116 ++++++++++++++++----------- 1 file changed, 69 insertions(+), 47 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index c2160714..addd0c59 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -92,6 +92,11 @@ typedef struct { ZSTD_CCtx* cctx; } adaptCCtx; +typedef struct { + FILE* srcFile; + adaptCCtx* ctx; +} fcResources; + static void freeCompressionJobs(adaptCCtx* ctx) { unsigned u; @@ -207,6 +212,7 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) static void waitUntilAllJobsCompleted(adaptCCtx* ctx) { + if (!ctx) return; pthread_mutex_lock(&ctx->allJobsCompleted_mutex); while (ctx->allJobsCompleted == 0) { pthread_cond_wait(&ctx->allJobsCompleted_cond, &ctx->allJobsCompleted_mutex); @@ -466,40 +472,10 @@ static void printStats(cStat_t stats) DISPLAY("# times waited on job Write: %u\n\n", stats.waitWrite); } -static int compressFilename(const char* const srcFilename, const char* const dstFilenameOrNull) +static int performCompression(adaptCCtx* ctx, FILE* const srcFile) { - unsigned const stdinUsed = !strcmp(srcFilename, stdinmark); - FILE* const srcFile = stdinUsed ? stdin : fopen(srcFilename, "rb"); - const char* const outFilenameIntermediate = (stdinUsed && !dstFilenameOrNull) ? stdoutmark : dstFilenameOrNull; - const char* outFilename = outFilenameIntermediate; - char fileAndSuffix[MAX_PATH]; - size_t const numJobs = MAX_NUM_JOBS; - int ret = 0; - adaptCCtx* ctx = NULL; - UTIL_getTime(&g_startTime); - g_streamedSize = 0; - - if (!outFilenameIntermediate) { - if (snprintf(fileAndSuffix, MAX_PATH, "%s.zst", srcFilename) + 1 > MAX_PATH) { - DISPLAY("Error: output filename is too long\n"); - ret = 1; - goto cleanup; - } - outFilename = fileAndSuffix; - } - - /* checking for errors */ - if (!srcFilename || !outFilename || !srcFile) { - DISPLAY("Error: initial variables could not be allocated\n"); - ret = 1; - goto cleanup; - } - - /* creating context */ - ctx = createCCtx(numJobs, outFilename); - if (ctx == NULL) { - ret = 1; - goto cleanup; + if (!ctx || !srcFile) { + return 1; } /* create output thread */ @@ -507,8 +483,8 @@ static int compressFilename(const char* const srcFilename, const char* const dst pthread_t out; if (pthread_create(&out, NULL, &outputThread, ctx)) { DISPLAY("Error: could not create output thread\n"); - ret = 1; - goto cleanup; + ctx->threadError = 1; + return 1; } } @@ -517,8 +493,8 @@ static int compressFilename(const char* const srcFilename, const char* const dst pthread_t compression; if (pthread_create(&compression, NULL, &compressionThread, ctx)) { DISPLAY("Error: could not create compression thread\n"); - ret = 1; - goto cleanup; + ctx->threadError = 1; + return 1; } } @@ -528,8 +504,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); ctx->threadError = 1; - ret = 1; - goto cleanup; + return 1; } g_streamedSize += readSize; /* reading was fine, now create the compression job */ @@ -537,9 +512,8 @@ static int compressFilename(const char* const srcFilename, const char* const dst int const last = feof(srcFile); int const error = createCompressionJob(ctx, readSize, last); if (error != 0) { - ret = error; ctx->threadError = 1; - goto cleanup; + return error; } } if (feof(srcFile)) { @@ -548,12 +522,60 @@ static int compressFilename(const char* const srcFilename, const char* const dst } } -cleanup: - waitUntilAllJobsCompleted(ctx); - if (g_displayStats) printStats(ctx->stats); - /* file compression completed */ - ret |= (srcFile != NULL) ? fclose(srcFile) : 0; - ret |= (ctx != NULL) ? freeCCtx(ctx) : 0; + /* success -- created all jobs */ + return 0; +} + +static fcResources createFileCompressionResources(const char* const srcFilename, const char* const dstFilenameOrNull) +{ + fcResources fcr; + unsigned const stdinUsed = !strcmp(srcFilename, stdinmark); + FILE* const srcFile = stdinUsed ? stdin : fopen(srcFilename, "rb"); + const char* const outFilenameIntermediate = (stdinUsed && !dstFilenameOrNull) ? stdoutmark : dstFilenameOrNull; + const char* outFilename = outFilenameIntermediate; + char fileAndSuffix[MAX_PATH]; + size_t const numJobs = MAX_NUM_JOBS; + + memset(&fcr, 0, sizeof(fcr)); + + if (!outFilenameIntermediate) { + if (snprintf(fileAndSuffix, MAX_PATH, "%s.zst", srcFilename) + 1 > MAX_PATH) { + DISPLAY("Error: output filename is too long\n"); + return fcr; + } + outFilename = fileAndSuffix; + } + + /* checking for errors */ + if (!outFilename || !srcFile) { + DISPLAY("Error: initial variables could not be allocated\n"); + return fcr; + } + + /* creating context */ + fcr.ctx = createCCtx(numJobs, outFilename); + fcr.srcFile = srcFile; + return fcr; +} + +static int freeFileCompressionResources(fcResources* fcr) +{ + int ret = 0; + waitUntilAllJobsCompleted(fcr->ctx); + if (g_displayStats) printStats(fcr->ctx->stats); + ret |= (fcr->srcFile != NULL) ? fclose(fcr->srcFile) : 0; + ret |= (fcr->ctx != NULL) ? freeCCtx(fcr->ctx) : 0; + return ret; +} + +static int compressFilename(const char* const srcFilename, const char* const dstFilenameOrNull) +{ + int ret = 0; + UTIL_getTime(&g_startTime); + g_streamedSize = 0; + fcResources fcr = createFileCompressionResources(srcFilename, dstFilenameOrNull); + ret |= performCompression(fcr.ctx, fcr.srcFile); + ret |= freeFileCompressionResources(&fcr); return ret; } From 1ab3f06f0041b5a29eadaa2da81112424999f554 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 14 Jul 2017 16:29:29 -0700 Subject: [PATCH 075/145] updated tests to use different seeds when executing different tests --- .../adaptive-compression/test-correctness.sh | 78 +++++++++---------- .../adaptive-compression/test-performance.sh | 18 ++--- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/contrib/adaptive-compression/test-correctness.sh b/contrib/adaptive-compression/test-correctness.sh index e6ac6dfb..b057cbd6 100755 --- a/contrib/adaptive-compression/test-correctness.sh +++ b/contrib/adaptive-compression/test-correctness.sh @@ -1,238 +1,238 @@ echo "correctness tests -- general" -./datagen -g1GB > tmp +./datagen -s1 -g1GB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g500MB > tmp +./datagen -s2 -g500MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g250MB > tmp +./datagen -s3 -g250MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g125MB > tmp +./datagen -s4 -g125MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g50MB > tmp +./datagen -s5 -g50MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g25MB > tmp +./datagen -s6 -g25MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g10MB > tmp +./datagen -s7 -g10MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g5MB > tmp +./datagen -s8 -g5MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g500KB > tmp +./datagen -s9 -g500KB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* echo -e "\ncorrectness tests -- streaming" -./datagen -g1GB > tmp +./datagen -s10 -g1GB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g100MB > tmp +./datagen -s11 -g100MB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g10MB > tmp +./datagen -s12 -g10MB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g1MB > tmp +./datagen -s13 -g1MB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g100KB > tmp +./datagen -s14 -g100KB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g10KB > tmp +./datagen -s15 -g10KB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* echo -e "\ncorrectness tests -- read limit" -./datagen -g1GB > tmp +./datagen -s16 -g1GB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g100MB > tmp +./datagen -s17 -g100MB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g10MB > tmp +./datagen -s18 -g10MB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g1MB > tmp +./datagen -s19 -g1MB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g100KB > tmp +./datagen -s20 -g100KB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g10KB > tmp +./datagen -s21 -g10KB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* echo -e "\ncorrectness tests -- write limit" -./datagen -g1GB > tmp +./datagen -s22 -g1GB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g100MB > tmp +./datagen -s23 -g100MB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g10MB > tmp +./datagen -s24 -g10MB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g1MB > tmp +./datagen -s25 -g1MB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g100KB > tmp +./datagen -s26 -g100KB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g10KB > tmp +./datagen -s27 -g10KB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* echo -e "\ncorrectness tests -- read and write limits" -./datagen -g1GB > tmp +./datagen -s28 -g1GB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g100MB > tmp +./datagen -s29 -g100MB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g10MB > tmp +./datagen -s30 -g10MB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g1MB > tmp +./datagen -s31 -g1MB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g100KB > tmp +./datagen -s32 -g100KB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* -./datagen -g10KB > tmp +./datagen -s33 -g10KB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 diff -q tmp tmp2 rm tmp* echo -e "\ncorrectness tests -- forced compression level" -./datagen -g1GB > tmp +./datagen -s34 -g1GB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 diff tmp tmp2 rm tmp* -./datagen -g100MB > tmp +./datagen -s35 -g100MB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 diff tmp tmp2 rm tmp* -./datagen -g10MB > tmp +./datagen -s36 -g10MB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 diff tmp tmp2 rm tmp* -./datagen -g1MB > tmp +./datagen -s37 -g1MB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 diff tmp tmp2 rm tmp* -./datagen -g100KB > tmp +./datagen -s38 -g100KB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 diff tmp tmp2 rm tmp* -./datagen -g10KB > tmp +./datagen -s39 -g10KB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 diff tmp tmp2 diff --git a/contrib/adaptive-compression/test-performance.sh b/contrib/adaptive-compression/test-performance.sh index 6c4991c4..958cb3cc 100755 --- a/contrib/adaptive-compression/test-performance.sh +++ b/contrib/adaptive-compression/test-performance.sh @@ -1,40 +1,40 @@ echo "testing time -- no limits set" -./datagen -g1GB > tmp +./datagen -s1 -g1GB > tmp time ./adapt -otmp1.zst tmp time zstd -1 -o tmp2.zst tmp rm tmp* -./datagen -g2GB > tmp +./datagen -s2 -g2GB > tmp time ./adapt -otmp1.zst tmp time zstd -1 -o tmp2.zst tmp rm tmp* -./datagen -g4GB > tmp +./datagen -s3 -g4GB > tmp time ./adapt -otmp1.zst tmp time zstd -1 -o tmp2.zst tmp rm tmp* echo -e "\ntesting compression ratio -- no limits set" -./datagen -g1GB > tmp +./datagen -s4 -g1GB > tmp time ./adapt -otmp1.zst tmp time zstd -1 -o tmp2.zst tmp ls -l tmp1.zst tmp2.zst rm tmp* -./datagen -g2GB > tmp +./datagen -s5 -g2GB > tmp time ./adapt -otmp1.zst tmp time zstd -1 -o tmp2.zst tmp ls -l tmp1.zst tmp2.zst rm tmp* -./datagen -g4GB > tmp +./datagen -s6 -g4GB > tmp time ./adapt -otmp1.zst tmp time zstd -1 -o tmp2.zst tmp ls -l tmp1.zst tmp2.zst rm tmp* echo e "\ntesting performance at various compression levels -- no limits set" -./datagen -g1GB > tmp +./datagen -s7 -g1GB > tmp echo "adapt" time ./adapt -i5 -f tmp -otmp1.zst echo "zstdcli" @@ -42,7 +42,7 @@ time zstd -5 tmp -o tmp2.zst ls -l tmp1.zst tmp2.zst rm tmp* -./datagen -g1GB > tmp +./datagen -s8 -g1GB > tmp echo "adapt" time ./adapt -i10 -f tmp -otmp1.zst echo "zstdcli" @@ -50,7 +50,7 @@ time zstd -10 tmp -o tmp2.zst ls -l tmp1.zst tmp2.zst rm tmp* -./datagen -g1GB > tmp +./datagen -s9 -g1GB > tmp echo "adapt" time ./adapt -i15 -f tmp -otmp1.zst echo "zstdcli" From 50ce4eaeb65fcdd521c5be710cfb0dff7c02e1bf Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 17 Jul 2017 10:12:44 -0700 Subject: [PATCH 076/145] added error detection for pthread initialization, added compression completion measurement, fixed const values --- contrib/adaptive-compression/adapt.c | 169 ++++++++++++++++++--------- lib/compress/zstd_compress.c | 9 ++ lib/zstd.h | 6 +- 3 files changed, 131 insertions(+), 53 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index addd0c59..d45bdf85 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -25,6 +25,7 @@ #define DEFAULT_DISPLAY_LEVEL 1 #define DEFAULT_COMPRESSION_LEVEL 6 #define DEFAULT_ADAPT_PARAM 1 +#define MAX_COMPRESSION_LEVEL_CHANGE 10 static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; @@ -65,6 +66,16 @@ typedef struct { size_t dictSize; } jobDescription; +typedef struct { + pthread_mutex_t pMutex; + int noError; +} mutex_t; + +typedef struct { + pthread_cond_t pCond; + int noError; +} cond_t; + typedef struct { unsigned compressionLevel; unsigned numActiveThreads; @@ -76,14 +87,16 @@ typedef struct { unsigned jobWriteID; unsigned allJobsCompleted; unsigned adaptParam; - pthread_mutex_t jobCompressed_mutex; - pthread_cond_t jobCompressed_cond; - pthread_mutex_t jobReady_mutex; - pthread_cond_t jobReady_cond; - pthread_mutex_t allJobsCompleted_mutex; - pthread_cond_t allJobsCompleted_cond; - pthread_mutex_t jobWrite_mutex; - pthread_cond_t jobWrite_cond; + unsigned completionMeasured; + double completion; + mutex_t jobCompressed_mutex; + cond_t jobCompressed_cond; + mutex_t jobReady_mutex; + cond_t jobReady_cond; + mutex_t allJobsCompleted_mutex; + cond_t allJobsCompleted_cond; + mutex_t jobWrite_mutex; + cond_t jobWrite_cond; size_t lastDictSize; inBuff_t input; cStat_t stats; @@ -107,20 +120,38 @@ static void freeCompressionJobs(adaptCCtx* ctx) } } +static int destroyMutex(mutex_t* mutex) +{ + if (mutex->noError) { + int const ret = pthread_mutex_destroy(&mutex->pMutex); + return ret; + } + return 0; +} + +static int destroyCond(cond_t* cond) +{ + if (cond->noError) { + int const ret = pthread_cond_destroy(&cond->pCond); + return ret; + } + return 0; +} + static int freeCCtx(adaptCCtx* ctx) { if (!ctx) return 0; { int error = 0; - error |= pthread_mutex_destroy(&ctx->jobCompressed_mutex); - error |= pthread_cond_destroy(&ctx->jobCompressed_cond); - error |= pthread_mutex_destroy(&ctx->jobReady_mutex); - error |= pthread_cond_destroy(&ctx->jobReady_cond); - error |= pthread_mutex_destroy(&ctx->allJobsCompleted_mutex); - error |= pthread_cond_destroy(&ctx->allJobsCompleted_cond); - error |= pthread_mutex_destroy(&ctx->jobWrite_mutex); - error |= pthread_cond_destroy(&ctx->jobWrite_cond); - error |= (ctx->dstFile != NULL && ctx->dstFile != stdout) ? fclose(ctx->dstFile) : 0; + error |= destroyMutex(&ctx->jobCompressed_mutex); + error |= destroyCond(&ctx->jobCompressed_cond); + error |= destroyMutex(&ctx->jobReady_mutex); + error |= destroyCond(&ctx->jobReady_cond); + error |= destroyMutex(&ctx->allJobsCompleted_mutex); + error |= destroyCond(&ctx->allJobsCompleted_cond); + error |= destroyMutex(&ctx->jobWrite_mutex); + error |= destroyCond(&ctx->jobWrite_cond); + error |= (ctx->dstFile != NULL && ctx->dstFile != stdout) ? fclose(ctx->dstFile) : 0; error |= ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)); free(ctx->input.buffer.start); if (ctx->jobs){ @@ -132,23 +163,41 @@ static int freeCCtx(adaptCCtx* ctx) } } +static int initMutex(mutex_t* mutex) +{ + int const ret = pthread_mutex_init(&mutex->pMutex, NULL); + mutex->noError = !ret; + return ret; +} + +static int initCond(cond_t* cond) +{ + int const ret = pthread_cond_init(&cond->pCond, NULL); + cond->noError = !ret; + return ret; +} + static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) { - adaptCCtx* ctx = calloc(1, sizeof(adaptCCtx)); + adaptCCtx* const ctx = calloc(1, sizeof(adaptCCtx)); if (ctx == NULL) { DISPLAY("Error: could not allocate space for context\n"); return NULL; } ctx->compressionLevel = g_compressionLevel; - pthread_mutex_init(&ctx->jobCompressed_mutex, NULL); - pthread_cond_init(&ctx->jobCompressed_cond, NULL); - pthread_mutex_init(&ctx->jobReady_mutex, NULL); - pthread_cond_init(&ctx->jobReady_cond, NULL); - pthread_mutex_init(&ctx->allJobsCompleted_mutex, NULL); - pthread_cond_init(&ctx->allJobsCompleted_cond, NULL); - pthread_mutex_init(&ctx->jobWrite_mutex, NULL); - pthread_cond_init(&ctx->jobWrite_cond, NULL); + { + int pthreadError = 0; + pthreadError |= initMutex(&ctx->jobCompressed_mutex); + pthreadError |= initCond(&ctx->jobCompressed_cond); + pthreadError |= initMutex(&ctx->jobReady_mutex); + pthreadError |= initCond(&ctx->jobReady_cond); + pthreadError |= initMutex(&ctx->allJobsCompleted_mutex); + pthreadError |= initCond(&ctx->allJobsCompleted_cond); + pthreadError |= initMutex(&ctx->jobWrite_mutex); + pthreadError |= initCond(&ctx->jobWrite_cond); + if (pthreadError) return NULL; + } ctx->numJobs = numJobs; ctx->jobReadyID = 0; ctx->jobCompressedID = 0; @@ -213,11 +262,11 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) static void waitUntilAllJobsCompleted(adaptCCtx* ctx) { if (!ctx) return; - pthread_mutex_lock(&ctx->allJobsCompleted_mutex); + pthread_mutex_lock(&ctx->allJobsCompleted_mutex.pMutex); while (ctx->allJobsCompleted == 0) { - pthread_cond_wait(&ctx->allJobsCompleted_cond, &ctx->allJobsCompleted_mutex); + pthread_cond_wait(&ctx->allJobsCompleted_cond.pCond, &ctx->allJobsCompleted_mutex.pMutex); } - pthread_mutex_unlock(&ctx->allJobsCompleted_mutex); + pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); } /* @@ -252,14 +301,20 @@ static unsigned adaptCompressionLevel(adaptCCtx* ctx) reset = 1; } else if (compressSlow && ctx->compressionLevel > 1) { + double const completion = ctx->completion; + unsigned const maxChange = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); + unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); - ctx->compressionLevel--; + DEBUG(2, "completion: %f\n", completion); + ctx->compressionLevel -= change; reset = 1; } if (reset) { ctx->stats.readyCounter = 0; ctx->stats.writeCounter = 0; ctx->stats.compressedCounter = 0; + ctx->completion = 1; + ctx->completionMeasured = 0; } return ctx->compressionLevel; } @@ -281,14 +336,14 @@ static void* compressionThread(void* arg) unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "compressionThread(): waiting on job ready\n"); - pthread_mutex_lock(&ctx->jobReady_mutex); + pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); while(currJob + 1 > ctx->jobReadyID) { ctx->stats.waitReady++; ctx->stats.readyCounter++; DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); - pthread_cond_wait(&ctx->jobReady_cond, &ctx->jobReady_mutex); + pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); } - pthread_mutex_unlock(&ctx->jobReady_mutex); + pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); DEBUG(3, "compressionThread(): continuing after job ready\n"); DEBUG(3, "DICTIONARY ENDED\n"); DEBUG(3, "%.*s", (int)job->src.size, (char*)job->src.start); @@ -299,7 +354,7 @@ static void* compressionThread(void* arg) DEBUG(3, "compression level used: %u\n", cLevel); /* begin compression */ { - size_t useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); + size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); DEBUG(2, "useDictSize: %zu, job->dictSize: %zu\n", useDictSize, job->dictSize); size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start + job->dictSize - useDictSize, useDictSize, cLevel); @@ -332,11 +387,11 @@ static void* compressionThread(void* arg) } job->dst.size = job->compressedSize; } - pthread_mutex_lock(&ctx->jobCompressed_mutex); + pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); ctx->jobCompressedID++; DEBUG(3, "signaling for job %u\n", currJob); - pthread_cond_signal(&ctx->jobCompressed_cond); - pthread_mutex_unlock(&ctx->jobCompressed_mutex); + pthread_cond_signal(&ctx->jobCompressed_cond.pCond); + pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); DEBUG(3, "finished job compression %u\n", currJob); currJob++; if (job->lastJob || ctx->threadError) { @@ -374,14 +429,19 @@ static void* outputThread(void* arg) unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "outputThread(): waiting on job compressed\n"); - pthread_mutex_lock(&ctx->jobCompressed_mutex); + pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (currJob + 1 > ctx->jobCompressedID) { ctx->stats.waitCompressed++; ctx->stats.compressedCounter++; + if (!ctx->completionMeasured) { + ctx->completion = ZSTD_getCompletion(ctx->cctx); + ctx->completionMeasured = 1; + } + DEBUG(2, "output detected completion: %f\n", ctx->completion); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); - pthread_cond_wait(&ctx->jobCompressed_cond, &ctx->jobCompressed_mutex); + pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } - pthread_mutex_unlock(&ctx->jobCompressed_mutex); + pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); DEBUG(3, "outputThread(): continuing after job compressed\n"); { size_t const compressedSize = job->compressedSize; @@ -403,19 +463,19 @@ static void* outputThread(void* arg) currJob++; displayProgress(currJob, ctx->compressionLevel, job->lastJob); DEBUG(3, "locking job write mutex\n"); - pthread_mutex_lock(&ctx->jobWrite_mutex); + pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); ctx->jobWriteID++; - pthread_cond_signal(&ctx->jobWrite_cond); - pthread_mutex_unlock(&ctx->jobWrite_mutex); + pthread_cond_signal(&ctx->jobWrite_cond.pCond); + pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); DEBUG(3, "unlocking job write mutex\n"); if (job->lastJob || ctx->threadError) { /* finished with all jobs */ DEBUG(3, "all jobs finished writing\n"); - pthread_mutex_lock(&ctx->allJobsCompleted_mutex); + pthread_mutex_lock(&ctx->allJobsCompleted_mutex.pMutex); ctx->allJobsCompleted = 1; - pthread_cond_signal(&ctx->allJobsCompleted_cond); - pthread_mutex_unlock(&ctx->allJobsCompleted_mutex); + pthread_cond_signal(&ctx->allJobsCompleted_cond.pCond); + pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); break; } } @@ -428,15 +488,20 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) unsigned const nextJobIndex = nextJob % ctx->numJobs; jobDescription* job = &ctx->jobs[nextJobIndex]; DEBUG(3, "createCompressionJob(): wait for job write\n"); - pthread_mutex_lock(&ctx->jobWrite_mutex); + pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); DEBUG(3, "Creating new compression job -- nextJob: %u, jobCompressedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompressedID, ctx->jobWriteID, ctx->numJobs); while (nextJob - ctx->jobWriteID >= ctx->numJobs) { ctx->stats.waitWrite++; ctx->stats.writeCounter++; + if (!ctx->completionMeasured) { + ctx->completion = ZSTD_getCompletion(ctx->cctx); + ctx->completionMeasured = 1; + } + DEBUG(2, "job creation detected completion %f\n", ctx->completion); DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); - pthread_cond_wait(&ctx->jobWrite_cond, &ctx->jobWrite_mutex); + pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); } - pthread_mutex_unlock(&ctx->jobWrite_mutex); + pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); DEBUG(3, "createCompressionJob(): continuing after job write\n"); DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); @@ -446,10 +511,10 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) job->lastJob = last; memcpy(job->src.start, ctx->input.buffer.start, ctx->lastDictSize + srcSize); job->dictSize = ctx->lastDictSize; - pthread_mutex_lock(&ctx->jobReady_mutex); + pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); ctx->jobReadyID++; - pthread_cond_signal(&ctx->jobReady_cond); - pthread_mutex_unlock(&ctx->jobReady_mutex); + pthread_cond_signal(&ctx->jobReady_cond.pCond); + pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); DEBUG(3, "finished job creation %u\n", nextJob); ctx->nextJobID++; DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index f492d92b..0c8edece 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -140,6 +140,9 @@ struct ZSTD_CCtx_s { /* Multi-threading */ U32 nbThreads; ZSTDMT_CCtx* mtctx; + + /* adaptive compression */ + double completion; }; @@ -2845,6 +2848,7 @@ static size_t ZSTD_compress_frameChunk (ZSTD_CCtx* cctx, BYTE* op = ostart; U32 const maxDist = 1 << cctx->appliedParams.cParams.windowLog; + cctx->completion = 0; if (cctx->appliedParams.fParams.checksumFlag && srcSize) XXH64_update(&cctx->xxhState, src, srcSize); @@ -2895,6 +2899,7 @@ static size_t ZSTD_compress_frameChunk (ZSTD_CCtx* cctx, } remaining -= blockSize; + cctx->completion = 1 - (double)remaining/srcSize; dstCapacity -= cSize; ip += blockSize; op += cSize; @@ -2997,6 +3002,10 @@ static size_t ZSTD_compressContinue_internal (ZSTD_CCtx* cctx, return fhSize; } +ZSTDLIB_API double ZSTD_getCompletion(ZSTD_CCtx* cctx) +{ + return cctx->completion; +} size_t ZSTD_compressContinue (ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, diff --git a/lib/zstd.h b/lib/zstd.h index 58e9a560..e835ad3a 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -808,7 +808,11 @@ ZSTDLIB_API size_t ZSTD_copyCCtx(ZSTD_CCtx* cctx, const ZSTD_CCtx* preparedCCtx, ZSTDLIB_API size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); ZSTDLIB_API size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); - +/*! ZSTD_getCompletion: get a double representing how much of a file/buffer has been compressed + * using ZSTD_compressContinue() + * return: a double value in the range of 0 to 1 representing how much a compression job has finished + */ +ZSTDLIB_API double ZSTD_getCompletion(ZSTD_CCtx* cctx); /*- Buffer-less streaming decompression (synchronous mode) From 044e40db5a97eabfc7d67e44faa751114767ac20 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 17 Jul 2017 11:19:23 -0700 Subject: [PATCH 077/145] removed freeCCtx() calls from createCCtx() so that it is not called twice during errors --- contrib/adaptive-compression/adapt.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index d45bdf85..a6ba4724 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -214,7 +214,6 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) job->lastJob = 0; if (!job->src.start || !job->dst.start) { DISPLAY("Could not allocate buffers for jobs\n"); - freeCCtx(ctx); return NULL; } job->src.capacity = FILE_CHUNK_SIZE; @@ -231,17 +230,14 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) ctx->input.buffer.start = malloc(ctx->input.buffer.capacity); if (!ctx->input.buffer.start) { DISPLAY("Error: could not allocate input buffer\n"); - freeCCtx(ctx); return NULL; } if (!ctx->cctx) { DISPLAY("Error: could not allocate ZSTD_CCtx\n"); - freeCCtx(ctx); return NULL; } if (!ctx->jobs) { DISPLAY("Error: could not allocate space for jobs during context creation\n"); - freeCCtx(ctx); return NULL; } { @@ -249,7 +245,6 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) FILE* dstFile = stdoutUsed ? stdout : fopen(outFilename, "wb"); if (dstFile == NULL) { DISPLAY("Error: could not open output file\n"); - freeCCtx(ctx); return NULL; } ctx->dstFile = dstFile; From 708238e07e28569ff946c05b9eef3f98b32af124 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 17 Jul 2017 14:01:13 -0700 Subject: [PATCH 078/145] open file outside of adaptCCtx, pass to the output thread --- contrib/adaptive-compression/adapt.c | 55 +++++++++++++++++----------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index a6ba4724..67d7fcc9 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -101,13 +101,18 @@ typedef struct { inBuff_t input; cStat_t stats; jobDescription* jobs; - FILE* dstFile; ZSTD_CCtx* cctx; } adaptCCtx; +typedef struct { + adaptCCtx* ctx; + FILE* dstFile; +} outputThreadArg; + typedef struct { FILE* srcFile; adaptCCtx* ctx; + outputThreadArg* otArg; } fcResources; static void freeCompressionJobs(adaptCCtx* ctx) @@ -151,7 +156,6 @@ static int freeCCtx(adaptCCtx* ctx) error |= destroyCond(&ctx->allJobsCompleted_cond); error |= destroyMutex(&ctx->jobWrite_mutex); error |= destroyCond(&ctx->jobWrite_cond); - error |= (ctx->dstFile != NULL && ctx->dstFile != stdout) ? fclose(ctx->dstFile) : 0; error |= ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)); free(ctx->input.buffer.start); if (ctx->jobs){ @@ -177,7 +181,7 @@ static int initCond(cond_t* cond) return ret; } -static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) +static adaptCCtx* createCCtx(unsigned numJobs) { adaptCCtx* const ctx = calloc(1, sizeof(adaptCCtx)); @@ -240,15 +244,6 @@ static adaptCCtx* createCCtx(unsigned numJobs, const char* const outFilename) DISPLAY("Error: could not allocate space for jobs during context creation\n"); return NULL; } - { - unsigned const stdoutUsed = !strcmp(outFilename, stdoutmark); - FILE* dstFile = stdoutUsed ? stdout : fopen(outFilename, "wb"); - if (dstFile == NULL) { - DISPLAY("Error: could not open output file\n"); - return NULL; - } - ctx->dstFile = dstFile; - } return ctx; } @@ -417,7 +412,9 @@ static void displayProgress(unsigned jobDoneID, unsigned cLevel, unsigned last) static void* outputThread(void* arg) { - adaptCCtx* ctx = (adaptCCtx*)arg; + outputThreadArg* const otArg = (outputThreadArg*)arg; + adaptCCtx* const ctx = otArg->ctx; + FILE* const dstFile = otArg->dstFile; unsigned currJob = 0; for ( ; ; ) { @@ -446,7 +443,7 @@ static void* outputThread(void* arg) return arg; } { - size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, ctx->dstFile); + size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, dstFile); if (writeSize != compressedSize) { DISPLAY("Error: an error occurred during file write operation\n"); ctx->threadError = 1; @@ -532,16 +529,16 @@ static void printStats(cStat_t stats) DISPLAY("# times waited on job Write: %u\n\n", stats.waitWrite); } -static int performCompression(adaptCCtx* ctx, FILE* const srcFile) +static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadArg* otArg) { - if (!ctx || !srcFile) { + if (!ctx || !srcFile || !otArg) { return 1; } /* create output thread */ { pthread_t out; - if (pthread_create(&out, NULL, &outputThread, ctx)) { + if (pthread_create(&out, NULL, &outputThread, otArg)) { DISPLAY("Error: could not create output thread\n"); ctx->threadError = 1; return 1; @@ -606,14 +603,25 @@ static fcResources createFileCompressionResources(const char* const srcFilename, outFilename = fileAndSuffix; } + { + unsigned const stdoutUsed = !strcmp(outFilename, stdoutmark); + FILE* const dstFile = stdoutUsed ? stdout : fopen(outFilename, "wb"); + fcr.otArg = malloc(sizeof(outputThreadArg)); + if (!fcr.otArg) { + DISPLAY("Error: could not allocate space for output thread argument\n"); + return fcr; + } + fcr.otArg->dstFile = dstFile; + } /* checking for errors */ - if (!outFilename || !srcFile) { - DISPLAY("Error: initial variables could not be allocated\n"); + if (!fcr.otArg->dstFile || !srcFile) { + DISPLAY("Error: some file(s) could not be opened\n"); return fcr; } /* creating context */ - fcr.ctx = createCCtx(numJobs, outFilename); + fcr.ctx = createCCtx(numJobs); + fcr.otArg->ctx = fcr.ctx; fcr.srcFile = srcFile; return fcr; } @@ -625,6 +633,11 @@ static int freeFileCompressionResources(fcResources* fcr) if (g_displayStats) printStats(fcr->ctx->stats); ret |= (fcr->srcFile != NULL) ? fclose(fcr->srcFile) : 0; ret |= (fcr->ctx != NULL) ? freeCCtx(fcr->ctx) : 0; + if (fcr->otArg) { + ret |= (fcr->otArg->dstFile != stdout) ? fclose(fcr->otArg->dstFile) : 0; + free(fcr->otArg); + /* no need to freeCCtx() on otArg->ctx because it should be the same context */ + } return ret; } @@ -634,7 +647,7 @@ static int compressFilename(const char* const srcFilename, const char* const dst UTIL_getTime(&g_startTime); g_streamedSize = 0; fcResources fcr = createFileCompressionResources(srcFilename, dstFilenameOrNull); - ret |= performCompression(fcr.ctx, fcr.srcFile); + ret |= performCompression(fcr.ctx, fcr.srcFile, fcr.otArg); ret |= freeFileCompressionResources(&fcr); return ret; } From 6be22f1f842edc65ec1ddcf9c961e9bb854e1dc1 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 17 Jul 2017 14:39:10 -0700 Subject: [PATCH 079/145] swap buffers instead of copying memory over --- contrib/adaptive-compression/adapt.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 67d7fcc9..3aec50c0 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -501,7 +501,12 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) job->src.size = srcSize; job->jobID = nextJob; job->lastJob = last; - memcpy(job->src.start, ctx->input.buffer.start, ctx->lastDictSize + srcSize); + { + /* swap buffer */ + void* const copy = job->src.start; + job->src.start = ctx->input.buffer.start; + ctx->input.buffer.start = copy; + } job->dictSize = ctx->lastDictSize; pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); ctx->jobReadyID++; @@ -514,7 +519,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) if (!last) { size_t const oldDictSize = ctx->lastDictSize; DEBUG(3, "oldDictSize %zu\n", oldDictSize); - memmove(ctx->input.buffer.start, ctx->input.buffer.start + oldDictSize, srcSize); + memcpy(ctx->input.buffer.start, job->src.start + oldDictSize, srcSize); ctx->lastDictSize = srcSize; ctx->input.filled = srcSize; } From b3c9e02bb6ab8807990ddc85a99f9953b9ee3578 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 17 Jul 2017 15:34:58 -0700 Subject: [PATCH 080/145] added signal to other threads whenever error occurs --- contrib/adaptive-compression/adapt.c | 44 ++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 3aec50c0..01a73a66 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -247,13 +247,31 @@ static adaptCCtx* createCCtx(unsigned numJobs) return ctx; } +static void signalErrorToThreads(adaptCCtx* ctx) +{ + ctx->threadError = 1; + pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); + pthread_cond_signal(&ctx->jobReady_cond.pCond); + pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); + pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); + pthread_cond_signal(&ctx->jobCompressed_cond.pCond); + pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); + + pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); + pthread_cond_signal(&ctx->jobWrite_cond.pCond); + pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); + + pthread_mutex_lock(&ctx->allJobsCompleted_mutex.pMutex); + pthread_cond_signal(&ctx->allJobsCompleted_cond.pCond); + pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); +} static void waitUntilAllJobsCompleted(adaptCCtx* ctx) { if (!ctx) return; pthread_mutex_lock(&ctx->allJobsCompleted_mutex.pMutex); - while (ctx->allJobsCompleted == 0) { + while (ctx->allJobsCompleted == 0 && !ctx->threadError) { pthread_cond_wait(&ctx->allJobsCompleted_cond.pCond, &ctx->allJobsCompleted_mutex.pMutex); } pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); @@ -327,7 +345,7 @@ static void* compressionThread(void* arg) jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "compressionThread(): waiting on job ready\n"); pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); - while(currJob + 1 > ctx->jobReadyID) { + while(currJob + 1 > ctx->jobReadyID && !ctx->threadError) { ctx->stats.waitReady++; ctx->stats.readyCounter++; DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); @@ -351,7 +369,7 @@ static void* compressionThread(void* arg) size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { DISPLAY("Error: something went wrong while starting compression\n"); - ctx->threadError = 1; + signalErrorToThreads(ctx); return arg; } } @@ -362,7 +380,7 @@ static void* compressionThread(void* arg) if (ZSTD_isError(hSize)) { DISPLAY("Error: something went wrong while continuing compression\n"); job->compressedSize = hSize; - ctx->threadError = 1; + signalErrorToThreads(ctx); return arg; } ZSTD_invalidateRepCodes(ctx->cctx); @@ -372,7 +390,7 @@ static void* compressionThread(void* arg) ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.capacity, job->src.start + job->dictSize, job->src.size); if (ZSTD_isError(job->compressedSize)) { DISPLAY("Error: something went wrong during compression: %s\n", ZSTD_getErrorName(job->compressedSize)); - ctx->threadError = 1; + signalErrorToThreads(ctx); return arg; } job->dst.size = job->compressedSize; @@ -422,7 +440,7 @@ static void* outputThread(void* arg) jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "outputThread(): waiting on job compressed\n"); pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); - while (currJob + 1 > ctx->jobCompressedID) { + while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { ctx->stats.waitCompressed++; ctx->stats.compressedCounter++; if (!ctx->completionMeasured) { @@ -439,14 +457,14 @@ static void* outputThread(void* arg) size_t const compressedSize = job->compressedSize; if (ZSTD_isError(compressedSize)) { DISPLAY("Error: an error occurred during compression\n"); - ctx->threadError = 1; + signalErrorToThreads(ctx); return arg; } { size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, dstFile); if (writeSize != compressedSize) { DISPLAY("Error: an error occurred during file write operation\n"); - ctx->threadError = 1; + signalErrorToThreads(ctx); return arg; } } @@ -482,7 +500,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) DEBUG(3, "createCompressionJob(): wait for job write\n"); pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); DEBUG(3, "Creating new compression job -- nextJob: %u, jobCompressedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompressedID, ctx->jobWriteID, ctx->numJobs); - while (nextJob - ctx->jobWriteID >= ctx->numJobs) { + while (nextJob - ctx->jobWriteID >= ctx->numJobs && !ctx->threadError) { ctx->stats.waitWrite++; ctx->stats.writeCounter++; if (!ctx->completionMeasured) { @@ -545,7 +563,7 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA pthread_t out; if (pthread_create(&out, NULL, &outputThread, otArg)) { DISPLAY("Error: could not create output thread\n"); - ctx->threadError = 1; + signalErrorToThreads(ctx); return 1; } } @@ -555,7 +573,7 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA pthread_t compression; if (pthread_create(&compression, NULL, &compressionThread, ctx)) { DISPLAY("Error: could not create compression thread\n"); - ctx->threadError = 1; + signalErrorToThreads(ctx); return 1; } } @@ -565,7 +583,7 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA size_t const readSize = fread(ctx->input.buffer.start + ctx->input.filled, 1, FILE_CHUNK_SIZE, srcFile); if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); - ctx->threadError = 1; + signalErrorToThreads(ctx); return 1; } g_streamedSize += readSize; @@ -574,7 +592,7 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA int const last = feof(srcFile); int const error = createCompressionJob(ctx, readSize, last); if (error != 0) { - ctx->threadError = 1; + signalErrorToThreads(ctx); return error; } } From 5af04c57b058fcf7be1e0b8545d918c901c4bbb6 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 17 Jul 2017 17:59:50 -0700 Subject: [PATCH 081/145] change parameters for compression level adapt --- contrib/adaptive-compression/adapt.c | 43 +++++++++++++++++++--------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 01a73a66..62f5ec91 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -25,7 +25,7 @@ #define DEFAULT_DISPLAY_LEVEL 1 #define DEFAULT_COMPRESSION_LEVEL 6 #define DEFAULT_ADAPT_PARAM 1 -#define MAX_COMPRESSION_LEVEL_CHANGE 10 +#define MAX_COMPRESSION_LEVEL_CHANGE 3 static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; @@ -277,6 +277,15 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); } +/* this function normalizes counters when compression level is changing */ +static void reduceCounters(adaptCCtx* ctx) +{ + unsigned const min = MIN(ctx->stats.compressedCounter, MIN(ctx->stats.writeCounter, ctx->stats.readyCounter)); + ctx->stats.writeCounter -= min; + ctx->stats.compressedCounter -= min; + ctx->stats.readyCounter -= min; +} + /* * Compression level is changed depending on which part of the compression process is lagging * Currently, three theads exist for job creation, compression, and file writing respectively. @@ -285,10 +294,10 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) * compression thread lag => decreased compression level * detecting which thread is lagging is done by keeping track of how many calls each thread makes to pthread_cond_wait */ -static unsigned adaptCompressionLevel(adaptCCtx* ctx) +static void adaptCompressionLevel(adaptCCtx* ctx) { if (g_forceCompressionLevel) { - return g_compressionLevel; + ctx->compressionLevel = g_compressionLevel; } else { unsigned reset = 0; @@ -296,10 +305,11 @@ static unsigned adaptCompressionLevel(adaptCCtx* ctx) unsigned const compressWaiting = ctx->adaptParam < ctx->stats.readyCounter; unsigned const writeWaiting = ctx->adaptParam < ctx->stats.compressedCounter; unsigned const createWaiting = ctx->adaptParam < ctx->stats.writeCounter; - unsigned const writeSlow = ((compressWaiting && createWaiting) || (createWaiting && !writeWaiting)); - unsigned const compressSlow = ((writeWaiting && createWaiting) || (writeWaiting && !compressWaiting)); - unsigned const createSlow = ((compressWaiting && writeWaiting) || (compressWaiting && !createWaiting)); - DEBUG(3, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); + unsigned const writeSlow = (compressWaiting && createWaiting); + unsigned const compressSlow = (writeWaiting && createWaiting); + unsigned const createSlow = (compressWaiting && writeWaiting); + DEBUG(2, "createWaiting: %u, compressWaiting: %u, writeWaiting: %u\n", createWaiting, compressWaiting, writeWaiting); + DEBUG(2, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); if (allSlow) { reset = 1; } @@ -310,10 +320,10 @@ static unsigned adaptCompressionLevel(adaptCCtx* ctx) } else if (compressSlow && ctx->compressionLevel > 1) { double const completion = ctx->completion; - unsigned const maxChange = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); + unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE-1)) + 1; unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); - DEBUG(2, "completion: %f\n", completion); + DEBUG(3, "completion: %f\n", completion); ctx->compressionLevel -= change; reset = 1; } @@ -324,7 +334,6 @@ static unsigned adaptCompressionLevel(adaptCCtx* ctx) ctx->completion = 1; ctx->completionMeasured = 0; } - return ctx->compressionLevel; } } @@ -348,6 +357,8 @@ static void* compressionThread(void* arg) while(currJob + 1 > ctx->jobReadyID && !ctx->threadError) { ctx->stats.waitReady++; ctx->stats.readyCounter++; + reduceCounters(ctx); + adaptCompressionLevel(ctx); DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); } @@ -357,13 +368,13 @@ static void* compressionThread(void* arg) DEBUG(3, "%.*s", (int)job->src.size, (char*)job->src.start); /* compress the data */ { - unsigned const cLevel = adaptCompressionLevel(ctx); + unsigned const cLevel = ctx->compressionLevel; DEBUG(3, "cLevel used: %u\n", cLevel); DEBUG(3, "compression level used: %u\n", cLevel); /* begin compression */ { size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); - DEBUG(2, "useDictSize: %zu, job->dictSize: %zu\n", useDictSize, job->dictSize); + DEBUG(3, "useDictSize: %zu, job->dictSize: %zu\n", useDictSize, job->dictSize); size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start + job->dictSize - useDictSize, useDictSize, cLevel); size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); @@ -443,11 +454,13 @@ static void* outputThread(void* arg) while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { ctx->stats.waitCompressed++; ctx->stats.compressedCounter++; + reduceCounters(ctx); if (!ctx->completionMeasured) { ctx->completion = ZSTD_getCompletion(ctx->cctx); ctx->completionMeasured = 1; } - DEBUG(2, "output detected completion: %f\n", ctx->completion); + adaptCompressionLevel(ctx); + DEBUG(3, "output detected completion: %f\n", ctx->completion); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } @@ -503,11 +516,13 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) while (nextJob - ctx->jobWriteID >= ctx->numJobs && !ctx->threadError) { ctx->stats.waitWrite++; ctx->stats.writeCounter++; + reduceCounters(ctx); if (!ctx->completionMeasured) { ctx->completion = ZSTD_getCompletion(ctx->cctx); ctx->completionMeasured = 1; } - DEBUG(2, "job creation detected completion %f\n", ctx->completion); + adaptCompressionLevel(ctx); + DEBUG(3, "job creation detected completion %f\n", ctx->completion); DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); } From ae47eab2fdb48cd55f20756f282dfdff3a7f4ee7 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 18 Jul 2017 12:58:50 -0700 Subject: [PATCH 082/145] changed test cases to use -s setting on the diffs --- .../adaptive-compression/test-correctness.sh | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/contrib/adaptive-compression/test-correctness.sh b/contrib/adaptive-compression/test-correctness.sh index b057cbd6..86d39ee4 100755 --- a/contrib/adaptive-compression/test-correctness.sh +++ b/contrib/adaptive-compression/test-correctness.sh @@ -2,239 +2,239 @@ echo "correctness tests -- general" ./datagen -s1 -g1GB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s2 -g500MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s3 -g250MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s4 -g125MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s5 -g50MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s6 -g25MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s7 -g10MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s8 -g5MB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s9 -g500KB > tmp ./adapt -otmp.zst tmp zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* echo -e "\ncorrectness tests -- streaming" ./datagen -s10 -g1GB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s11 -g100MB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s12 -g10MB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s13 -g1MB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s14 -g100KB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s15 -g10KB > tmp cat tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* echo -e "\ncorrectness tests -- read limit" ./datagen -s16 -g1GB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s17 -g100MB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s18 -g10MB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s19 -g1MB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s20 -g100KB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s21 -g10KB > tmp pv -L 50m -q tmp | ./adapt > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* echo -e "\ncorrectness tests -- write limit" ./datagen -s22 -g1GB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s23 -g100MB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s24 -g10MB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s25 -g1MB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s26 -g100KB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s27 -g10KB > tmp pv -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* echo -e "\ncorrectness tests -- read and write limits" ./datagen -s28 -g1GB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s29 -g100MB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s30 -g10MB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s31 -g1MB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s32 -g100KB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s33 -g10KB > tmp pv -L 50m -q tmp | ./adapt | pv -L 5m -q > tmp.zst zstd -d tmp.zst -o tmp2 -diff -q tmp tmp2 +diff -s -q tmp tmp2 rm tmp* echo -e "\ncorrectness tests -- forced compression level" ./datagen -s34 -g1GB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 -diff tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s35 -g100MB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 -diff tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s36 -g10MB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 -diff tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s37 -g1MB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 -diff tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s38 -g100KB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 -diff tmp tmp2 +diff -s -q tmp tmp2 rm tmp* ./datagen -s39 -g10KB > tmp ./adapt tmp -otmp.zst -i11 -f zstd -d tmp.zst -o tmp2 -diff tmp tmp2 +diff -s -q tmp tmp2 rm tmp* make clean From 29c36cf051545119fddfa898e8a03ce06e0380ef Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 18 Jul 2017 13:30:29 -0700 Subject: [PATCH 083/145] rename completion variable, split up fwrite operations in order to track progress --- contrib/adaptive-compression/adapt.c | 41 ++++++++++++++++++---------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 62f5ec91..404db6d9 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -87,8 +87,8 @@ typedef struct { unsigned jobWriteID; unsigned allJobsCompleted; unsigned adaptParam; - unsigned completionMeasured; - double completion; + unsigned compressionCompletionMeasured; + double compressionCompletion; mutex_t jobCompressed_mutex; cond_t jobCompressed_cond; mutex_t jobReady_mutex; @@ -319,7 +319,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) reset = 1; } else if (compressSlow && ctx->compressionLevel > 1) { - double const completion = ctx->completion; + double const completion = ctx->compressionCompletion; unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE-1)) + 1; unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); @@ -331,8 +331,8 @@ static void adaptCompressionLevel(adaptCCtx* ctx) ctx->stats.readyCounter = 0; ctx->stats.writeCounter = 0; ctx->stats.compressedCounter = 0; - ctx->completion = 1; - ctx->completionMeasured = 0; + ctx->compressionCompletion = 1; + ctx->compressionCompletionMeasured = 0; } } } @@ -455,12 +455,12 @@ static void* outputThread(void* arg) ctx->stats.waitCompressed++; ctx->stats.compressedCounter++; reduceCounters(ctx); - if (!ctx->completionMeasured) { - ctx->completion = ZSTD_getCompletion(ctx->cctx); - ctx->completionMeasured = 1; + if (!ctx->compressionCompletionMeasured) { + ctx->compressionCompletion = ZSTD_getCompletion(ctx->cctx); + ctx->compressionCompletionMeasured = 1; } adaptCompressionLevel(ctx); - DEBUG(3, "output detected completion: %f\n", ctx->completion); + DEBUG(3, "output detected completion: %f\n", ctx->compressionCompletion); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } @@ -468,14 +468,25 @@ static void* outputThread(void* arg) DEBUG(3, "outputThread(): continuing after job compressed\n"); { size_t const compressedSize = job->compressedSize; + size_t remaining = compressedSize; if (ZSTD_isError(compressedSize)) { DISPLAY("Error: an error occurred during compression\n"); signalErrorToThreads(ctx); return arg; } { - size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, dstFile); - if (writeSize != compressedSize) { + // size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, dstFile); + size_t const blockSize = 4 << 20; + size_t pos = 0; + for ( ; ; ) { + size_t const writeSize = MIN(remaining, blockSize); + size_t const ret = fwrite(job->dst.start + pos, 1, writeSize, dstFile); + if (ret != writeSize) break; + pos += ret; + remaining -= ret; + if (remaining == 0) break; + } + if (pos != compressedSize) { DISPLAY("Error: an error occurred during file write operation\n"); signalErrorToThreads(ctx); return arg; @@ -517,12 +528,12 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) ctx->stats.waitWrite++; ctx->stats.writeCounter++; reduceCounters(ctx); - if (!ctx->completionMeasured) { - ctx->completion = ZSTD_getCompletion(ctx->cctx); - ctx->completionMeasured = 1; + if (!ctx->compressionCompletion) { + ctx->compressionCompletion = ZSTD_getCompletion(ctx->cctx); + ctx->compressionCompletionMeasured = 1; } adaptCompressionLevel(ctx); - DEBUG(3, "job creation detected completion %f\n", ctx->completion); + DEBUG(3, "job creation detected completion %f\n", ctx->compressionCompletion); DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); } From a34bc30237b2e311753198912f3768ff1e7d0edc Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 18 Jul 2017 13:31:02 -0700 Subject: [PATCH 084/145] setting up basic readme --- contrib/adaptive-compression/README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 contrib/adaptive-compression/README.md diff --git a/contrib/adaptive-compression/README.md b/contrib/adaptive-compression/README.md new file mode 100644 index 00000000..1d261337 --- /dev/null +++ b/contrib/adaptive-compression/README.md @@ -0,0 +1,2 @@ +ZSTD_adapt is a compression tool targeted at optimizing performance across network connections. The tool aims at sensing network speeds and adapting compression level based on network or pipe speeds. +In many scenarios, using ZSTD without a properly adjusted compression level results in a pipe bottleneck, or a compressed file that could have been reduced in size. From ad66faf16a7093c0cc02b86da2d4c583a5bb7dbf Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 18 Jul 2017 15:23:11 -0700 Subject: [PATCH 085/145] added progress check for filewriting, put important shared data behind mutex when being read from/written to --- contrib/adaptive-compression/adapt.c | 78 +++++++++++++++++++++------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 404db6d9..b7f4dccf 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -88,7 +88,9 @@ typedef struct { unsigned allJobsCompleted; unsigned adaptParam; unsigned compressionCompletionMeasured; + unsigned writeCompletionMeasured; double compressionCompletion; + double writeCompletion; mutex_t jobCompressed_mutex; cond_t jobCompressed_cond; mutex_t jobReady_mutex; @@ -97,6 +99,8 @@ typedef struct { cond_t allJobsCompleted_cond; mutex_t jobWrite_mutex; cond_t jobWrite_cond; + mutex_t completion_mutex; + mutex_t stats_mutex; size_t lastDictSize; inBuff_t input; cStat_t stats; @@ -156,6 +160,8 @@ static int freeCCtx(adaptCCtx* ctx) error |= destroyCond(&ctx->allJobsCompleted_cond); error |= destroyMutex(&ctx->jobWrite_mutex); error |= destroyCond(&ctx->jobWrite_cond); + error |= destroyMutex(&ctx->completion_mutex); + error |= destroyMutex(&ctx->stats_mutex); error |= ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)); free(ctx->input.buffer.start); if (ctx->jobs){ @@ -200,6 +206,8 @@ static adaptCCtx* createCCtx(unsigned numJobs) pthreadError |= initCond(&ctx->allJobsCompleted_cond); pthreadError |= initMutex(&ctx->jobWrite_mutex); pthreadError |= initCond(&ctx->jobWrite_cond); + pthreadError |= initMutex(&ctx->completion_mutex); + pthreadError |= initMutex(&ctx->stats_mutex); if (pthreadError) return NULL; } ctx->numJobs = numJobs; @@ -315,24 +323,44 @@ static void adaptCompressionLevel(adaptCCtx* ctx) } else if ((writeSlow || createSlow) && ctx->compressionLevel < (unsigned)ZSTD_maxCLevel()) { DEBUG(3, "increasing compression level %u\n", ctx->compressionLevel); - ctx->compressionLevel++; - reset = 1; + double completion; + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + completion = ctx->writeCompletion; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + { + unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE - 1)) + 1; + unsigned const change = writeSlow ? MIN(maxChange, ZSTD_maxCLevel() - ctx->compressionLevel) : 1; + DEBUG(2, "writeSlow: %u, change: %u\n", writeSlow, change); + DEBUG(2, "write completion: %f\n", completion); + ctx->compressionLevel += change; + reset = 1; + } } else if (compressSlow && ctx->compressionLevel > 1) { - double const completion = ctx->compressionCompletion; - unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE-1)) + 1; - unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); - DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); - DEBUG(3, "completion: %f\n", completion); - ctx->compressionLevel -= change; - reset = 1; + double completion; + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + completion = ctx->compressionCompletion; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + { + unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE-1)) + 1; + unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); + DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); + DEBUG(3, "completion: %f\n", completion); + ctx->compressionLevel -= change; + reset = 1; + } } if (reset) { ctx->stats.readyCounter = 0; ctx->stats.writeCounter = 0; ctx->stats.compressedCounter = 0; + + pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->compressionCompletion = 1; ctx->compressionCompletionMeasured = 0; + ctx->writeCompletion = 1; + ctx->writeCompletionMeasured = 0; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } } } @@ -455,12 +483,14 @@ static void* outputThread(void* arg) ctx->stats.waitCompressed++; ctx->stats.compressedCounter++; reduceCounters(ctx); + pthread_mutex_lock(&ctx->completion_mutex.pMutex); if (!ctx->compressionCompletionMeasured) { ctx->compressionCompletion = ZSTD_getCompletion(ctx->cctx); ctx->compressionCompletionMeasured = 1; + DEBUG(3, "output detected completion: %f\n", ctx->compressionCompletion); } + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); adaptCompressionLevel(ctx); - DEBUG(3, "output detected completion: %f\n", ctx->compressionCompletion); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } @@ -484,6 +514,14 @@ static void* outputThread(void* arg) if (ret != writeSize) break; pos += ret; remaining -= ret; + + /* update completion variable for writing */ + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + if (!ctx->writeCompletionMeasured) { + ctx->writeCompletion = 1 - (double)remaining/compressedSize; + } + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + if (remaining == 0) break; } if (pos != compressedSize) { @@ -528,12 +566,10 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) ctx->stats.waitWrite++; ctx->stats.writeCounter++; reduceCounters(ctx); - if (!ctx->compressionCompletion) { - ctx->compressionCompletion = ZSTD_getCompletion(ctx->cctx); - ctx->compressionCompletionMeasured = 1; - } + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->writeCompletionMeasured = 1; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); adaptCompressionLevel(ctx); - DEBUG(3, "job creation detected completion %f\n", ctx->compressionCompletion); DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); } @@ -552,10 +588,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) ctx->input.buffer.start = copy; } job->dictSize = ctx->lastDictSize; - pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); - ctx->jobReadyID++; - pthread_cond_signal(&ctx->jobReady_cond.pCond); - pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); + DEBUG(3, "finished job creation %u\n", nextJob); ctx->nextJobID++; DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); @@ -567,6 +600,13 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) ctx->lastDictSize = srcSize; ctx->input.filled = srcSize; } + + /* signal job ready */ + pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); + ctx->jobReadyID++; + pthread_cond_signal(&ctx->jobReady_cond.pCond); + pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); + return 0; } From 2c4e4ddc50deb1798eb89de6ee0f0a539838e54c Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 18 Jul 2017 15:55:58 -0700 Subject: [PATCH 086/145] added mutex for stats struct --- contrib/adaptive-compression/adapt.c | 122 ++++++++++++++++----------- 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index b7f4dccf..35e26abb 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -288,10 +288,12 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) /* this function normalizes counters when compression level is changing */ static void reduceCounters(adaptCCtx* ctx) { + pthread_mutex_lock(&ctx->stats_mutex.pMutex); unsigned const min = MIN(ctx->stats.compressedCounter, MIN(ctx->stats.writeCounter, ctx->stats.readyCounter)); ctx->stats.writeCounter -= min; ctx->stats.compressedCounter -= min; ctx->stats.readyCounter -= min; + pthread_mutex_unlock(&ctx->stats_mutex.pMutex); } /* @@ -309,58 +311,68 @@ static void adaptCompressionLevel(adaptCCtx* ctx) } else { unsigned reset = 0; - unsigned const allSlow = ctx->adaptParam < ctx->stats.compressedCounter && ctx->adaptParam < ctx->stats.writeCounter && ctx->adaptParam < ctx->stats.readyCounter; - unsigned const compressWaiting = ctx->adaptParam < ctx->stats.readyCounter; - unsigned const writeWaiting = ctx->adaptParam < ctx->stats.compressedCounter; - unsigned const createWaiting = ctx->adaptParam < ctx->stats.writeCounter; - unsigned const writeSlow = (compressWaiting && createWaiting); - unsigned const compressSlow = (writeWaiting && createWaiting); - unsigned const createSlow = (compressWaiting && writeWaiting); - DEBUG(2, "createWaiting: %u, compressWaiting: %u, writeWaiting: %u\n", createWaiting, compressWaiting, writeWaiting); - DEBUG(2, "ready: %u compressed: %u write: %u\n", ctx->stats.readyCounter, ctx->stats.compressedCounter, ctx->stats.writeCounter); - if (allSlow) { - reset = 1; - } - else if ((writeSlow || createSlow) && ctx->compressionLevel < (unsigned)ZSTD_maxCLevel()) { - DEBUG(3, "increasing compression level %u\n", ctx->compressionLevel); - double completion; - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - completion = ctx->writeCompletion; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - { - unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE - 1)) + 1; - unsigned const change = writeSlow ? MIN(maxChange, ZSTD_maxCLevel() - ctx->compressionLevel) : 1; - DEBUG(2, "writeSlow: %u, change: %u\n", writeSlow, change); - DEBUG(2, "write completion: %f\n", completion); - ctx->compressionLevel += change; - reset = 1; - } - } - else if (compressSlow && ctx->compressionLevel > 1) { - double completion; - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - completion = ctx->compressionCompletion; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - { - unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE-1)) + 1; - unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); - DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); - DEBUG(3, "completion: %f\n", completion); - ctx->compressionLevel -= change; - reset = 1; - } - } - if (reset) { - ctx->stats.readyCounter = 0; - ctx->stats.writeCounter = 0; - ctx->stats.compressedCounter = 0; + unsigned allSlow; + unsigned compressWaiting; + unsigned writeWaiting; + unsigned createWaiting; - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->compressionCompletion = 1; - ctx->compressionCompletionMeasured = 0; - ctx->writeCompletion = 1; - ctx->writeCompletionMeasured = 0; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->stats_mutex.pMutex); + allSlow = ctx->adaptParam < ctx->stats.compressedCounter && ctx->adaptParam < ctx->stats.writeCounter && ctx->adaptParam < ctx->stats.readyCounter; + compressWaiting = ctx->adaptParam < ctx->stats.readyCounter; + writeWaiting = ctx->adaptParam < ctx->stats.compressedCounter; + createWaiting = ctx->adaptParam < ctx->stats.writeCounter; + pthread_mutex_unlock(&ctx->stats_mutex.pMutex); + { + unsigned const writeSlow = (compressWaiting && createWaiting); + unsigned const compressSlow = (writeWaiting && createWaiting); + unsigned const createSlow = (compressWaiting && writeWaiting); + DEBUG(2, "createWaiting: %u, compressWaiting: %u, writeWaiting: %u\n", createWaiting, compressWaiting, writeWaiting); + if (allSlow) { + reset = 1; + } + else if ((writeSlow || createSlow) && ctx->compressionLevel < (unsigned)ZSTD_maxCLevel()) { + DEBUG(3, "increasing compression level %u\n", ctx->compressionLevel); + double completion; + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + completion = ctx->writeCompletion; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + { + unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE - 1)) + 1; + unsigned const change = writeSlow ? MIN(maxChange, ZSTD_maxCLevel() - ctx->compressionLevel) : 1; + DEBUG(2, "writeSlow: %u, change: %u\n", writeSlow, change); + DEBUG(2, "write completion: %f\n", completion); + ctx->compressionLevel += change; + reset = 1; + } + } + else if (compressSlow && ctx->compressionLevel > 1) { + double completion; + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + completion = ctx->compressionCompletion; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + { + unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE-1)) + 1; + unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); + DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); + DEBUG(3, "completion: %f\n", completion); + ctx->compressionLevel -= change; + reset = 1; + } + } + if (reset) { + pthread_mutex_lock(&ctx->stats_mutex.pMutex); + ctx->stats.readyCounter = 0; + ctx->stats.writeCounter = 0; + ctx->stats.compressedCounter = 0; + pthread_mutex_unlock(&ctx->stats_mutex.pMutex); + + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->compressionCompletion = 1; + ctx->compressionCompletionMeasured = 0; + ctx->writeCompletion = 1; + ctx->writeCompletionMeasured = 0; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + } } } } @@ -383,8 +395,10 @@ static void* compressionThread(void* arg) DEBUG(3, "compressionThread(): waiting on job ready\n"); pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); while(currJob + 1 > ctx->jobReadyID && !ctx->threadError) { + pthread_mutex_lock(&ctx->stats_mutex.pMutex); ctx->stats.waitReady++; ctx->stats.readyCounter++; + pthread_mutex_unlock(&ctx->stats_mutex.pMutex); reduceCounters(ctx); adaptCompressionLevel(ctx); DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); @@ -480,8 +494,10 @@ static void* outputThread(void* arg) DEBUG(3, "outputThread(): waiting on job compressed\n"); pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { + pthread_mutex_lock(&ctx->stats_mutex.pMutex); ctx->stats.waitCompressed++; ctx->stats.compressedCounter++; + pthread_mutex_unlock(&ctx->stats_mutex.pMutex); reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); if (!ctx->compressionCompletionMeasured) { @@ -563,8 +579,10 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); DEBUG(3, "Creating new compression job -- nextJob: %u, jobCompressedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompressedID, ctx->jobWriteID, ctx->numJobs); while (nextJob - ctx->jobWriteID >= ctx->numJobs && !ctx->threadError) { + pthread_mutex_lock(&ctx->stats_mutex.pMutex); ctx->stats.waitWrite++; ctx->stats.writeCounter++; + pthread_mutex_unlock(&ctx->stats_mutex.pMutex); reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->writeCompletionMeasured = 1; @@ -719,7 +737,9 @@ static int freeFileCompressionResources(fcResources* fcr) { int ret = 0; waitUntilAllJobsCompleted(fcr->ctx); + pthread_mutex_lock(&fcr->ctx->stats_mutex.pMutex); if (g_displayStats) printStats(fcr->ctx->stats); + pthread_mutex_unlock(&fcr->ctx->stats_mutex.pMutex); ret |= (fcr->srcFile != NULL) ? fclose(fcr->srcFile) : 0; ret |= (fcr->ctx != NULL) ? freeCCtx(fcr->ctx) : 0; if (fcr->otArg) { From 3d7f1afadd508de925783df775414b6e8e24e7bf Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 18 Jul 2017 17:32:36 -0700 Subject: [PATCH 087/145] changed createCCtx() to split into initialization and creation --- contrib/adaptive-compression/adapt.c | 52 +++++++++++++++++++--------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 35e26abb..7eb4333a 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -187,14 +187,8 @@ static int initCond(cond_t* cond) return ret; } -static adaptCCtx* createCCtx(unsigned numJobs) +static int initCCtx(adaptCCtx* ctx, unsigned numJobs) { - - adaptCCtx* const ctx = calloc(1, sizeof(adaptCCtx)); - if (ctx == NULL) { - DISPLAY("Error: could not allocate space for context\n"); - return NULL; - } ctx->compressionLevel = g_compressionLevel; { int pthreadError = 0; @@ -208,7 +202,7 @@ static adaptCCtx* createCCtx(unsigned numJobs) pthreadError |= initCond(&ctx->jobWrite_cond); pthreadError |= initMutex(&ctx->completion_mutex); pthreadError |= initMutex(&ctx->stats_mutex); - if (pthreadError) return NULL; + if (pthreadError) return pthreadError; } ctx->numJobs = numJobs; ctx->jobReadyID = 0; @@ -216,6 +210,12 @@ static adaptCCtx* createCCtx(unsigned numJobs) ctx->jobWriteID = 0; ctx->lastDictSize = 0; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); + + if (!ctx->jobs) { + DISPLAY("Error: could not allocate space for jobs during context creation\n"); + return 1; + } + /* initializing jobs */ { unsigned jobNum; @@ -226,33 +226,51 @@ static adaptCCtx* createCCtx(unsigned numJobs) job->lastJob = 0; if (!job->src.start || !job->dst.start) { DISPLAY("Could not allocate buffers for jobs\n"); - return NULL; + return 1; } job->src.capacity = FILE_CHUNK_SIZE; job->dst.capacity = ZSTD_compressBound(FILE_CHUNK_SIZE); } } + ctx->nextJobID = 0; ctx->threadError = 0; ctx->allJobsCompleted = 0; ctx->adaptParam = DEFAULT_ADAPT_PARAM; + ctx->cctx = ZSTD_createCCtx(); + if (!ctx->cctx) { + DISPLAY("Error: could not allocate ZSTD_CCtx\n"); + return 1; + } + ctx->input.filled = 0; ctx->input.buffer.capacity = 2 * FILE_CHUNK_SIZE; + ctx->input.buffer.start = malloc(ctx->input.buffer.capacity); if (!ctx->input.buffer.start) { DISPLAY("Error: could not allocate input buffer\n"); + return 1; + } + return 0; +} + +static adaptCCtx* createCCtx(unsigned numJobs) +{ + + adaptCCtx* const ctx = calloc(1, sizeof(adaptCCtx)); + if (ctx == NULL) { + DISPLAY("Error: could not allocate space for context\n"); return NULL; } - if (!ctx->cctx) { - DISPLAY("Error: could not allocate ZSTD_CCtx\n"); - return NULL; + { + int const error = initCCtx(ctx, numJobs); + if (error) { + freeCCtx(ctx); + return NULL; + } + return ctx; } - if (!ctx->jobs) { - DISPLAY("Error: could not allocate space for jobs during context creation\n"); - return NULL; - } - return ctx; } static void signalErrorToThreads(adaptCCtx* ctx) From 6119cd216458d46909d2d12d858ac9bfa1c7837f Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 09:43:17 -0700 Subject: [PATCH 088/145] added additional print for help menu --- contrib/adaptive-compression/adapt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 7eb4333a..2182ad1e 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -827,6 +827,7 @@ static void help() PRINT(" -i# : provide initial compression level\n"); PRINT(" -s : display information stats\n"); PRINT(" -h : display help/information\n"); + PRINT(" -f : force the compression level to stay constant\n"); } /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) From 559ea4ff25b3728024a55fdc9966925de2e116ba Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 09:59:17 -0700 Subject: [PATCH 089/145] split up read process into smaller chunks --- contrib/adaptive-compression/adapt.c | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 2182ad1e..d3ffe6a8 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -682,17 +682,30 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA /* creating jobs */ for ( ; ; ) { - size_t const readSize = fread(ctx->input.buffer.start + ctx->input.filled, 1, FILE_CHUNK_SIZE, srcFile); - if (readSize != FILE_CHUNK_SIZE && !feof(srcFile)) { + size_t pos = 0; + size_t const readBlockSize = 1 << 15; + size_t remaining = FILE_CHUNK_SIZE; + while (remaining != 0 && !feof(srcFile)) { + size_t const ret = fread(ctx->input.buffer.start + ctx->input.filled + pos, 1, readBlockSize, srcFile); + if (ret != readBlockSize && !feof(srcFile)) { + /* error could not read correct number of bytes */ + DISPLAY("Error: problem occurred during read from src file\n"); + signalErrorToThreads(ctx); + return 1; + } + pos += ret; + remaining -= ret; + } + if (remaining != 0 && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); signalErrorToThreads(ctx); return 1; } - g_streamedSize += readSize; + g_streamedSize += pos; /* reading was fine, now create the compression job */ { int const last = feof(srcFile); - int const error = createCompressionJob(ctx, readSize, last); + int const error = createCompressionJob(ctx, pos, last); if (error != 0) { signalErrorToThreads(ctx); return error; From e11bf55d0bf0a83327031b921480625781133033 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 10:10:47 -0700 Subject: [PATCH 090/145] added mechanism for measuring how much of a job has been created --- contrib/adaptive-compression/adapt.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index d3ffe6a8..de2e5e13 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -89,8 +89,10 @@ typedef struct { unsigned adaptParam; unsigned compressionCompletionMeasured; unsigned writeCompletionMeasured; + unsigned createCompletionMeasured; double compressionCompletion; double writeCompletion; + double createCompletion; mutex_t jobCompressed_mutex; cond_t jobCompressed_cond; mutex_t jobReady_mutex; @@ -344,7 +346,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) unsigned const writeSlow = (compressWaiting && createWaiting); unsigned const compressSlow = (writeWaiting && createWaiting); unsigned const createSlow = (compressWaiting && writeWaiting); - DEBUG(2, "createWaiting: %u, compressWaiting: %u, writeWaiting: %u\n", createWaiting, compressWaiting, writeWaiting); + DEBUG(3, "createWaiting: %u, compressWaiting: %u, writeWaiting: %u\n", createWaiting, compressWaiting, writeWaiting); if (allSlow) { reset = 1; } @@ -352,13 +354,14 @@ static void adaptCompressionLevel(adaptCCtx* ctx) DEBUG(3, "increasing compression level %u\n", ctx->compressionLevel); double completion; pthread_mutex_lock(&ctx->completion_mutex.pMutex); - completion = ctx->writeCompletion; + completion = writeSlow ? ctx->writeCompletion : ctx->createCompletion; + DEBUG(2, "write completion: %f, create completion: %f\n", ctx->writeCompletion, ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); { unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE - 1)) + 1; unsigned const change = writeSlow ? MIN(maxChange, ZSTD_maxCLevel() - ctx->compressionLevel) : 1; - DEBUG(2, "writeSlow: %u, change: %u\n", writeSlow, change); - DEBUG(2, "write completion: %f\n", completion); + DEBUG(3, "writeSlow: %u, change: %u\n", writeSlow, change); + DEBUG(3, "write completion: %f\n", completion); ctx->compressionLevel += change; reset = 1; } @@ -418,6 +421,9 @@ static void* compressionThread(void* arg) ctx->stats.readyCounter++; pthread_mutex_unlock(&ctx->stats_mutex.pMutex); reduceCounters(ctx); + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->createCompletionMeasured = 1; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); adaptCompressionLevel(ctx); DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); @@ -695,6 +701,12 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA } pos += ret; remaining -= ret; + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + if (!ctx->createCompletionMeasured) { + ctx->createCompletion = 1 - (double)remaining/((size_t)FILE_CHUNK_SIZE); + } + DEBUG(3, "create completion: %f\n", ctx->createCompletion); + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } if (remaining != 0 && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); From 4497ecf297d3d9d71dca2f85aec3ee5467f1a37b Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 10:14:00 -0700 Subject: [PATCH 091/145] change compression level only right before actually performing compression. When waiting, only update waiting statistics. --- contrib/adaptive-compression/adapt.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index de2e5e13..b928d0a2 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -424,7 +424,6 @@ static void* compressionThread(void* arg) pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->createCompletionMeasured = 1; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - adaptCompressionLevel(ctx); DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); } @@ -434,6 +433,7 @@ static void* compressionThread(void* arg) DEBUG(3, "%.*s", (int)job->src.size, (char*)job->src.start); /* compress the data */ { + adaptCompressionLevel(ctx); unsigned const cLevel = ctx->compressionLevel; DEBUG(3, "cLevel used: %u\n", cLevel); DEBUG(3, "compression level used: %u\n", cLevel); @@ -530,7 +530,6 @@ static void* outputThread(void* arg) DEBUG(3, "output detected completion: %f\n", ctx->compressionCompletion); } pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - adaptCompressionLevel(ctx); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } @@ -611,7 +610,6 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->writeCompletionMeasured = 1; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - adaptCompressionLevel(ctx); DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); } From 338951cd482f630cd12b7ca4d15d84c594a77308 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 10:23:46 -0700 Subject: [PATCH 092/145] moved compression adapt to avoid warning --- contrib/adaptive-compression/adapt.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index b928d0a2..cf908ca9 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -431,9 +431,12 @@ static void* compressionThread(void* arg) DEBUG(3, "compressionThread(): continuing after job ready\n"); DEBUG(3, "DICTIONARY ENDED\n"); DEBUG(3, "%.*s", (int)job->src.size, (char*)job->src.start); + + /* adapt compression level */ + adaptCompressionLevel(ctx); + /* compress the data */ { - adaptCompressionLevel(ctx); unsigned const cLevel = ctx->compressionLevel; DEBUG(3, "cLevel used: %u\n", cLevel); DEBUG(3, "compression level used: %u\n", cLevel); From f1ac518b59f471434fd4e9c7ce7c4ae609189914 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 11:23:40 -0700 Subject: [PATCH 093/145] split compression into smaller blocks --- contrib/adaptive-compression/adapt.c | 84 ++++++++++++++++++---------- 1 file changed, 53 insertions(+), 31 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index cf908ca9..17721f87 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -434,44 +434,66 @@ static void* compressionThread(void* arg) /* adapt compression level */ adaptCompressionLevel(ctx); - + /* compress the data */ { + size_t const compressionBlockSize = 4 << 17; /* 128 KB */ unsigned const cLevel = ctx->compressionLevel; + unsigned blockNum = 0; + size_t remaining = job->src.size; + size_t srcPos = 0; + size_t dstPos = 0; + size_t dictPos = 0; DEBUG(3, "cLevel used: %u\n", cLevel); DEBUG(3, "compression level used: %u\n", cLevel); - /* begin compression */ - { - size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); - DEBUG(3, "useDictSize: %zu, job->dictSize: %zu\n", useDictSize, job->dictSize); - size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); - size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start + job->dictSize - useDictSize, useDictSize, cLevel); - size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); - if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { - DISPLAY("Error: something went wrong while starting compression\n"); - signalErrorToThreads(ctx); - return arg; - } - } - /* continue compression */ - if (currJob != 0) { /* not first job flush/overwrite the frame header */ - size_t const hSize = ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.capacity, job->src.start + job->dictSize, 0); - if (ZSTD_isError(hSize)) { - DISPLAY("Error: something went wrong while continuing compression\n"); - job->compressedSize = hSize; - signalErrorToThreads(ctx); - return arg; + /* reset compressed size */ + job->compressedSize = 0; + + while (remaining != 0) { + size_t const actualBlockSize = MIN(remaining, compressionBlockSize); + /* begin compression */ + { + size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); + DEBUG(3, "useDictSize: %zu, job->dictSize: %zu\n", useDictSize, job->dictSize); + size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); + size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start + job->dictSize - useDictSize + dictPos, useDictSize, cLevel); + size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); + if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { + DISPLAY("Error: something went wrong while starting compression\n"); + signalErrorToThreads(ctx); + return arg; + } + } + + /* continue compression */ + if (currJob != 0 || blockNum != 0) { /* not first block of first job flush/overwrite the frame header */ + size_t const hSize = ZSTD_compressContinue(ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, 0); + if (ZSTD_isError(hSize)) { + DISPLAY("Error: something went wrong while continuing compression\n"); + job->compressedSize = hSize; + signalErrorToThreads(ctx); + return arg; + } + ZSTD_invalidateRepCodes(ctx->cctx); + } + { + + size_t const ret = (job->lastJob && remaining <= compressionBlockSize) ? + ZSTD_compressEnd (ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, actualBlockSize) : + ZSTD_compressContinue(ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, actualBlockSize); + if (ZSTD_isError(ret)) { + DISPLAY("Error: something went wrong during compression: %s\n", ZSTD_getErrorName(ret)); + signalErrorToThreads(ctx); + return arg; + } + job->compressedSize += ret; + remaining -= actualBlockSize; + srcPos += actualBlockSize; + dstPos += ret; + dictPos += actualBlockSize; + blockNum++; } - ZSTD_invalidateRepCodes(ctx->cctx); - } - job->compressedSize = (job->lastJob) ? - ZSTD_compressEnd (ctx->cctx, job->dst.start, job->dst.capacity, job->src.start + job->dictSize, job->src.size) : - ZSTD_compressContinue(ctx->cctx, job->dst.start, job->dst.capacity, job->src.start + job->dictSize, job->src.size); - if (ZSTD_isError(job->compressedSize)) { - DISPLAY("Error: something went wrong during compression: %s\n", ZSTD_getErrorName(job->compressedSize)); - signalErrorToThreads(ctx); - return arg; } job->dst.size = job->compressedSize; } From 5a85c57e3055b9a1bace0623bdfe8600d7efb128 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 11:47:17 -0700 Subject: [PATCH 094/145] set up new calculations compression completion progress --- contrib/adaptive-compression/adapt.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 17721f87..ab332f49 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -355,7 +355,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) double completion; pthread_mutex_lock(&ctx->completion_mutex.pMutex); completion = writeSlow ? ctx->writeCompletion : ctx->createCompletion; - DEBUG(2, "write completion: %f, create completion: %f\n", ctx->writeCompletion, ctx->createCompletion); + DEBUG(3, "write completion: %f, create completion: %f\n", ctx->writeCompletion, ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); { unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE - 1)) + 1; @@ -375,7 +375,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE-1)) + 1; unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); - DEBUG(3, "completion: %f\n", completion); + DEBUG(2, "completion: %f\n", completion); ctx->compressionLevel -= change; reset = 1; } @@ -493,6 +493,13 @@ static void* compressionThread(void* arg) dstPos += ret; dictPos += actualBlockSize; blockNum++; + + /* update completion */ + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + if (!ctx->compressionCompletionMeasured) { + ctx->compressionCompletion = 1 - (double)remaining/job->src.size; + } + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } } job->dst.size = job->compressedSize; @@ -549,11 +556,7 @@ static void* outputThread(void* arg) pthread_mutex_unlock(&ctx->stats_mutex.pMutex); reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); - if (!ctx->compressionCompletionMeasured) { - ctx->compressionCompletion = ZSTD_getCompletion(ctx->cctx); - ctx->compressionCompletionMeasured = 1; - DEBUG(3, "output detected completion: %f\n", ctx->compressionCompletion); - } + ctx->compressionCompletionMeasured = 1; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); From 6945b3c43d5be449d0bd1ff06c40bca2e5230fa3 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 11:51:50 -0700 Subject: [PATCH 095/145] removed previous version of completion for compression --- lib/compress/zstd_compress.c | 10 ---------- lib/zstd.h | 5 ----- 2 files changed, 15 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 0c8edece..90002c2c 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -140,9 +140,6 @@ struct ZSTD_CCtx_s { /* Multi-threading */ U32 nbThreads; ZSTDMT_CCtx* mtctx; - - /* adaptive compression */ - double completion; }; @@ -2848,7 +2845,6 @@ static size_t ZSTD_compress_frameChunk (ZSTD_CCtx* cctx, BYTE* op = ostart; U32 const maxDist = 1 << cctx->appliedParams.cParams.windowLog; - cctx->completion = 0; if (cctx->appliedParams.fParams.checksumFlag && srcSize) XXH64_update(&cctx->xxhState, src, srcSize); @@ -2899,7 +2895,6 @@ static size_t ZSTD_compress_frameChunk (ZSTD_CCtx* cctx, } remaining -= blockSize; - cctx->completion = 1 - (double)remaining/srcSize; dstCapacity -= cSize; ip += blockSize; op += cSize; @@ -3002,11 +2997,6 @@ static size_t ZSTD_compressContinue_internal (ZSTD_CCtx* cctx, return fhSize; } -ZSTDLIB_API double ZSTD_getCompletion(ZSTD_CCtx* cctx) -{ - return cctx->completion; -} - size_t ZSTD_compressContinue (ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize) diff --git a/lib/zstd.h b/lib/zstd.h index e835ad3a..6d69d94e 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -808,11 +808,6 @@ ZSTDLIB_API size_t ZSTD_copyCCtx(ZSTD_CCtx* cctx, const ZSTD_CCtx* preparedCCtx, ZSTDLIB_API size_t ZSTD_compressContinue(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); ZSTDLIB_API size_t ZSTD_compressEnd(ZSTD_CCtx* cctx, void* dst, size_t dstCapacity, const void* src, size_t srcSize); -/*! ZSTD_getCompletion: get a double representing how much of a file/buffer has been compressed - * using ZSTD_compressContinue() - * return: a double value in the range of 0 to 1 representing how much a compression job has finished - */ -ZSTDLIB_API double ZSTD_getCompletion(ZSTD_CCtx* cctx); /*- Buffer-less streaming decompression (synchronous mode) From 42382c121639c8f620c2c67acfc2d2983781dffa Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 13:30:07 -0700 Subject: [PATCH 096/145] added some debug statements, adjusted end condition --- contrib/adaptive-compression/adapt.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index ab332f49..d572ee19 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -375,7 +375,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE-1)) + 1; unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); - DEBUG(2, "completion: %f\n", completion); + DEBUG(3, "completion: %f\n", completion); ctx->compressionLevel -= change; reset = 1; } @@ -452,6 +452,8 @@ static void* compressionThread(void* arg) while (remaining != 0) { size_t const actualBlockSize = MIN(remaining, compressionBlockSize); + DEBUG(2, "remaining: %zu\n", remaining); + DEBUG(2, "actualBlockSize: %zu\n", actualBlockSize); /* begin compression */ { size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); @@ -478,8 +480,10 @@ static void* compressionThread(void* arg) ZSTD_invalidateRepCodes(ctx->cctx); } { - - size_t const ret = (job->lastJob && remaining <= compressionBlockSize) ? + DEBUG(2, "write out ending: %d\n", job->lastJob && (remaining == actualBlockSize)); + DEBUG(2, "lastJob %u\n", job->lastJob); + DEBUG(2, "compressionBlockSize %zu\n", compressionBlockSize); + size_t const ret = (job->lastJob && remaining == actualBlockSize) ? ZSTD_compressEnd (ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, actualBlockSize) : ZSTD_compressContinue(ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, actualBlockSize); if (ZSTD_isError(ret)) { From 6767abe65273056289e431c5454b937b13bc34bc Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 14:54:15 -0700 Subject: [PATCH 097/145] fixing error when file size is multiple of job size (in which case, the srcSize of the last job is 0) --- contrib/adaptive-compression/adapt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index d572ee19..dcaf3aef 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -450,7 +450,7 @@ static void* compressionThread(void* arg) /* reset compressed size */ job->compressedSize = 0; - while (remaining != 0) { + do { size_t const actualBlockSize = MIN(remaining, compressionBlockSize); DEBUG(2, "remaining: %zu\n", remaining); DEBUG(2, "actualBlockSize: %zu\n", actualBlockSize); @@ -505,7 +505,7 @@ static void* compressionThread(void* arg) } pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } - } + } while (remaining != 0); job->dst.size = job->compressedSize; } pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); From 2a22c7915e749879e1abc8283b86212d8947e5fb Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 16:00:54 -0700 Subject: [PATCH 098/145] call ZSTD_compressBegin() once --- contrib/adaptive-compression/adapt.c | 31 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index dcaf3aef..cc72d155 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -437,36 +437,36 @@ static void* compressionThread(void* arg) /* compress the data */ { - size_t const compressionBlockSize = 4 << 17; /* 128 KB */ + size_t const compressionBlockSize = 1 << 17; /* 128 KB */ unsigned const cLevel = ctx->compressionLevel; unsigned blockNum = 0; size_t remaining = job->src.size; size_t srcPos = 0; size_t dstPos = 0; - size_t dictPos = 0; DEBUG(3, "cLevel used: %u\n", cLevel); DEBUG(3, "compression level used: %u\n", cLevel); /* reset compressed size */ job->compressedSize = 0; + /* begin compression */ + { + size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); + DEBUG(3, "useDictSize: %zu, job->dictSize: %zu\n", useDictSize, job->dictSize); + size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); + size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start + job->dictSize - useDictSize, useDictSize, cLevel); + size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); + if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { + DISPLAY("Error: something went wrong while starting compression\n"); + signalErrorToThreads(ctx); + return arg; + } + } + do { size_t const actualBlockSize = MIN(remaining, compressionBlockSize); DEBUG(2, "remaining: %zu\n", remaining); DEBUG(2, "actualBlockSize: %zu\n", actualBlockSize); - /* begin compression */ - { - size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); - DEBUG(3, "useDictSize: %zu, job->dictSize: %zu\n", useDictSize, job->dictSize); - size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); - size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start + job->dictSize - useDictSize + dictPos, useDictSize, cLevel); - size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); - if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { - DISPLAY("Error: something went wrong while starting compression\n"); - signalErrorToThreads(ctx); - return arg; - } - } /* continue compression */ if (currJob != 0 || blockNum != 0) { /* not first block of first job flush/overwrite the frame header */ @@ -495,7 +495,6 @@ static void* compressionThread(void* arg) remaining -= actualBlockSize; srcPos += actualBlockSize; dstPos += ret; - dictPos += actualBlockSize; blockNum++; /* update completion */ From dcf609f835b0c0fb2499d13c8fa755d99808341a Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 19 Jul 2017 16:36:33 -0700 Subject: [PATCH 099/145] make adaptCompressionLevel oscillate less --- contrib/adaptive-compression/adapt.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index cc72d155..ce92d914 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -358,8 +358,8 @@ static void adaptCompressionLevel(adaptCCtx* ctx) DEBUG(3, "write completion: %f, create completion: %f\n", ctx->writeCompletion, ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); { - unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE - 1)) + 1; - unsigned const change = writeSlow ? MIN(maxChange, ZSTD_maxCLevel() - ctx->compressionLevel) : 1; + unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE)); + unsigned const change = MIN(maxChange, ZSTD_maxCLevel() - ctx->compressionLevel); DEBUG(3, "writeSlow: %u, change: %u\n", writeSlow, change); DEBUG(3, "write completion: %f\n", completion); ctx->compressionLevel += change; @@ -372,7 +372,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) completion = ctx->compressionCompletion; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); { - unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE-1)) + 1; + unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE)); unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); DEBUG(3, "completion: %f\n", completion); @@ -388,10 +388,12 @@ static void adaptCompressionLevel(adaptCCtx* ctx) pthread_mutex_unlock(&ctx->stats_mutex.pMutex); pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->compressionCompletion = 1; + ctx->compressionCompletion = 0; ctx->compressionCompletionMeasured = 0; - ctx->writeCompletion = 1; + ctx->writeCompletion = 0; ctx->writeCompletionMeasured = 0; + ctx->createCompletion = 0; + ctx->createCompletionMeasured = 0; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } } @@ -423,6 +425,7 @@ static void* compressionThread(void* arg) reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->createCompletionMeasured = 1; + DEBUG(2, "create completion: %f\n", ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); @@ -465,8 +468,8 @@ static void* compressionThread(void* arg) do { size_t const actualBlockSize = MIN(remaining, compressionBlockSize); - DEBUG(2, "remaining: %zu\n", remaining); - DEBUG(2, "actualBlockSize: %zu\n", actualBlockSize); + DEBUG(3, "remaining: %zu\n", remaining); + DEBUG(3, "actualBlockSize: %zu\n", actualBlockSize); /* continue compression */ if (currJob != 0 || blockNum != 0) { /* not first block of first job flush/overwrite the frame header */ @@ -480,9 +483,9 @@ static void* compressionThread(void* arg) ZSTD_invalidateRepCodes(ctx->cctx); } { - DEBUG(2, "write out ending: %d\n", job->lastJob && (remaining == actualBlockSize)); - DEBUG(2, "lastJob %u\n", job->lastJob); - DEBUG(2, "compressionBlockSize %zu\n", compressionBlockSize); + DEBUG(3, "write out ending: %d\n", job->lastJob && (remaining == actualBlockSize)); + DEBUG(3, "lastJob %u\n", job->lastJob); + DEBUG(3, "compressionBlockSize %zu\n", compressionBlockSize); size_t const ret = (job->lastJob && remaining == actualBlockSize) ? ZSTD_compressEnd (ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, actualBlockSize) : ZSTD_compressContinue(ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, actualBlockSize); @@ -560,6 +563,7 @@ static void* outputThread(void* arg) reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->compressionCompletionMeasured = 1; + DEBUG(2, "compressionCompletion %f\n", ctx->compressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); @@ -576,7 +580,7 @@ static void* outputThread(void* arg) } { // size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, dstFile); - size_t const blockSize = 4 << 20; + size_t const blockSize = 64 << 10; /* 64 KB */ size_t pos = 0; for ( ; ; ) { size_t const writeSize = MIN(remaining, blockSize); @@ -640,6 +644,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->writeCompletionMeasured = 1; + DEBUG(2, "writeCompletion: %f\n", ctx->writeCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); From 7ab758a640629665ba43f4f8ce5fa38c9260b1ba Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 20 Jul 2017 10:53:51 -0700 Subject: [PATCH 100/145] changed how completion is actually sampled --- contrib/adaptive-compression/adapt.c | 65 ++++++++++++---------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index ce92d914..cf232ca7 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -24,8 +24,8 @@ #define MAX_PATH 256 #define DEFAULT_DISPLAY_LEVEL 1 #define DEFAULT_COMPRESSION_LEVEL 6 -#define DEFAULT_ADAPT_PARAM 1 -#define MAX_COMPRESSION_LEVEL_CHANGE 3 +#define DEFAULT_ADAPT_PARAM 0 +#define MAX_COMPRESSION_LEVEL_CHANGE 4 static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; @@ -87,9 +87,9 @@ typedef struct { unsigned jobWriteID; unsigned allJobsCompleted; unsigned adaptParam; - unsigned compressionCompletionMeasured; - unsigned writeCompletionMeasured; - unsigned createCompletionMeasured; + double compressionCompletionMeasured; + double writeCompletionMeasured; + double createCompletionMeasured; double compressionCompletion; double writeCompletion; double createCompletion; @@ -342,6 +342,9 @@ static void adaptCompressionLevel(adaptCCtx* ctx) writeWaiting = ctx->adaptParam < ctx->stats.compressedCounter; createWaiting = ctx->adaptParam < ctx->stats.writeCounter; pthread_mutex_unlock(&ctx->stats_mutex.pMutex); + DEBUG(2, "createWaiting %u\n", createWaiting); + DEBUG(2, "compressWaiting %u\n", compressWaiting); + DEBUG(2, "writeWaiting %u\n\n", writeWaiting); { unsigned const writeSlow = (compressWaiting && createWaiting); unsigned const compressSlow = (writeWaiting && createWaiting); @@ -351,14 +354,14 @@ static void adaptCompressionLevel(adaptCCtx* ctx) reset = 1; } else if ((writeSlow || createSlow) && ctx->compressionLevel < (unsigned)ZSTD_maxCLevel()) { - DEBUG(3, "increasing compression level %u\n", ctx->compressionLevel); + DEBUG(2, "increasing compression level %u\n", ctx->compressionLevel); double completion; pthread_mutex_lock(&ctx->completion_mutex.pMutex); - completion = writeSlow ? ctx->writeCompletion : ctx->createCompletion; - DEBUG(3, "write completion: %f, create completion: %f\n", ctx->writeCompletion, ctx->createCompletion); + completion = writeSlow ? ctx->writeCompletionMeasured : ctx->createCompletionMeasured; + DEBUG(2, "write completion: %f, create completion: %f\n", ctx->writeCompletionMeasured, ctx->createCompletionMeasured); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); { - unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE)); + unsigned const maxChange = MAX_COMPRESSION_LEVEL_CHANGE - (unsigned)(completion*MAX_COMPRESSION_LEVEL_CHANGE); unsigned const change = MIN(maxChange, ZSTD_maxCLevel() - ctx->compressionLevel); DEBUG(3, "writeSlow: %u, change: %u\n", writeSlow, change); DEBUG(3, "write completion: %f\n", completion); @@ -369,13 +372,13 @@ static void adaptCompressionLevel(adaptCCtx* ctx) else if (compressSlow && ctx->compressionLevel > 1) { double completion; pthread_mutex_lock(&ctx->completion_mutex.pMutex); - completion = ctx->compressionCompletion; + completion = ctx->compressionCompletionMeasured; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); { - unsigned const maxChange = (unsigned)((1-completion) * (MAX_COMPRESSION_LEVEL_CHANGE)); + unsigned const maxChange = MAX_COMPRESSION_LEVEL_CHANGE - (unsigned)(completion*MAX_COMPRESSION_LEVEL_CHANGE); unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); - DEBUG(3, "decreasing compression level %u\n", ctx->compressionLevel); - DEBUG(3, "completion: %f\n", completion); + DEBUG(2, "decreasing compression level %u\n", ctx->compressionLevel); + DEBUG(2, "completion: %f\n", completion); ctx->compressionLevel -= change; reset = 1; } @@ -386,15 +389,6 @@ static void adaptCompressionLevel(adaptCCtx* ctx) ctx->stats.writeCounter = 0; ctx->stats.compressedCounter = 0; pthread_mutex_unlock(&ctx->stats_mutex.pMutex); - - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->compressionCompletion = 0; - ctx->compressionCompletionMeasured = 0; - ctx->writeCompletion = 0; - ctx->writeCompletionMeasured = 0; - ctx->createCompletion = 0; - ctx->createCompletionMeasured = 0; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } } } @@ -424,8 +418,8 @@ static void* compressionThread(void* arg) pthread_mutex_unlock(&ctx->stats_mutex.pMutex); reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->createCompletionMeasured = 1; - DEBUG(2, "create completion: %f\n", ctx->createCompletion); + ctx->createCompletionMeasured = ctx->createCompletion; + DEBUG(3, "create completion: %f\n", ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); @@ -502,9 +496,8 @@ static void* compressionThread(void* arg) /* update completion */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); - if (!ctx->compressionCompletionMeasured) { - ctx->compressionCompletion = 1 - (double)remaining/job->src.size; - } + ctx->compressionCompletion = 1 - (double)remaining/job->src.size; + DEBUG(2, "update on job %u: compression completion %f\n", currJob, ctx->compressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } } while (remaining != 0); @@ -562,8 +555,8 @@ static void* outputThread(void* arg) pthread_mutex_unlock(&ctx->stats_mutex.pMutex); reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->compressionCompletionMeasured = 1; - DEBUG(2, "compressionCompletion %f\n", ctx->compressionCompletion); + ctx->compressionCompletionMeasured = ctx->compressionCompletion; + DEBUG(2, "waited on job %u: compressionCompletion %f\n", currJob, ctx->compressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); @@ -580,7 +573,7 @@ static void* outputThread(void* arg) } { // size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, dstFile); - size_t const blockSize = 64 << 10; /* 64 KB */ + size_t const blockSize = compressedSize >> 7; size_t pos = 0; for ( ; ; ) { size_t const writeSize = MIN(remaining, blockSize); @@ -591,9 +584,7 @@ static void* outputThread(void* arg) /* update completion variable for writing */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); - if (!ctx->writeCompletionMeasured) { - ctx->writeCompletion = 1 - (double)remaining/compressedSize; - } + ctx->writeCompletion = 1 - (double)remaining/compressedSize; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); if (remaining == 0) break; @@ -643,8 +634,8 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) pthread_mutex_unlock(&ctx->stats_mutex.pMutex); reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->writeCompletionMeasured = 1; - DEBUG(2, "writeCompletion: %f\n", ctx->writeCompletion); + ctx->writeCompletionMeasured = ctx->writeCompletion; + DEBUG(3, "writeCompletion: %f\n", ctx->writeCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); @@ -736,9 +727,7 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA pos += ret; remaining -= ret; pthread_mutex_lock(&ctx->completion_mutex.pMutex); - if (!ctx->createCompletionMeasured) { - ctx->createCompletion = 1 - (double)remaining/((size_t)FILE_CHUNK_SIZE); - } + ctx->createCompletion = 1 - (double)remaining/((size_t)FILE_CHUNK_SIZE); DEBUG(3, "create completion: %f\n", ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } From a19916425d5d932e0e34e22dd4496114b4af6e73 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 20 Jul 2017 16:19:16 -0700 Subject: [PATCH 101/145] reworked adaptCompressionLevel to only account for completion information --- contrib/adaptive-compression/adapt.c | 267 ++++++++++++--------------- 1 file changed, 119 insertions(+), 148 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index cf232ca7..84e689a2 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -29,7 +29,6 @@ static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; -static unsigned g_displayStats = 0; static UTIL_time_t g_startTime; static size_t g_streamedSize = 0; static unsigned g_useProgressBar = 0; @@ -47,15 +46,6 @@ typedef struct { buffer_t buffer; } inBuff_t; -typedef struct { - unsigned waitCompressed; - unsigned waitReady; - unsigned waitWrite; - unsigned readyCounter; - unsigned compressedCounter; - unsigned writeCounter; -} cStat_t; - typedef struct { buffer_t src; buffer_t dst; @@ -102,10 +92,9 @@ typedef struct { mutex_t jobWrite_mutex; cond_t jobWrite_cond; mutex_t completion_mutex; - mutex_t stats_mutex; + mutex_t wait_mutex; size_t lastDictSize; inBuff_t input; - cStat_t stats; jobDescription* jobs; ZSTD_CCtx* cctx; } adaptCCtx; @@ -163,7 +152,7 @@ static int freeCCtx(adaptCCtx* ctx) error |= destroyMutex(&ctx->jobWrite_mutex); error |= destroyCond(&ctx->jobWrite_cond); error |= destroyMutex(&ctx->completion_mutex); - error |= destroyMutex(&ctx->stats_mutex); + error |= destroyMutex(&ctx->wait_mutex); error |= ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)); free(ctx->input.buffer.start); if (ctx->jobs){ @@ -203,7 +192,7 @@ static int initCCtx(adaptCCtx* ctx, unsigned numJobs) pthreadError |= initMutex(&ctx->jobWrite_mutex); pthreadError |= initCond(&ctx->jobWrite_cond); pthreadError |= initMutex(&ctx->completion_mutex); - pthreadError |= initMutex(&ctx->stats_mutex); + pthreadError |= initMutex(&ctx->wait_mutex); if (pthreadError) return pthreadError; } ctx->numJobs = numJobs; @@ -211,6 +200,10 @@ static int initCCtx(adaptCCtx* ctx, unsigned numJobs) ctx->jobCompressedID = 0; ctx->jobWriteID = 0; ctx->lastDictSize = 0; + ctx->createCompletionMeasured = 1; + ctx->compressionCompletionMeasured = 1; + ctx->writeCompletionMeasured = 1; + ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); if (!ctx->jobs) { @@ -305,17 +298,6 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); } -/* this function normalizes counters when compression level is changing */ -static void reduceCounters(adaptCCtx* ctx) -{ - pthread_mutex_lock(&ctx->stats_mutex.pMutex); - unsigned const min = MIN(ctx->stats.compressedCounter, MIN(ctx->stats.writeCounter, ctx->stats.readyCounter)); - ctx->stats.writeCounter -= min; - ctx->stats.compressedCounter -= min; - ctx->stats.readyCounter -= min; - pthread_mutex_unlock(&ctx->stats_mutex.pMutex); -} - /* * Compression level is changed depending on which part of the compression process is lagging * Currently, three theads exist for job creation, compression, and file writing respectively. @@ -330,67 +312,42 @@ static void adaptCompressionLevel(adaptCCtx* ctx) ctx->compressionLevel = g_compressionLevel; } else { - unsigned reset = 0; - unsigned allSlow; - unsigned compressWaiting; - unsigned writeWaiting; - unsigned createWaiting; + DEBUG(2, "compression level %u\n", ctx->compressionLevel); + /* check if compression is too slow */ + unsigned createChange; + unsigned writeChange; + unsigned compressionChange; + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + createChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->createCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; + writeChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->writeCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; + compressionChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->compressionCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; + DEBUG(2, "createCompletionMeasured %f\n", ctx->createCompletionMeasured); + DEBUG(2, "compressionCompletionMeasured %f\n", ctx->compressionCompletionMeasured); + DEBUG(2, "writeCompletionMeasured %f\n", ctx->writeCompletionMeasured); + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - pthread_mutex_lock(&ctx->stats_mutex.pMutex); - allSlow = ctx->adaptParam < ctx->stats.compressedCounter && ctx->adaptParam < ctx->stats.writeCounter && ctx->adaptParam < ctx->stats.readyCounter; - compressWaiting = ctx->adaptParam < ctx->stats.readyCounter; - writeWaiting = ctx->adaptParam < ctx->stats.compressedCounter; - createWaiting = ctx->adaptParam < ctx->stats.writeCounter; - pthread_mutex_unlock(&ctx->stats_mutex.pMutex); - DEBUG(2, "createWaiting %u\n", createWaiting); - DEBUG(2, "compressWaiting %u\n", compressWaiting); - DEBUG(2, "writeWaiting %u\n\n", writeWaiting); { - unsigned const writeSlow = (compressWaiting && createWaiting); - unsigned const compressSlow = (writeWaiting && createWaiting); - unsigned const createSlow = (compressWaiting && writeWaiting); - DEBUG(3, "createWaiting: %u, compressWaiting: %u, writeWaiting: %u\n", createWaiting, compressWaiting, writeWaiting); - if (allSlow) { - reset = 1; + unsigned const compressionFastChange = MIN(MIN(createChange, writeChange), ZSTD_maxCLevel() - ctx->compressionLevel); + DEBUG(2, "compressionFastChange %u\n", compressionFastChange); + + if (compressionFastChange) { + DEBUG(2, "compression level too low\n"); + ctx->compressionLevel += compressionFastChange; } - else if ((writeSlow || createSlow) && ctx->compressionLevel < (unsigned)ZSTD_maxCLevel()) { - DEBUG(2, "increasing compression level %u\n", ctx->compressionLevel); - double completion; - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - completion = writeSlow ? ctx->writeCompletionMeasured : ctx->createCompletionMeasured; - DEBUG(2, "write completion: %f, create completion: %f\n", ctx->writeCompletionMeasured, ctx->createCompletionMeasured); - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - { - unsigned const maxChange = MAX_COMPRESSION_LEVEL_CHANGE - (unsigned)(completion*MAX_COMPRESSION_LEVEL_CHANGE); - unsigned const change = MIN(maxChange, ZSTD_maxCLevel() - ctx->compressionLevel); - DEBUG(3, "writeSlow: %u, change: %u\n", writeSlow, change); - DEBUG(3, "write completion: %f\n", completion); - ctx->compressionLevel += change; - reset = 1; - } - } - else if (compressSlow && ctx->compressionLevel > 1) { - double completion; - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - completion = ctx->compressionCompletionMeasured; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - { - unsigned const maxChange = MAX_COMPRESSION_LEVEL_CHANGE - (unsigned)(completion*MAX_COMPRESSION_LEVEL_CHANGE); - unsigned const change = MIN(maxChange, ctx->compressionLevel - 1); - DEBUG(2, "decreasing compression level %u\n", ctx->compressionLevel); - DEBUG(2, "completion: %f\n", completion); - ctx->compressionLevel -= change; - reset = 1; - } - } - if (reset) { - pthread_mutex_lock(&ctx->stats_mutex.pMutex); - ctx->stats.readyCounter = 0; - ctx->stats.writeCounter = 0; - ctx->stats.compressedCounter = 0; - pthread_mutex_unlock(&ctx->stats_mutex.pMutex); + else { + unsigned const compressionSlowChange = MIN(compressionChange, ctx->compressionLevel-1); + DEBUG(2, "compression level too high\n"); + ctx->compressionLevel -= compressionSlowChange; } } + + /* reset */ + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->createCompletionMeasured = 1; + ctx->compressionCompletionMeasured = 1; + ctx->writeCompletionMeasured = 1; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + DEBUG(2, "\n"); } } @@ -410,21 +367,31 @@ static void* compressionThread(void* arg) unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "compressionThread(): waiting on job ready\n"); + + /* new job, reset completion */ + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->compressionCompletion = 0; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); while(currJob + 1 > ctx->jobReadyID && !ctx->threadError) { - pthread_mutex_lock(&ctx->stats_mutex.pMutex); - ctx->stats.waitReady++; - ctx->stats.readyCounter++; - pthread_mutex_unlock(&ctx->stats_mutex.pMutex); - reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); + /* compression thread is waiting, take measurements of write completion and read completion */ ctx->createCompletionMeasured = ctx->createCompletion; + ctx->writeCompletionMeasured = ctx->writeCompletion; + DEBUG(2, "compression thread waiting : createCompletionMeasured %f : writeCompletionMeasured %f\n", ctx->createCompletionMeasured, ctx->writeCompletionMeasured); DEBUG(3, "create completion: %f\n", ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); + + /* reset create completion */ + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->createCompletion = 0; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + DEBUG(3, "compressionThread(): continuing after job ready\n"); DEBUG(3, "DICTIONARY ENDED\n"); DEBUG(3, "%.*s", (int)job->src.size, (char*)job->src.start); @@ -497,7 +464,7 @@ static void* compressionThread(void* arg) /* update completion */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->compressionCompletion = 1 - (double)remaining/job->src.size; - DEBUG(2, "update on job %u: compression completion %f\n", currJob, ctx->compressionCompletion); + DEBUG(3, "update on job %u: compression completion %f\n", currJob, ctx->compressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } } while (remaining != 0); @@ -547,21 +514,29 @@ static void* outputThread(void* arg) unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "outputThread(): waiting on job compressed\n"); + + /* new job, reset completion */ + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->writeCompletion = 0; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { - pthread_mutex_lock(&ctx->stats_mutex.pMutex); - ctx->stats.waitCompressed++; - ctx->stats.compressedCounter++; - pthread_mutex_unlock(&ctx->stats_mutex.pMutex); - reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); + /* write thread is waiting, take measurement of compression completion */ ctx->compressionCompletionMeasured = ctx->compressionCompletion; - DEBUG(2, "waited on job %u: compressionCompletion %f\n", currJob, ctx->compressionCompletion); + DEBUG(2, "write thread waiting : compressionCompletionMeasured %f\n", ctx->compressionCompletionMeasured); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); + + /* reset compression completion */ + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->compressionCompletion = 0; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + DEBUG(3, "outputThread(): continuing after job compressed\n"); { size_t const compressedSize = job->compressedSize; @@ -615,6 +590,7 @@ static void* outputThread(void* arg) pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); break; } + } return arg; } @@ -628,19 +604,21 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); DEBUG(3, "Creating new compression job -- nextJob: %u, jobCompressedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompressedID, ctx->jobWriteID, ctx->numJobs); while (nextJob - ctx->jobWriteID >= ctx->numJobs && !ctx->threadError) { - pthread_mutex_lock(&ctx->stats_mutex.pMutex); - ctx->stats.waitWrite++; - ctx->stats.writeCounter++; - pthread_mutex_unlock(&ctx->stats_mutex.pMutex); - reduceCounters(ctx); pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->writeCompletionMeasured = ctx->writeCompletion; + /* creation thread is waiting, take measurement of compression completion */ + ctx->compressionCompletionMeasured = ctx->compressionCompletion; + DEBUG(2, "creation thread waiting : compression completion measured : %f\n", ctx->compressionCompletionMeasured); DEBUG(3, "writeCompletion: %f\n", ctx->writeCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); + + /* reset write completion */ + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->writeCompletion = 0; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "createCompressionJob(): continuing after job write\n"); DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); @@ -677,14 +655,6 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) return 0; } -static void printStats(cStat_t stats) -{ - DISPLAY("========STATISTICS========\n"); - DISPLAY("# times waited on job ready: %u\n", stats.waitReady); - DISPLAY("# times waited on job compressed: %u\n", stats.waitCompressed); - DISPLAY("# times waited on job Write: %u\n\n", stats.waitWrite); -} - static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadArg* otArg) { if (!ctx || !srcFile || !otArg) { @@ -710,48 +680,56 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA return 1; } } + { + unsigned currJob = 0; + /* creating jobs */ + for ( ; ; ) { + size_t pos = 0; + size_t const readBlockSize = 1 << 15; + size_t remaining = FILE_CHUNK_SIZE; - /* creating jobs */ - for ( ; ; ) { - size_t pos = 0; - size_t const readBlockSize = 1 << 15; - size_t remaining = FILE_CHUNK_SIZE; - while (remaining != 0 && !feof(srcFile)) { - size_t const ret = fread(ctx->input.buffer.start + ctx->input.filled + pos, 1, readBlockSize, srcFile); - if (ret != readBlockSize && !feof(srcFile)) { - /* error could not read correct number of bytes */ + /* new job reset completion */ + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->createCompletion = 0; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + + while (remaining != 0 && !feof(srcFile)) { + size_t const ret = fread(ctx->input.buffer.start + ctx->input.filled + pos, 1, readBlockSize, srcFile); + if (ret != readBlockSize && !feof(srcFile)) { + /* error could not read correct number of bytes */ + DISPLAY("Error: problem occurred during read from src file\n"); + signalErrorToThreads(ctx); + return 1; + } + pos += ret; + remaining -= ret; + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->createCompletion = 1 - (double)remaining/((size_t)FILE_CHUNK_SIZE); + DEBUG(3, "create completion: %f\n", ctx->createCompletion); + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + } + if (remaining != 0 && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); signalErrorToThreads(ctx); return 1; } - pos += ret; - remaining -= ret; - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->createCompletion = 1 - (double)remaining/((size_t)FILE_CHUNK_SIZE); - DEBUG(3, "create completion: %f\n", ctx->createCompletion); - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - } - if (remaining != 0 && !feof(srcFile)) { - DISPLAY("Error: problem occurred during read from src file\n"); - signalErrorToThreads(ctx); - return 1; - } - g_streamedSize += pos; - /* reading was fine, now create the compression job */ - { - int const last = feof(srcFile); - int const error = createCompressionJob(ctx, pos, last); - if (error != 0) { - signalErrorToThreads(ctx); - return error; + g_streamedSize += pos; + /* reading was fine, now create the compression job */ + { + int const last = feof(srcFile); + int const error = createCompressionJob(ctx, pos, last); + if (error != 0) { + signalErrorToThreads(ctx); + return error; + } + } + currJob++; + if (feof(srcFile)) { + DEBUG(3, "THE STREAM OF DATA ENDED %u\n", ctx->nextJobID); + break; } } - if (feof(srcFile)) { - DEBUG(3, "THE STREAM OF DATA ENDED %u\n", ctx->nextJobID); - break; - } } - /* success -- created all jobs */ return 0; } @@ -803,9 +781,6 @@ static int freeFileCompressionResources(fcResources* fcr) { int ret = 0; waitUntilAllJobsCompleted(fcr->ctx); - pthread_mutex_lock(&fcr->ctx->stats_mutex.pMutex); - if (g_displayStats) printStats(fcr->ctx->stats); - pthread_mutex_unlock(&fcr->ctx->stats_mutex.pMutex); ret |= (fcr->srcFile != NULL) ? fclose(fcr->srcFile) : 0; ret |= (fcr->ctx != NULL) ? freeCCtx(fcr->ctx) : 0; if (fcr->otArg) { @@ -873,7 +848,6 @@ static void help() PRINT(" -oFILE : specify the output file name\n"); PRINT(" -v : display debug information\n"); PRINT(" -i# : provide initial compression level\n"); - PRINT(" -s : display information stats\n"); PRINT(" -h : display help/information\n"); PRINT(" -f : force the compression level to stay constant\n"); } @@ -913,9 +887,6 @@ int main(int argCount, const char* argv[]) g_compressionLevel = readU32FromChar(&argument); DEBUG(3, "g_compressionLevel: %u\n", g_compressionLevel); break; - case 's': - g_displayStats = 1; - break; case 'h': help(); goto _main_exit; From 82e488770c979d6bef9655bc0cd409c0df6b126d Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 20 Jul 2017 16:38:02 -0700 Subject: [PATCH 102/145] fixed bug where writeSize could be zero --- contrib/adaptive-compression/adapt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 84e689a2..3c6b7e90 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -548,7 +548,7 @@ static void* outputThread(void* arg) } { // size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, dstFile); - size_t const blockSize = compressedSize >> 7; + size_t const blockSize = MAX(compressedSize >> 7, 64 << 10); size_t pos = 0; for ( ; ; ) { size_t const writeSize = MIN(remaining, blockSize); From 9259c7afa412af57e7f578bc8e1f2c5d3906949c Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Thu, 20 Jul 2017 18:45:33 -0700 Subject: [PATCH 103/145] semi working version that stabilizes --- contrib/adaptive-compression/adapt.c | 104 ++++++++++++--------------- 1 file changed, 45 insertions(+), 59 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 3c6b7e90..f25d22a4 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -308,47 +308,47 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) */ static void adaptCompressionLevel(adaptCCtx* ctx) { + /* check if compression is too slow */ + unsigned createChange; + unsigned writeChange; + unsigned compressionChange; + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + createChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->createCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; + writeChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->writeCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; + compressionChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->compressionCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; + DEBUG(2, "compression level %u\n", ctx->compressionLevel); + DEBUG(2, "createCompletionMeasured %f\n", ctx->createCompletionMeasured); + DEBUG(2, "compressionCompletionMeasured %f\n", ctx->compressionCompletionMeasured); + DEBUG(2, "writeCompletionMeasured %f\n", ctx->writeCompletionMeasured); + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + + { + unsigned const compressionFastChange = MIN(MIN(createChange, writeChange), ZSTD_maxCLevel() - ctx->compressionLevel); + + DEBUG(2, "compressionFastChange %u\n", compressionFastChange); + + if (compressionFastChange) { + DEBUG(2, "compression level too low\n"); + ctx->compressionLevel += compressionFastChange; + } + else { + unsigned const compressionSlowChange = MIN(compressionChange, ctx->compressionLevel-1); + DEBUG(2, "compression level too high\n"); + ctx->compressionLevel -= compressionSlowChange; + } + } + + /* reset */ + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->createCompletionMeasured = 1; + ctx->compressionCompletionMeasured = 1; + ctx->writeCompletionMeasured = 1; + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + DEBUG(2, "\n"); + if (g_forceCompressionLevel) { ctx->compressionLevel = g_compressionLevel; } - else { - DEBUG(2, "compression level %u\n", ctx->compressionLevel); - /* check if compression is too slow */ - unsigned createChange; - unsigned writeChange; - unsigned compressionChange; - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - createChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->createCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; - writeChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->writeCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; - compressionChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->compressionCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; - DEBUG(2, "createCompletionMeasured %f\n", ctx->createCompletionMeasured); - DEBUG(2, "compressionCompletionMeasured %f\n", ctx->compressionCompletionMeasured); - DEBUG(2, "writeCompletionMeasured %f\n", ctx->writeCompletionMeasured); - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - - { - unsigned const compressionFastChange = MIN(MIN(createChange, writeChange), ZSTD_maxCLevel() - ctx->compressionLevel); - DEBUG(2, "compressionFastChange %u\n", compressionFastChange); - - if (compressionFastChange) { - DEBUG(2, "compression level too low\n"); - ctx->compressionLevel += compressionFastChange; - } - else { - unsigned const compressionSlowChange = MIN(compressionChange, ctx->compressionLevel-1); - DEBUG(2, "compression level too high\n"); - ctx->compressionLevel -= compressionSlowChange; - } - } - - /* reset */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->createCompletionMeasured = 1; - ctx->compressionCompletionMeasured = 1; - ctx->writeCompletionMeasured = 1; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - DEBUG(2, "\n"); - } } static size_t getUseableDictSize(unsigned compressionLevel) @@ -368,10 +368,6 @@ static void* compressionThread(void* arg) jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "compressionThread(): waiting on job ready\n"); - /* new job, reset completion */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->compressionCompletion = 0; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); while(currJob + 1 > ctx->jobReadyID && !ctx->threadError) { @@ -387,9 +383,9 @@ static void* compressionThread(void* arg) } pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); - /* reset create completion */ + /* reset compression completion */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->createCompletion = 0; + ctx->compressionCompletion = 0; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "compressionThread(): continuing after job ready\n"); @@ -397,7 +393,7 @@ static void* compressionThread(void* arg) DEBUG(3, "%.*s", (int)job->src.size, (char*)job->src.start); /* adapt compression level */ - adaptCompressionLevel(ctx); + if (currJob) adaptCompressionLevel(ctx); /* compress the data */ { @@ -515,11 +511,6 @@ static void* outputThread(void* arg) jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "outputThread(): waiting on job compressed\n"); - /* new job, reset completion */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->writeCompletion = 0; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { pthread_mutex_lock(&ctx->completion_mutex.pMutex); @@ -532,9 +523,9 @@ static void* outputThread(void* arg) } pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); - /* reset compression completion */ + /* reset write completion */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->compressionCompletion = 0; + ctx->writeCompletion = 0; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "outputThread(): continuing after job compressed\n"); @@ -615,9 +606,9 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) } pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); - /* reset write completion */ + /* reset create completion */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->writeCompletion = 0; + ctx->createCompletion = 0; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "createCompressionJob(): continuing after job write\n"); @@ -688,11 +679,6 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA size_t const readBlockSize = 1 << 15; size_t remaining = FILE_CHUNK_SIZE; - /* new job reset completion */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->createCompletion = 0; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - while (remaining != 0 && !feof(srcFile)) { size_t const ret = fread(ctx->input.buffer.start + ctx->input.filled + pos, 1, readBlockSize, srcFile); if (ret != readBlockSize && !feof(srcFile)) { From e929d3b787b4a75b14546aa5f3e2c2e601eb2299 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 21 Jul 2017 09:26:35 -0700 Subject: [PATCH 104/145] added priority decision making for adapt compression level --- contrib/adaptive-compression/adapt.c | 59 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index f25d22a4..758bf558 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -308,43 +308,42 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) */ static void adaptCompressionLevel(adaptCCtx* ctx) { - /* check if compression is too slow */ - unsigned createChange; - unsigned writeChange; - unsigned compressionChange; + double createCompletion, compressionCompletion, writeCompletion; + double const threshold = 0.00001; pthread_mutex_lock(&ctx->completion_mutex.pMutex); - createChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->createCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; - writeChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->writeCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; - compressionChange = MAX_COMPRESSION_LEVEL_CHANGE - ctx->compressionCompletionMeasured * MAX_COMPRESSION_LEVEL_CHANGE; - DEBUG(2, "compression level %u\n", ctx->compressionLevel); - DEBUG(2, "createCompletionMeasured %f\n", ctx->createCompletionMeasured); - DEBUG(2, "compressionCompletionMeasured %f\n", ctx->compressionCompletionMeasured); - DEBUG(2, "writeCompletionMeasured %f\n", ctx->writeCompletionMeasured); + createCompletion = ctx->createCompletionMeasured; + compressionCompletion = ctx->compressionCompletionMeasured; + writeCompletion = ctx->writeCompletionMeasured; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - { - unsigned const compressionFastChange = MIN(MIN(createChange, writeChange), ZSTD_maxCLevel() - ctx->compressionLevel); - - DEBUG(2, "compressionFastChange %u\n", compressionFastChange); - - if (compressionFastChange) { - DEBUG(2, "compression level too low\n"); - ctx->compressionLevel += compressionFastChange; - } - else { - unsigned const compressionSlowChange = MIN(compressionChange, ctx->compressionLevel-1); - DEBUG(2, "compression level too high\n"); - ctx->compressionLevel -= compressionSlowChange; - } + DEBUG(2, "create completion: %f\n", createCompletion); + DEBUG(2, "compression completion: %f\n", compressionCompletion); + DEBUG(2, "write completion: %f\n", writeCompletion); + /* adapt compression based on bottleneck */ + if (1 - createCompletion > threshold) { + /* job creation was not finished, compression thread waited */ + unsigned const change = MAX_COMPRESSION_LEVEL_CHANGE - createCompletion * MAX_COMPRESSION_LEVEL_CHANGE; + DEBUG(2, "increasing compression level %u by %u\n", ctx->compressionLevel, change); + ctx->compressionLevel += change; + } + else if (1 - writeCompletion > threshold) { + /* write thread was not finished, compression thread waited */ + unsigned const change = MAX_COMPRESSION_LEVEL_CHANGE - writeCompletion * MAX_COMPRESSION_LEVEL_CHANGE; + DEBUG(2, "increasing compression level %u by %u\n", ctx->compressionLevel, change); + ctx->compressionLevel += change; + } + else if (1 - compressionCompletion > threshold) { + /* compression thread was not finished, one of the other two threads waited */ + unsigned const change = MAX_COMPRESSION_LEVEL_CHANGE - compressionCompletion * MAX_COMPRESSION_LEVEL_CHANGE; + DEBUG(2, "decreasing compression level %u by %u\n", ctx->compressionLevel, change); + ctx->compressionLevel -= change; } - /* reset */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->createCompletionMeasured = 1; ctx->compressionCompletionMeasured = 1; ctx->writeCompletionMeasured = 1; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - DEBUG(2, "\n"); if (g_forceCompressionLevel) { ctx->compressionLevel = g_compressionLevel; @@ -375,7 +374,7 @@ static void* compressionThread(void* arg) /* compression thread is waiting, take measurements of write completion and read completion */ ctx->createCompletionMeasured = ctx->createCompletion; ctx->writeCompletionMeasured = ctx->writeCompletion; - DEBUG(2, "compression thread waiting : createCompletionMeasured %f : writeCompletionMeasured %f\n", ctx->createCompletionMeasured, ctx->writeCompletionMeasured); + DEBUG(3, "compression thread waiting : createCompletionMeasured %f : writeCompletionMeasured %f\n", ctx->createCompletionMeasured, ctx->writeCompletionMeasured); DEBUG(3, "create completion: %f\n", ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); @@ -516,7 +515,7 @@ static void* outputThread(void* arg) pthread_mutex_lock(&ctx->completion_mutex.pMutex); /* write thread is waiting, take measurement of compression completion */ ctx->compressionCompletionMeasured = ctx->compressionCompletion; - DEBUG(2, "write thread waiting : compressionCompletionMeasured %f\n", ctx->compressionCompletionMeasured); + DEBUG(3, "write thread waiting : compressionCompletionMeasured %f\n", ctx->compressionCompletionMeasured); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); @@ -598,7 +597,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) pthread_mutex_lock(&ctx->completion_mutex.pMutex); /* creation thread is waiting, take measurement of compression completion */ ctx->compressionCompletionMeasured = ctx->compressionCompletion; - DEBUG(2, "creation thread waiting : compression completion measured : %f\n", ctx->compressionCompletionMeasured); + DEBUG(3, "creation thread waiting : compression completion measured : %f\n", ctx->compressionCompletionMeasured); DEBUG(3, "writeCompletion: %f\n", ctx->writeCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); From 721c6a8b973c8a2e23a4f4eec56bf77e253098ea Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 21 Jul 2017 09:30:24 -0700 Subject: [PATCH 105/145] added bounding to compression level change --- contrib/adaptive-compression/adapt.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 758bf558..c542c9a3 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -323,20 +323,23 @@ static void adaptCompressionLevel(adaptCCtx* ctx) if (1 - createCompletion > threshold) { /* job creation was not finished, compression thread waited */ unsigned const change = MAX_COMPRESSION_LEVEL_CHANGE - createCompletion * MAX_COMPRESSION_LEVEL_CHANGE; + unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); DEBUG(2, "increasing compression level %u by %u\n", ctx->compressionLevel, change); - ctx->compressionLevel += change; + ctx->compressionLevel += boundChange; } else if (1 - writeCompletion > threshold) { /* write thread was not finished, compression thread waited */ unsigned const change = MAX_COMPRESSION_LEVEL_CHANGE - writeCompletion * MAX_COMPRESSION_LEVEL_CHANGE; + unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); DEBUG(2, "increasing compression level %u by %u\n", ctx->compressionLevel, change); - ctx->compressionLevel += change; + ctx->compressionLevel += boundChange; } else if (1 - compressionCompletion > threshold) { /* compression thread was not finished, one of the other two threads waited */ unsigned const change = MAX_COMPRESSION_LEVEL_CHANGE - compressionCompletion * MAX_COMPRESSION_LEVEL_CHANGE; + unsigned const boundChange = MIN(change, ctx->compressionLevel - 1); DEBUG(2, "decreasing compression level %u by %u\n", ctx->compressionLevel, change); - ctx->compressionLevel -= change; + ctx->compressionLevel -= boundChange; } /* reset */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); From db109f8fef99c51154a7ff56371ff6470cbb43eb Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 21 Jul 2017 13:38:24 -0700 Subject: [PATCH 106/145] measure multiple completion levels during each wait --- contrib/adaptive-compression/adapt.c | 123 ++++++++++++++++----------- 1 file changed, 75 insertions(+), 48 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index c542c9a3..b466dbdd 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -25,7 +25,7 @@ #define DEFAULT_DISPLAY_LEVEL 1 #define DEFAULT_COMPRESSION_LEVEL 6 #define DEFAULT_ADAPT_PARAM 0 -#define MAX_COMPRESSION_LEVEL_CHANGE 4 +#define MAX_COMPRESSION_LEVEL_CHANGE 3 static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; @@ -77,9 +77,12 @@ typedef struct { unsigned jobWriteID; unsigned allJobsCompleted; unsigned adaptParam; - double compressionCompletionMeasured; - double writeCompletionMeasured; - double createCompletionMeasured; + double createWaitWriteCompletion; + double createWaitCompressionCompletion; + double compressWaitCreateCompletion; + double compressWaitWriteCompletion; + double writeWaitCreateCompletion; + double writeWaitCompressionCompletion; double compressionCompletion; double writeCompletion; double createCompletion; @@ -200,9 +203,14 @@ static int initCCtx(adaptCCtx* ctx, unsigned numJobs) ctx->jobCompressedID = 0; ctx->jobWriteID = 0; ctx->lastDictSize = 0; - ctx->createCompletionMeasured = 1; - ctx->compressionCompletionMeasured = 1; - ctx->writeCompletionMeasured = 1; + + + ctx->createWaitWriteCompletion = 1; + ctx->createWaitCompressionCompletion = 1; + ctx->compressWaitCreateCompletion = 1; + ctx->compressWaitWriteCompletion = 1; + ctx->writeWaitCreateCompletion = 1; + ctx->writeWaitCompressionCompletion = 1; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); @@ -308,45 +316,61 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) */ static void adaptCompressionLevel(adaptCCtx* ctx) { - double createCompletion, compressionCompletion, writeCompletion; + double createWaitWriteCompletion; + double createWaitCompressionCompletion; + double compressWaitCreateCompletion; + double compressWaitWriteCompletion; + double writeWaitCreateCompletion; + double writeWaitCompressionCompletion; double const threshold = 0.00001; + + + /* read and reset completion measurements */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); - createCompletion = ctx->createCompletionMeasured; - compressionCompletion = ctx->compressionCompletionMeasured; - writeCompletion = ctx->writeCompletionMeasured; + DEBUG(2, "rc %f\n", ctx->createWaitCompressionCompletion); + DEBUG(2, "rw %f\n", ctx->createWaitWriteCompletion); + DEBUG(2, "cr %f\n", ctx->compressWaitCreateCompletion); + DEBUG(2, "cw %f\n", ctx->compressWaitWriteCompletion); + DEBUG(2, "wr %f\n", ctx->writeWaitCreateCompletion); + DEBUG(2, "wc %f\n\n", ctx->writeWaitCompressionCompletion); + + createWaitCompressionCompletion = ctx->createWaitCompressionCompletion; + createWaitWriteCompletion = ctx->createWaitWriteCompletion; + compressWaitCreateCompletion = ctx->compressWaitCreateCompletion; + compressWaitWriteCompletion = ctx->compressWaitWriteCompletion; + writeWaitCreateCompletion = ctx->writeWaitCreateCompletion; + writeWaitCompressionCompletion = ctx->writeWaitCompressionCompletion; + + ctx->createWaitWriteCompletion = 1; + ctx->createWaitCompressionCompletion = 1; + ctx->compressWaitCreateCompletion = 1; + ctx->compressWaitWriteCompletion = 1; + ctx->writeWaitCreateCompletion = 1; + ctx->writeWaitCompressionCompletion = 1; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - DEBUG(2, "create completion: %f\n", createCompletion); - DEBUG(2, "compression completion: %f\n", compressionCompletion); - DEBUG(2, "write completion: %f\n", writeCompletion); - /* adapt compression based on bottleneck */ - if (1 - createCompletion > threshold) { - /* job creation was not finished, compression thread waited */ - unsigned const change = MAX_COMPRESSION_LEVEL_CHANGE - createCompletion * MAX_COMPRESSION_LEVEL_CHANGE; - unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); - DEBUG(2, "increasing compression level %u by %u\n", ctx->compressionLevel, change); - ctx->compressionLevel += boundChange; - } - else if (1 - writeCompletion > threshold) { - /* write thread was not finished, compression thread waited */ - unsigned const change = MAX_COMPRESSION_LEVEL_CHANGE - writeCompletion * MAX_COMPRESSION_LEVEL_CHANGE; - unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); - DEBUG(2, "increasing compression level %u by %u\n", ctx->compressionLevel, change); - ctx->compressionLevel += boundChange; - } - else if (1 - compressionCompletion > threshold) { - /* compression thread was not finished, one of the other two threads waited */ - unsigned const change = MAX_COMPRESSION_LEVEL_CHANGE - compressionCompletion * MAX_COMPRESSION_LEVEL_CHANGE; + /* adaptation logic */ + if (1-createWaitCompressionCompletion > threshold && 1-writeWaitCompressionCompletion > threshold) { + /* both create and write threads waiting on compression */ + /* use writeWaitCompressionCompletion */ + unsigned const change = (unsigned)((1-writeWaitCompressionCompletion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ctx->compressionLevel - 1); - DEBUG(2, "decreasing compression level %u by %u\n", ctx->compressionLevel, change); ctx->compressionLevel -= boundChange; } - /* reset */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->createCompletionMeasured = 1; - ctx->compressionCompletionMeasured = 1; - ctx->writeCompletionMeasured = 1; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + else if (1-createWaitWriteCompletion > threshold && 1-compressWaitWriteCompletion > threshold) { + /* both create and compression thread waiting on write */ + /* use createWaitWriteCompletion */ + unsigned const change = (unsigned)((1-createWaitWriteCompletion) * MAX_COMPRESSION_LEVEL_CHANGE); + unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); + ctx->compressionLevel += boundChange; + } + else if (1-writeWaitCreateCompletion > threshold && 1-compressWaitCreateCompletion > threshold) { + /* both compression and write waiting on create */ + /* use compressWaitCreateCompletion */ + unsigned const change = (unsigned)((1-compressWaitCreateCompletion) * MAX_COMPRESSION_LEVEL_CHANGE); + unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); + ctx->compressionLevel += boundChange; + } if (g_forceCompressionLevel) { ctx->compressionLevel = g_compressionLevel; @@ -375,9 +399,9 @@ static void* compressionThread(void* arg) while(currJob + 1 > ctx->jobReadyID && !ctx->threadError) { pthread_mutex_lock(&ctx->completion_mutex.pMutex); /* compression thread is waiting, take measurements of write completion and read completion */ - ctx->createCompletionMeasured = ctx->createCompletion; - ctx->writeCompletionMeasured = ctx->writeCompletion; - DEBUG(3, "compression thread waiting : createCompletionMeasured %f : writeCompletionMeasured %f\n", ctx->createCompletionMeasured, ctx->writeCompletionMeasured); + ctx->compressWaitCreateCompletion = ctx->createCompletion; + ctx->compressWaitWriteCompletion = ctx->writeCompletion; + DEBUG(3, "compression thread waiting : compressWaitCreateCompletion %f : compressWaitWriteCompletion %f\n", ctx->compressWaitCreateCompletion, ctx->compressWaitWriteCompletion); DEBUG(3, "create completion: %f\n", ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); @@ -462,7 +486,7 @@ static void* compressionThread(void* arg) /* update completion */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->compressionCompletion = 1 - (double)remaining/job->src.size; - DEBUG(3, "update on job %u: compression completion %f\n", currJob, ctx->compressionCompletion); + DEBUG(3, "compression completion %f\n", ctx->compressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } } while (remaining != 0); @@ -517,8 +541,9 @@ static void* outputThread(void* arg) while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { pthread_mutex_lock(&ctx->completion_mutex.pMutex); /* write thread is waiting, take measurement of compression completion */ - ctx->compressionCompletionMeasured = ctx->compressionCompletion; - DEBUG(3, "write thread waiting : compressionCompletionMeasured %f\n", ctx->compressionCompletionMeasured); + ctx->writeWaitCompressionCompletion = ctx->compressionCompletion; + ctx->writeWaitCreateCompletion = ctx->createCompletion; + DEBUG(3, "write thread waiting : writeWaitCreateCompletion %f : writeWaitCompressionCompletion %f\n", ctx->writeWaitCreateCompletion, ctx->writeWaitCompressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); @@ -541,7 +566,7 @@ static void* outputThread(void* arg) } { // size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, dstFile); - size_t const blockSize = MAX(compressedSize >> 7, 64 << 10); + size_t const blockSize = MAX(compressedSize >> 7, 1 << 10); size_t pos = 0; for ( ; ; ) { size_t const writeSize = MIN(remaining, blockSize); @@ -553,6 +578,7 @@ static void* outputThread(void* arg) /* update completion variable for writing */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->writeCompletion = 1 - (double)remaining/compressedSize; + DEBUG(3, "write completion %f\n", ctx->writeCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); if (remaining == 0) break; @@ -599,8 +625,9 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) while (nextJob - ctx->jobWriteID >= ctx->numJobs && !ctx->threadError) { pthread_mutex_lock(&ctx->completion_mutex.pMutex); /* creation thread is waiting, take measurement of compression completion */ - ctx->compressionCompletionMeasured = ctx->compressionCompletion; - DEBUG(3, "creation thread waiting : compression completion measured : %f\n", ctx->compressionCompletionMeasured); + ctx->createWaitCompressionCompletion = ctx->compressionCompletion; + ctx->createWaitWriteCompletion = ctx->writeCompletion; + DEBUG(3, "creation thread waiting : createWaitCompressionCompletion %f : createWaitWriteCompletion %f\n", ctx->createWaitCompressionCompletion, ctx->createWaitWriteCompletion); DEBUG(3, "writeCompletion: %f\n", ctx->writeCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); From 05fe8dd47c77f2a826a4deb58eb8a98a40f8ffdf Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 21 Jul 2017 14:06:24 -0700 Subject: [PATCH 107/145] updating debug statements --- contrib/adaptive-compression/adapt.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index b466dbdd..5a3b723d 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -324,7 +324,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) double writeWaitCompressionCompletion; double const threshold = 0.00001; - + DEBUG(2, "adapting compression level %u\n", ctx->compressionLevel); /* read and reset completion measurements */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); DEBUG(2, "rc %f\n", ctx->createWaitCompressionCompletion); @@ -341,6 +341,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) writeWaitCreateCompletion = ctx->writeWaitCreateCompletion; writeWaitCompressionCompletion = ctx->writeWaitCompressionCompletion; + DEBUG(2, "resetting adaptive variables\n"); ctx->createWaitWriteCompletion = 1; ctx->createWaitCompressionCompletion = 1; ctx->compressWaitCreateCompletion = 1; @@ -404,7 +405,7 @@ static void* compressionThread(void* arg) DEBUG(3, "compression thread waiting : compressWaitCreateCompletion %f : compressWaitWriteCompletion %f\n", ctx->compressWaitCreateCompletion, ctx->compressWaitWriteCompletion); DEBUG(3, "create completion: %f\n", ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - DEBUG(3, "waiting on job ready, nextJob: %u\n", currJob); + DEBUG(2, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); @@ -545,7 +546,7 @@ static void* outputThread(void* arg) ctx->writeWaitCreateCompletion = ctx->createCompletion; DEBUG(3, "write thread waiting : writeWaitCreateCompletion %f : writeWaitCompressionCompletion %f\n", ctx->writeWaitCreateCompletion, ctx->writeWaitCompressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - DEBUG(3, "waiting on job compressed, nextJob: %u\n", currJob); + DEBUG(2, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); @@ -630,7 +631,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) DEBUG(3, "creation thread waiting : createWaitCompressionCompletion %f : createWaitWriteCompletion %f\n", ctx->createWaitCompressionCompletion, ctx->createWaitWriteCompletion); DEBUG(3, "writeCompletion: %f\n", ctx->writeCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - DEBUG(3, "waiting on job Write, nextJob: %u\n", nextJob); + DEBUG(2, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); From 6455ec482cbb4a76619fed60ca9ef28a8c6a2abc Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 21 Jul 2017 16:05:01 -0700 Subject: [PATCH 108/145] taking the maximum of the completion level reads in order to determine which one was waiting more --- contrib/adaptive-compression/adapt.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 5a3b723d..e07333fc 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -354,23 +354,29 @@ static void adaptCompressionLevel(adaptCCtx* ctx) if (1-createWaitCompressionCompletion > threshold && 1-writeWaitCompressionCompletion > threshold) { /* both create and write threads waiting on compression */ /* use writeWaitCompressionCompletion */ - unsigned const change = (unsigned)((1-writeWaitCompressionCompletion) * MAX_COMPRESSION_LEVEL_CHANGE); + double const completion = MAX(createWaitCompressionCompletion, writeWaitCompressionCompletion); + unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ctx->compressionLevel - 1); ctx->compressionLevel -= boundChange; + DEBUG(2, "create and write threads waiting, tried to decrease compression level by %u\n", boundChange); } else if (1-createWaitWriteCompletion > threshold && 1-compressWaitWriteCompletion > threshold) { /* both create and compression thread waiting on write */ /* use createWaitWriteCompletion */ - unsigned const change = (unsigned)((1-createWaitWriteCompletion) * MAX_COMPRESSION_LEVEL_CHANGE); + double const completion = MAX(createWaitWriteCompletion, compressWaitWriteCompletion); + unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); ctx->compressionLevel += boundChange; + DEBUG(2, "create and compression threads waiting, tried to increase compression level by %u\n", boundChange); } else if (1-writeWaitCreateCompletion > threshold && 1-compressWaitCreateCompletion > threshold) { /* both compression and write waiting on create */ /* use compressWaitCreateCompletion */ - unsigned const change = (unsigned)((1-compressWaitCreateCompletion) * MAX_COMPRESSION_LEVEL_CHANGE); + double const completion = MAX(writeWaitCreateCompletion, compressWaitCreateCompletion); + unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); ctx->compressionLevel += boundChange; + DEBUG(2, "compression and write threads waiting, tried to increase compression level by %u\n", boundChange); } if (g_forceCompressionLevel) { @@ -404,8 +410,8 @@ static void* compressionThread(void* arg) ctx->compressWaitWriteCompletion = ctx->writeCompletion; DEBUG(3, "compression thread waiting : compressWaitCreateCompletion %f : compressWaitWriteCompletion %f\n", ctx->compressWaitCreateCompletion, ctx->compressWaitWriteCompletion); DEBUG(3, "create completion: %f\n", ctx->createCompletion); + DEBUG(2, "compression thread waiting for nextJob: %u, compressWaitCreateCompletion %f, compressWaitWriteCompletion %f\n", currJob, ctx->compressWaitCreateCompletion, ctx->compressWaitWriteCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - DEBUG(2, "waiting on job ready, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); @@ -422,6 +428,7 @@ static void* compressionThread(void* arg) /* adapt compression level */ if (currJob) adaptCompressionLevel(ctx); + DEBUG(2, "job %u compressed with level %u\n", currJob, ctx->compressionLevel); /* compress the data */ { size_t const compressionBlockSize = 1 << 17; /* 128 KB */ @@ -487,7 +494,7 @@ static void* compressionThread(void* arg) /* update completion */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->compressionCompletion = 1 - (double)remaining/job->src.size; - DEBUG(3, "compression completion %f\n", ctx->compressionCompletion); + DEBUG(2, "compression completion %f\n", ctx->compressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } } while (remaining != 0); @@ -545,8 +552,8 @@ static void* outputThread(void* arg) ctx->writeWaitCompressionCompletion = ctx->compressionCompletion; ctx->writeWaitCreateCompletion = ctx->createCompletion; DEBUG(3, "write thread waiting : writeWaitCreateCompletion %f : writeWaitCompressionCompletion %f\n", ctx->writeWaitCreateCompletion, ctx->writeWaitCompressionCompletion); + DEBUG(2, "writer thread waiting for nextJob: %u, writeWaitCompressionCompletion %f, writeWaitCreateCompletion %f\n", currJob, ctx->writeWaitCompressionCompletion, ctx->writeWaitCreateCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - DEBUG(2, "waiting on job compressed, nextJob: %u\n", currJob); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); @@ -579,7 +586,7 @@ static void* outputThread(void* arg) /* update completion variable for writing */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->writeCompletion = 1 - (double)remaining/compressedSize; - DEBUG(3, "write completion %f\n", ctx->writeCompletion); + DEBUG(2, "write completion %f\n", ctx->writeCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); if (remaining == 0) break; @@ -630,8 +637,8 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) ctx->createWaitWriteCompletion = ctx->writeCompletion; DEBUG(3, "creation thread waiting : createWaitCompressionCompletion %f : createWaitWriteCompletion %f\n", ctx->createWaitCompressionCompletion, ctx->createWaitWriteCompletion); DEBUG(3, "writeCompletion: %f\n", ctx->writeCompletion); + DEBUG(2, "create thread waiting for nextJob: %u, createWaitCompressionCompletion %f, createWaitWriteCompletion %f\n", nextJob, ctx->createWaitCompressionCompletion, ctx->createWaitWriteCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - DEBUG(2, "waiting on job Write, nextJob: %u\n", nextJob); pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); @@ -721,7 +728,7 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA remaining -= ret; pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->createCompletion = 1 - (double)remaining/((size_t)FILE_CHUNK_SIZE); - DEBUG(3, "create completion: %f\n", ctx->createCompletion); + DEBUG(2, "create completion: %f\n", ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } if (remaining != 0 && !feof(srcFile)) { From 95bef759b3f1fc52bbcf5a82c1dddaf00906df8f Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 21 Jul 2017 17:49:39 -0700 Subject: [PATCH 109/145] switched over to model where reading only waits on compression thread --- contrib/adaptive-compression/adapt.c | 65 +++++++++++++++------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index e07333fc..9057bfcf 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -51,7 +51,7 @@ typedef struct { buffer_t dst; unsigned compressionLevel; unsigned jobID; - unsigned lastJob; + unsigned lastJobPlusOne; size_t compressedSize; size_t dictSize; } jobDescription; @@ -226,7 +226,7 @@ static int initCCtx(adaptCCtx* ctx, unsigned numJobs) jobDescription* job = &ctx->jobs[jobNum]; job->src.start = malloc(2 * FILE_CHUNK_SIZE); job->dst.start = malloc(ZSTD_compressBound(FILE_CHUNK_SIZE)); - job->lastJob = 0; + job->lastJobPlusOne = 0; if (!job->src.start || !job->dst.start) { DISPLAY("Could not allocate buffers for jobs\n"); return 1; @@ -400,22 +400,30 @@ static void* compressionThread(void* arg) unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "compressionThread(): waiting on job ready\n"); - - + /* wait until job is ready */ pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); - while(currJob + 1 > ctx->jobReadyID && !ctx->threadError) { + while (currJob + 1 > ctx->jobReadyID && !ctx->threadError) { pthread_mutex_lock(&ctx->completion_mutex.pMutex); - /* compression thread is waiting, take measurements of write completion and read completion */ + /* compression thread is waiting on creation thread, take measurement */ ctx->compressWaitCreateCompletion = ctx->createCompletion; - ctx->compressWaitWriteCompletion = ctx->writeCompletion; - DEBUG(3, "compression thread waiting : compressWaitCreateCompletion %f : compressWaitWriteCompletion %f\n", ctx->compressWaitCreateCompletion, ctx->compressWaitWriteCompletion); + DEBUG(3, "compression thread waiting : compressWaitCreateCompletion %f\n", ctx->compressWaitCreateCompletion); DEBUG(3, "create completion: %f\n", ctx->createCompletion); - DEBUG(2, "compression thread waiting for nextJob: %u, compressWaitCreateCompletion %f, compressWaitWriteCompletion %f\n", currJob, ctx->compressWaitCreateCompletion, ctx->compressWaitWriteCompletion); + DEBUG(2, "compression thread waiting for ready: %u, compressWaitCreateCompletion %f\n", currJob, ctx->compressWaitCreateCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); + /* wait until job previously in this space is written */ + pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); + while (currJob - ctx->jobWriteID >= ctx->numJobs && !ctx->threadError) { + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + ctx->compressWaitWriteCompletion = ctx->writeCompletion; + DEBUG(2, "compression thread waiting for write: %u, compressWaitWriteCompletion %f\n", currJob, ctx->compressWaitWriteCompletion); + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); + } + pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); /* reset compression completion */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->compressionCompletion = 0; @@ -439,10 +447,8 @@ static void* compressionThread(void* arg) size_t dstPos = 0; DEBUG(3, "cLevel used: %u\n", cLevel); DEBUG(3, "compression level used: %u\n", cLevel); - /* reset compressed size */ job->compressedSize = 0; - /* begin compression */ { size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); @@ -474,10 +480,10 @@ static void* compressionThread(void* arg) ZSTD_invalidateRepCodes(ctx->cctx); } { - DEBUG(3, "write out ending: %d\n", job->lastJob && (remaining == actualBlockSize)); - DEBUG(3, "lastJob %u\n", job->lastJob); + DEBUG(3, "write out ending: %d\n", (job->lastJobPlusOne == currJob + 1) && (remaining == actualBlockSize)); + DEBUG(3, "lastJobPlusOne %u\n", job->lastJobPlusOne); DEBUG(3, "compressionBlockSize %zu\n", compressionBlockSize); - size_t const ret = (job->lastJob && remaining == actualBlockSize) ? + size_t const ret = (job->lastJobPlusOne == currJob + 1 && remaining == actualBlockSize) ? ZSTD_compressEnd (ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, actualBlockSize) : ZSTD_compressContinue(ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, actualBlockSize); if (ZSTD_isError(ret)) { @@ -503,15 +509,16 @@ static void* compressionThread(void* arg) pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); ctx->jobCompressedID++; DEBUG(3, "signaling for job %u\n", currJob); - pthread_cond_signal(&ctx->jobCompressed_cond.pCond); + pthread_cond_broadcast(&ctx->jobCompressed_cond.pCond); pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); DEBUG(3, "finished job compression %u\n", currJob); - currJob++; - if (job->lastJob || ctx->threadError) { + if (job->lastJobPlusOne == currJob + 1 || ctx->threadError) { /* finished compressing all jobs */ DEBUG(3, "all jobs finished compressing\n"); break; } + + currJob++; } return arg; } @@ -544,7 +551,6 @@ static void* outputThread(void* arg) unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "outputThread(): waiting on job compressed\n"); - pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { pthread_mutex_lock(&ctx->completion_mutex.pMutex); @@ -599,8 +605,7 @@ static void* outputThread(void* arg) } } DEBUG(3, "finished job write %u\n", currJob); - currJob++; - displayProgress(currJob, ctx->compressionLevel, job->lastJob); + displayProgress(currJob, ctx->compressionLevel, job->lastJobPlusOne == currJob + 1); DEBUG(3, "locking job write mutex\n"); pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); ctx->jobWriteID++; @@ -608,7 +613,7 @@ static void* outputThread(void* arg) pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); DEBUG(3, "unlocking job write mutex\n"); - if (job->lastJob || ctx->threadError) { + if (job->lastJobPlusOne == currJob + 1 || ctx->threadError) { /* finished with all jobs */ DEBUG(3, "all jobs finished writing\n"); pthread_mutex_lock(&ctx->allJobsCompleted_mutex.pMutex); @@ -617,6 +622,7 @@ static void* outputThread(void* arg) pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); break; } + currJob++; } return arg; @@ -628,21 +634,22 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) unsigned const nextJobIndex = nextJob % ctx->numJobs; jobDescription* job = &ctx->jobs[nextJobIndex]; DEBUG(3, "createCompressionJob(): wait for job write\n"); - pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); - DEBUG(3, "Creating new compression job -- nextJob: %u, jobCompressedID: %u, jobWriteID: %u, numJObs: %u\n", nextJob,ctx->jobCompressedID, ctx->jobWriteID, ctx->numJobs); - while (nextJob - ctx->jobWriteID >= ctx->numJobs && !ctx->threadError) { + + + /* wait until the job has been compressed */ + pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); + while (nextJob - ctx->jobCompressedID >= ctx->numJobs && !ctx->threadError) { pthread_mutex_lock(&ctx->completion_mutex.pMutex); - /* creation thread is waiting, take measurement of compression completion */ + /* creation thread is waiting, take measurement of completion */ ctx->createWaitCompressionCompletion = ctx->compressionCompletion; ctx->createWaitWriteCompletion = ctx->writeCompletion; DEBUG(3, "creation thread waiting : createWaitCompressionCompletion %f : createWaitWriteCompletion %f\n", ctx->createWaitCompressionCompletion, ctx->createWaitWriteCompletion); DEBUG(3, "writeCompletion: %f\n", ctx->writeCompletion); DEBUG(2, "create thread waiting for nextJob: %u, createWaitCompressionCompletion %f, createWaitWriteCompletion %f\n", nextJob, ctx->createWaitCompressionCompletion, ctx->createWaitWriteCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); - pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); + pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } - pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); - + pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); /* reset create completion */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->createCompletion = 0; @@ -653,7 +660,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) job->compressionLevel = ctx->compressionLevel; job->src.size = srcSize; job->jobID = nextJob; - job->lastJob = last; + if (last) job->lastJobPlusOne = nextJob + 1; { /* swap buffer */ void* const copy = job->src.start; From 08d9e42ec61a51fe1a0189ac3b61b1f200918e57 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 21 Jul 2017 18:02:55 -0700 Subject: [PATCH 110/145] removed useless measurements --- contrib/adaptive-compression/adapt.c | 34 ++++++++-------------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 9057bfcf..68727cbd 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -77,11 +77,9 @@ typedef struct { unsigned jobWriteID; unsigned allJobsCompleted; unsigned adaptParam; - double createWaitWriteCompletion; double createWaitCompressionCompletion; double compressWaitCreateCompletion; double compressWaitWriteCompletion; - double writeWaitCreateCompletion; double writeWaitCompressionCompletion; double compressionCompletion; double writeCompletion; @@ -205,11 +203,9 @@ static int initCCtx(adaptCCtx* ctx, unsigned numJobs) ctx->lastDictSize = 0; - ctx->createWaitWriteCompletion = 1; ctx->createWaitCompressionCompletion = 1; ctx->compressWaitCreateCompletion = 1; ctx->compressWaitWriteCompletion = 1; - ctx->writeWaitCreateCompletion = 1; ctx->writeWaitCompressionCompletion = 1; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); @@ -316,11 +312,9 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) */ static void adaptCompressionLevel(adaptCCtx* ctx) { - double createWaitWriteCompletion; double createWaitCompressionCompletion; double compressWaitCreateCompletion; double compressWaitWriteCompletion; - double writeWaitCreateCompletion; double writeWaitCompressionCompletion; double const threshold = 0.00001; @@ -328,25 +322,19 @@ static void adaptCompressionLevel(adaptCCtx* ctx) /* read and reset completion measurements */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); DEBUG(2, "rc %f\n", ctx->createWaitCompressionCompletion); - DEBUG(2, "rw %f\n", ctx->createWaitWriteCompletion); DEBUG(2, "cr %f\n", ctx->compressWaitCreateCompletion); DEBUG(2, "cw %f\n", ctx->compressWaitWriteCompletion); - DEBUG(2, "wr %f\n", ctx->writeWaitCreateCompletion); DEBUG(2, "wc %f\n\n", ctx->writeWaitCompressionCompletion); createWaitCompressionCompletion = ctx->createWaitCompressionCompletion; - createWaitWriteCompletion = ctx->createWaitWriteCompletion; compressWaitCreateCompletion = ctx->compressWaitCreateCompletion; compressWaitWriteCompletion = ctx->compressWaitWriteCompletion; - writeWaitCreateCompletion = ctx->writeWaitCreateCompletion; writeWaitCompressionCompletion = ctx->writeWaitCompressionCompletion; DEBUG(2, "resetting adaptive variables\n"); - ctx->createWaitWriteCompletion = 1; ctx->createWaitCompressionCompletion = 1; ctx->compressWaitCreateCompletion = 1; ctx->compressWaitWriteCompletion = 1; - ctx->writeWaitCreateCompletion = 1; ctx->writeWaitCompressionCompletion = 1; pthread_mutex_unlock(&ctx->completion_mutex.pMutex); @@ -360,19 +348,18 @@ static void adaptCompressionLevel(adaptCCtx* ctx) ctx->compressionLevel -= boundChange; DEBUG(2, "create and write threads waiting, tried to decrease compression level by %u\n", boundChange); } - else if (1-createWaitWriteCompletion > threshold && 1-compressWaitWriteCompletion > threshold) { + else if (1-compressWaitWriteCompletion > threshold) { /* both create and compression thread waiting on write */ - /* use createWaitWriteCompletion */ - double const completion = MAX(createWaitWriteCompletion, compressWaitWriteCompletion); + double const completion = compressWaitWriteCompletion; unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); ctx->compressionLevel += boundChange; DEBUG(2, "create and compression threads waiting, tried to increase compression level by %u\n", boundChange); } - else if (1-writeWaitCreateCompletion > threshold && 1-compressWaitCreateCompletion > threshold) { + else if (1-compressWaitCreateCompletion > threshold) { /* both compression and write waiting on create */ /* use compressWaitCreateCompletion */ - double const completion = MAX(writeWaitCreateCompletion, compressWaitCreateCompletion); + double const completion = compressWaitCreateCompletion; unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); ctx->compressionLevel += boundChange; @@ -417,6 +404,7 @@ static void* compressionThread(void* arg) /* wait until job previously in this space is written */ pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); while (currJob - ctx->jobWriteID >= ctx->numJobs && !ctx->threadError) { + /* compression thread is waiting on writer thread, take measurement */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->compressWaitWriteCompletion = ctx->writeCompletion; DEBUG(2, "compression thread waiting for write: %u, compressWaitWriteCompletion %f\n", currJob, ctx->compressWaitWriteCompletion); @@ -554,11 +542,10 @@ static void* outputThread(void* arg) pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { pthread_mutex_lock(&ctx->completion_mutex.pMutex); - /* write thread is waiting, take measurement of compression completion */ + /* write thread is waiting on compression thread */ ctx->writeWaitCompressionCompletion = ctx->compressionCompletion; - ctx->writeWaitCreateCompletion = ctx->createCompletion; - DEBUG(3, "write thread waiting : writeWaitCreateCompletion %f : writeWaitCompressionCompletion %f\n", ctx->writeWaitCreateCompletion, ctx->writeWaitCompressionCompletion); - DEBUG(2, "writer thread waiting for nextJob: %u, writeWaitCompressionCompletion %f, writeWaitCreateCompletion %f\n", currJob, ctx->writeWaitCompressionCompletion, ctx->writeWaitCreateCompletion); + DEBUG(3, "write thread waiting : writeWaitCompressionCompletion %f\n", ctx->writeWaitCompressionCompletion); + DEBUG(2, "writer thread waiting for nextJob: %u, writeWaitCompressionCompletion %f\n", currJob, ctx->writeWaitCompressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } @@ -642,10 +629,9 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) pthread_mutex_lock(&ctx->completion_mutex.pMutex); /* creation thread is waiting, take measurement of completion */ ctx->createWaitCompressionCompletion = ctx->compressionCompletion; - ctx->createWaitWriteCompletion = ctx->writeCompletion; - DEBUG(3, "creation thread waiting : createWaitCompressionCompletion %f : createWaitWriteCompletion %f\n", ctx->createWaitCompressionCompletion, ctx->createWaitWriteCompletion); + DEBUG(3, "creation thread waiting : createWaitCompressionCompletion %f\n", ctx->createWaitCompressionCompletion); DEBUG(3, "writeCompletion: %f\n", ctx->writeCompletion); - DEBUG(2, "create thread waiting for nextJob: %u, createWaitCompressionCompletion %f, createWaitWriteCompletion %f\n", nextJob, ctx->createWaitCompressionCompletion, ctx->createWaitWriteCompletion); + DEBUG(2, "create thread waiting for nextJob: %u, createWaitCompressionCompletion %f\n", nextJob, ctx->createWaitCompressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } From 880f08d1049ee320aa54673b99c9ec0e57c5e397 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Sun, 23 Jul 2017 10:18:54 -0700 Subject: [PATCH 111/145] change how completion is measured in compression thread --- contrib/adaptive-compression/adapt.c | 48 ++++++++++++++++++---------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 68727cbd..24842145 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -25,7 +25,7 @@ #define DEFAULT_DISPLAY_LEVEL 1 #define DEFAULT_COMPRESSION_LEVEL 6 #define DEFAULT_ADAPT_PARAM 0 -#define MAX_COMPRESSION_LEVEL_CHANGE 3 +#define MAX_COMPRESSION_LEVEL_CHANGE 2 static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; @@ -207,6 +207,9 @@ static int initCCtx(adaptCCtx* ctx, unsigned numJobs) ctx->compressWaitCreateCompletion = 1; ctx->compressWaitWriteCompletion = 1; ctx->writeWaitCompressionCompletion = 1; + ctx->createCompletion = 1; + ctx->writeCompletion = 1; + ctx->compressionCompletion = 1; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); @@ -387,16 +390,34 @@ static void* compressionThread(void* arg) unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; DEBUG(3, "compressionThread(): waiting on job ready\n"); + + { + /* check if compression thread will have to wait */ + unsigned willWaitForCreate = 0; + unsigned willWaitForWrite = 0; + + pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); + if (currJob + 1 > ctx->jobReadyID) willWaitForCreate = 1; + pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); + + pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); + if (currJob - ctx->jobWriteID >= ctx->numJobs) willWaitForWrite = 1; + pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); + + pthread_mutex_lock(&ctx->completion_mutex.pMutex); + if (willWaitForCreate || willWaitForWrite) { + ctx->compressWaitCreateCompletion = ctx->createCompletion; + ctx->compressWaitWriteCompletion = ctx->writeCompletion; + DEBUG(2, "compression will wait for create or write\n"); + DEBUG(2, "create completion %f\n", ctx->compressWaitCreateCompletion); + DEBUG(2, "write completion %f\n", ctx->compressWaitWriteCompletion); + } + pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + } + /* wait until job is ready */ pthread_mutex_lock(&ctx->jobReady_mutex.pMutex); while (currJob + 1 > ctx->jobReadyID && !ctx->threadError) { - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - /* compression thread is waiting on creation thread, take measurement */ - ctx->compressWaitCreateCompletion = ctx->createCompletion; - DEBUG(3, "compression thread waiting : compressWaitCreateCompletion %f\n", ctx->compressWaitCreateCompletion); - DEBUG(3, "create completion: %f\n", ctx->createCompletion); - DEBUG(2, "compression thread waiting for ready: %u, compressWaitCreateCompletion %f\n", currJob, ctx->compressWaitCreateCompletion); - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); pthread_cond_wait(&ctx->jobReady_cond.pCond, &ctx->jobReady_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobReady_mutex.pMutex); @@ -404,11 +425,6 @@ static void* compressionThread(void* arg) /* wait until job previously in this space is written */ pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); while (currJob - ctx->jobWriteID >= ctx->numJobs && !ctx->threadError) { - /* compression thread is waiting on writer thread, take measurement */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); - ctx->compressWaitWriteCompletion = ctx->writeCompletion; - DEBUG(2, "compression thread waiting for write: %u, compressWaitWriteCompletion %f\n", currJob, ctx->compressWaitWriteCompletion); - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); pthread_cond_wait(&ctx->jobWrite_cond.pCond, &ctx->jobWrite_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); @@ -488,7 +504,7 @@ static void* compressionThread(void* arg) /* update completion */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->compressionCompletion = 1 - (double)remaining/job->src.size; - DEBUG(2, "compression completion %f\n", ctx->compressionCompletion); + DEBUG(2, "compression completion %u %f\n", currJob, ctx->compressionCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } } while (remaining != 0); @@ -579,7 +595,7 @@ static void* outputThread(void* arg) /* update completion variable for writing */ pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->writeCompletion = 1 - (double)remaining/compressedSize; - DEBUG(2, "write completion %f\n", ctx->writeCompletion); + DEBUG(2, "write completion %u %f\n", currJob, ctx->writeCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); if (remaining == 0) break; @@ -721,7 +737,7 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA remaining -= ret; pthread_mutex_lock(&ctx->completion_mutex.pMutex); ctx->createCompletion = 1 - (double)remaining/((size_t)FILE_CHUNK_SIZE); - DEBUG(2, "create completion: %f\n", ctx->createCompletion); + DEBUG(2, "create completion %u %f\n", currJob, ctx->createCompletion); pthread_mutex_unlock(&ctx->completion_mutex.pMutex); } if (remaining != 0 && !feof(srcFile)) { From 483d936b8760819f8f6b71c2eb0c635165293086 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Sun, 23 Jul 2017 14:09:16 -0700 Subject: [PATCH 112/145] reduced competition for completion mutex by separating mutex use based on which values is updated --- contrib/adaptive-compression/adapt.c | 93 ++++++++++++++++------------ 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 24842145..4e5a86c7 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -92,8 +92,9 @@ typedef struct { cond_t allJobsCompleted_cond; mutex_t jobWrite_mutex; cond_t jobWrite_cond; - mutex_t completion_mutex; - mutex_t wait_mutex; + mutex_t compressionCompletion_mutex; + mutex_t createCompletion_mutex; + mutex_t writeCompletion_mutex; size_t lastDictSize; inBuff_t input; jobDescription* jobs; @@ -152,8 +153,9 @@ static int freeCCtx(adaptCCtx* ctx) error |= destroyCond(&ctx->allJobsCompleted_cond); error |= destroyMutex(&ctx->jobWrite_mutex); error |= destroyCond(&ctx->jobWrite_cond); - error |= destroyMutex(&ctx->completion_mutex); - error |= destroyMutex(&ctx->wait_mutex); + error |= destroyMutex(&ctx->compressionCompletion_mutex); + error |= destroyMutex(&ctx->createCompletion_mutex); + error |= destroyMutex(&ctx->writeCompletion_mutex); error |= ZSTD_isError(ZSTD_freeCCtx(ctx->cctx)); free(ctx->input.buffer.start); if (ctx->jobs){ @@ -192,8 +194,9 @@ static int initCCtx(adaptCCtx* ctx, unsigned numJobs) pthreadError |= initCond(&ctx->allJobsCompleted_cond); pthreadError |= initMutex(&ctx->jobWrite_mutex); pthreadError |= initCond(&ctx->jobWrite_cond); - pthreadError |= initMutex(&ctx->completion_mutex); - pthreadError |= initMutex(&ctx->wait_mutex); + pthreadError |= initMutex(&ctx->compressionCompletion_mutex); + pthreadError |= initMutex(&ctx->createCompletion_mutex); + pthreadError |= initMutex(&ctx->writeCompletion_mutex); if (pthreadError) return pthreadError; } ctx->numJobs = numJobs; @@ -323,28 +326,32 @@ static void adaptCompressionLevel(adaptCCtx* ctx) DEBUG(2, "adapting compression level %u\n", ctx->compressionLevel); /* read and reset completion measurements */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); + + pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); DEBUG(2, "rc %f\n", ctx->createWaitCompressionCompletion); - DEBUG(2, "cr %f\n", ctx->compressWaitCreateCompletion); - DEBUG(2, "cw %f\n", ctx->compressWaitWriteCompletion); DEBUG(2, "wc %f\n\n", ctx->writeWaitCompressionCompletion); - createWaitCompressionCompletion = ctx->createWaitCompressionCompletion; - compressWaitCreateCompletion = ctx->compressWaitCreateCompletion; - compressWaitWriteCompletion = ctx->compressWaitWriteCompletion; writeWaitCompressionCompletion = ctx->writeWaitCompressionCompletion; - - DEBUG(2, "resetting adaptive variables\n"); ctx->createWaitCompressionCompletion = 1; - ctx->compressWaitCreateCompletion = 1; - ctx->compressWaitWriteCompletion = 1; ctx->writeWaitCompressionCompletion = 1; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); + + pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); + DEBUG(2, "cw %f\n", ctx->compressWaitWriteCompletion); + compressWaitWriteCompletion = ctx->compressWaitWriteCompletion; + ctx->compressWaitWriteCompletion = 1; + pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); + + pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); + DEBUG(2, "cr %f\n", ctx->compressWaitCreateCompletion); + compressWaitCreateCompletion = ctx->compressWaitCreateCompletion; + ctx->compressWaitCreateCompletion = 1; + pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); /* adaptation logic */ - if (1-createWaitCompressionCompletion > threshold && 1-writeWaitCompressionCompletion > threshold) { - /* both create and write threads waiting on compression */ - /* use writeWaitCompressionCompletion */ + if (1-createWaitCompressionCompletion > threshold || 1-writeWaitCompressionCompletion > threshold) { + /* compression waiting on either create or write */ + /* use whichever one waited less because it was slower */ double const completion = MAX(createWaitCompressionCompletion, writeWaitCompressionCompletion); unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ctx->compressionLevel - 1); @@ -404,15 +411,21 @@ static void* compressionThread(void* arg) if (currJob - ctx->jobWriteID >= ctx->numJobs) willWaitForWrite = 1; pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); - pthread_mutex_lock(&ctx->completion_mutex.pMutex); + if (willWaitForCreate || willWaitForWrite) { - ctx->compressWaitCreateCompletion = ctx->createCompletion; - ctx->compressWaitWriteCompletion = ctx->writeCompletion; DEBUG(2, "compression will wait for create or write\n"); + + pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); + ctx->compressWaitCreateCompletion = ctx->createCompletion; DEBUG(2, "create completion %f\n", ctx->compressWaitCreateCompletion); + pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); + + pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); + ctx->compressWaitWriteCompletion = ctx->writeCompletion; DEBUG(2, "write completion %f\n", ctx->compressWaitWriteCompletion); + pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); } - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + } /* wait until job is ready */ @@ -429,9 +442,9 @@ static void* compressionThread(void* arg) } pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); /* reset compression completion */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); ctx->compressionCompletion = 0; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); DEBUG(3, "compressionThread(): continuing after job ready\n"); DEBUG(3, "DICTIONARY ENDED\n"); @@ -502,10 +515,10 @@ static void* compressionThread(void* arg) blockNum++; /* update completion */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); ctx->compressionCompletion = 1 - (double)remaining/job->src.size; DEBUG(2, "compression completion %u %f\n", currJob, ctx->compressionCompletion); - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); } } while (remaining != 0); job->dst.size = job->compressedSize; @@ -557,20 +570,20 @@ static void* outputThread(void* arg) DEBUG(3, "outputThread(): waiting on job compressed\n"); pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { - pthread_mutex_lock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); /* write thread is waiting on compression thread */ ctx->writeWaitCompressionCompletion = ctx->compressionCompletion; DEBUG(3, "write thread waiting : writeWaitCompressionCompletion %f\n", ctx->writeWaitCompressionCompletion); DEBUG(2, "writer thread waiting for nextJob: %u, writeWaitCompressionCompletion %f\n", currJob, ctx->writeWaitCompressionCompletion); - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); /* reset write completion */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); ctx->writeCompletion = 0; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); DEBUG(3, "outputThread(): continuing after job compressed\n"); { @@ -593,10 +606,10 @@ static void* outputThread(void* arg) remaining -= ret; /* update completion variable for writing */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); ctx->writeCompletion = 1 - (double)remaining/compressedSize; DEBUG(2, "write completion %u %f\n", currJob, ctx->writeCompletion); - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); if (remaining == 0) break; } @@ -642,20 +655,20 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) /* wait until the job has been compressed */ pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (nextJob - ctx->jobCompressedID >= ctx->numJobs && !ctx->threadError) { - pthread_mutex_lock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); /* creation thread is waiting, take measurement of completion */ ctx->createWaitCompressionCompletion = ctx->compressionCompletion; DEBUG(3, "creation thread waiting : createWaitCompressionCompletion %f\n", ctx->createWaitCompressionCompletion); DEBUG(3, "writeCompletion: %f\n", ctx->writeCompletion); DEBUG(2, "create thread waiting for nextJob: %u, createWaitCompressionCompletion %f\n", nextJob, ctx->createWaitCompressionCompletion); - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); /* reset create completion */ - pthread_mutex_lock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); ctx->createCompletion = 0; - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); DEBUG(3, "createCompressionJob(): continuing after job write\n"); DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); @@ -735,10 +748,10 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA } pos += ret; remaining -= ret; - pthread_mutex_lock(&ctx->completion_mutex.pMutex); + pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); ctx->createCompletion = 1 - (double)remaining/((size_t)FILE_CHUNK_SIZE); DEBUG(2, "create completion %u %f\n", currJob, ctx->createCompletion); - pthread_mutex_unlock(&ctx->completion_mutex.pMutex); + pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); } if (remaining != 0 && !feof(srcFile)) { DISPLAY("Error: problem occurred during read from src file\n"); From e508f632d6d4f92836b339a7f2d2e70a009bbaf3 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 24 Jul 2017 11:01:36 -0700 Subject: [PATCH 113/145] updated comments and debug statements --- contrib/adaptive-compression/adapt.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 4e5a86c7..a90b5990 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -350,30 +350,30 @@ static void adaptCompressionLevel(adaptCCtx* ctx) /* adaptation logic */ if (1-createWaitCompressionCompletion > threshold || 1-writeWaitCompressionCompletion > threshold) { - /* compression waiting on either create or write */ + /* create or write waiting on compression */ /* use whichever one waited less because it was slower */ double const completion = MAX(createWaitCompressionCompletion, writeWaitCompressionCompletion); unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ctx->compressionLevel - 1); ctx->compressionLevel -= boundChange; - DEBUG(2, "create and write threads waiting, tried to decrease compression level by %u\n", boundChange); + DEBUG(2, "create or write threads waiting on compression, tried to decrease compression level by %u\n", boundChange); } else if (1-compressWaitWriteCompletion > threshold) { - /* both create and compression thread waiting on write */ + /* compress waiting on write */ double const completion = compressWaitWriteCompletion; unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); ctx->compressionLevel += boundChange; - DEBUG(2, "create and compression threads waiting, tried to increase compression level by %u\n", boundChange); + DEBUG(2, "compress waiting on write, tried to increase compression level by %u\n", boundChange); } else if (1-compressWaitCreateCompletion > threshold) { - /* both compression and write waiting on create */ + /* compress waiting on create*/ /* use compressWaitCreateCompletion */ double const completion = compressWaitCreateCompletion; unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); ctx->compressionLevel += boundChange; - DEBUG(2, "compression and write threads waiting, tried to increase compression level by %u\n", boundChange); + DEBUG(2, "compression waiting on create, tried to increase compression level by %u\n", boundChange); } if (g_forceCompressionLevel) { From d3d759301f0b6f90b2321f4b2d29120cc4ccd9b5 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 24 Jul 2017 13:47:39 -0700 Subject: [PATCH 114/145] changing position of endline for debug --- contrib/adaptive-compression/adapt.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index a90b5990..1b546f58 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -329,7 +329,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); DEBUG(2, "rc %f\n", ctx->createWaitCompressionCompletion); - DEBUG(2, "wc %f\n\n", ctx->writeWaitCompressionCompletion); + DEBUG(2, "wc %f\n", ctx->writeWaitCompressionCompletion); createWaitCompressionCompletion = ctx->createWaitCompressionCompletion; writeWaitCompressionCompletion = ctx->writeWaitCompressionCompletion; ctx->createWaitCompressionCompletion = 1; @@ -356,7 +356,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ctx->compressionLevel - 1); ctx->compressionLevel -= boundChange; - DEBUG(2, "create or write threads waiting on compression, tried to decrease compression level by %u\n", boundChange); + DEBUG(2, "create or write threads waiting on compression, tried to decrease compression level by %u\n\n", boundChange); } else if (1-compressWaitWriteCompletion > threshold) { /* compress waiting on write */ @@ -364,7 +364,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); ctx->compressionLevel += boundChange; - DEBUG(2, "compress waiting on write, tried to increase compression level by %u\n", boundChange); + DEBUG(2, "compress waiting on write, tried to increase compression level by %u\n\n", boundChange); } else if (1-compressWaitCreateCompletion > threshold) { /* compress waiting on create*/ @@ -373,7 +373,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); ctx->compressionLevel += boundChange; - DEBUG(2, "compression waiting on create, tried to increase compression level by %u\n", boundChange); + DEBUG(2, "compression waiting on create, tried to increase compression level by %u\n\n", boundChange); } if (g_forceCompressionLevel) { @@ -517,7 +517,7 @@ static void* compressionThread(void* arg) /* update completion */ pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); ctx->compressionCompletion = 1 - (double)remaining/job->src.size; - DEBUG(2, "compression completion %u %f\n", currJob, ctx->compressionCompletion); + DEBUG(3, "compression completion %u %f\n", currJob, ctx->compressionCompletion); pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); } } while (remaining != 0); @@ -608,7 +608,7 @@ static void* outputThread(void* arg) /* update completion variable for writing */ pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); ctx->writeCompletion = 1 - (double)remaining/compressedSize; - DEBUG(2, "write completion %u %f\n", currJob, ctx->writeCompletion); + DEBUG(3, "write completion %u %f\n", currJob, ctx->writeCompletion); pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); if (remaining == 0) break; @@ -750,7 +750,7 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA remaining -= ret; pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); ctx->createCompletion = 1 - (double)remaining/((size_t)FILE_CHUNK_SIZE); - DEBUG(2, "create completion %u %f\n", currJob, ctx->createCompletion); + DEBUG(3, "create completion %u %f\n", currJob, ctx->createCompletion); pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); } if (remaining != 0 && !feof(srcFile)) { From 8328f8192a61d4c4e140eff31f1bdaf034abb805 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 24 Jul 2017 14:40:23 -0700 Subject: [PATCH 115/145] updating debug statements again --- contrib/adaptive-compression/adapt.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 1b546f58..09884c47 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -396,7 +396,7 @@ static void* compressionThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; - DEBUG(3, "compressionThread(): waiting on job ready\n"); + DEBUG(2, "starting compression for job %u\n", currJob); { /* check if compression thread will have to wait */ @@ -413,7 +413,7 @@ static void* compressionThread(void* arg) if (willWaitForCreate || willWaitForWrite) { - DEBUG(2, "compression will wait for create or write\n"); + DEBUG(2, "compression will wait for create or write on job %u\n", currJob); pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); ctx->compressWaitCreateCompletion = ctx->createCompletion; @@ -534,7 +534,7 @@ static void* compressionThread(void* arg) DEBUG(3, "all jobs finished compressing\n"); break; } - + DEBUG(2, "finished compressing job %u\n", currJob); currJob++; } return arg; @@ -567,7 +567,7 @@ static void* outputThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* job = &ctx->jobs[currJobIndex]; - DEBUG(3, "outputThread(): waiting on job compressed\n"); + DEBUG(2, "starting write for job %u\n", currJob); pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); @@ -638,6 +638,7 @@ static void* outputThread(void* arg) pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); break; } + DEBUG(2, "finished writing job %u\n", currJob); currJob++; } @@ -738,6 +739,7 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA size_t const readBlockSize = 1 << 15; size_t remaining = FILE_CHUNK_SIZE; + DEBUG(2, "starting creation of job %u\n", currJob); while (remaining != 0 && !feof(srcFile)) { size_t const ret = fread(ctx->input.buffer.start + ctx->input.filled + pos, 1, readBlockSize, srcFile); if (ret != readBlockSize && !feof(srcFile)) { @@ -768,6 +770,7 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA return error; } } + DEBUG(2, "finished creating job %u\n", currJob); currJob++; if (feof(srcFile)) { DEBUG(3, "THE STREAM OF DATA ENDED %u\n", ctx->nextJobID); From 0ee3f8c2f8b65a0261158ba9bb8335a0a6900b15 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 24 Jul 2017 15:06:11 -0700 Subject: [PATCH 116/145] adding more debug --- contrib/adaptive-compression/adapt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 09884c47..ec40130a 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -466,6 +466,7 @@ static void* compressionThread(void* arg) DEBUG(3, "compression level used: %u\n", cLevel); /* reset compressed size */ job->compressedSize = 0; + DEBUG(2, "calling ZSTD_compressBegin()\n"); /* begin compression */ { size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); @@ -479,6 +480,7 @@ static void* compressionThread(void* arg) return arg; } } + DEBUG(2, "finished with ZSTD_compressBegin()\n"); do { size_t const actualBlockSize = MIN(remaining, compressionBlockSize); From 4dc83ca64c9e5f0e87d08daf5b349e59ef97d5e1 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 24 Jul 2017 15:14:58 -0700 Subject: [PATCH 117/145] compression thread should take measurements independently based on whether or not the create/write thread will actually bottleneck performance --- contrib/adaptive-compression/adapt.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index ec40130a..ef0da42f 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -412,14 +412,17 @@ static void* compressionThread(void* arg) pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); - if (willWaitForCreate || willWaitForWrite) { - DEBUG(2, "compression will wait for create or write on job %u\n", currJob); - + if (willWaitForCreate) { + DEBUG(2, "compression will wait for create on job %u\n", currJob); pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); ctx->compressWaitCreateCompletion = ctx->createCompletion; DEBUG(2, "create completion %f\n", ctx->compressWaitCreateCompletion); pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); + } + + if (willWaitForWrite) { + DEBUG(2, "compression will wait for write on job %u\n", currJob); pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); ctx->compressWaitWriteCompletion = ctx->writeCompletion; DEBUG(2, "write completion %f\n", ctx->compressWaitWriteCompletion); From df3754b6ed4f0f762f4702457eecc87f89593ec6 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 24 Jul 2017 16:19:07 -0700 Subject: [PATCH 118/145] add quiet option, make progress bar default --- contrib/adaptive-compression/adapt.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index ef0da42f..c4c46af5 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -31,7 +31,7 @@ static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; static UTIL_time_t g_startTime; static size_t g_streamedSize = 0; -static unsigned g_useProgressBar = 0; +static unsigned g_useProgressBar = 1; static UTIL_freq_t g_ticksPerSecond; static unsigned g_forceCompressionLevel = 0; @@ -903,6 +903,7 @@ static void help() PRINT(" -i# : provide initial compression level\n"); PRINT(" -h : display help/information\n"); PRINT(" -f : force the compression level to stay constant\n"); + PRINT(" -q : quiet mode -- do not show progress bar or other information\n"); } /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) @@ -953,6 +954,10 @@ int main(int argCount, const char* argv[]) case 'f': g_forceCompressionLevel = 1; break; + case 'q': + g_useProgressBar = 0; + g_displayLevel = 0; + break; default: DISPLAY("Error: invalid argument provided\n"); ret = 1; From 700758d67677836cc9208d868f49b9bc522b2552 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 24 Jul 2017 16:26:20 -0700 Subject: [PATCH 119/145] added help statement for -p, switched it to hide progress bar now that progress bar is default --- contrib/adaptive-compression/adapt.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index c4c46af5..f6bcddd1 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -903,6 +903,7 @@ static void help() PRINT(" -i# : provide initial compression level\n"); PRINT(" -h : display help/information\n"); PRINT(" -f : force the compression level to stay constant\n"); + PRINT(" -p : hide progress bar\n"); PRINT(" -q : quiet mode -- do not show progress bar or other information\n"); } /* return 0 if successful, else return error */ @@ -945,7 +946,7 @@ int main(int argCount, const char* argv[]) help(); goto _main_exit; case 'p': - g_useProgressBar = 1; + g_useProgressBar = 0; break; case 'c': forceStdout = 1; From 6f1e260eddd08001631d6f8c5d760f9da6674e3a Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 10:01:10 -0700 Subject: [PATCH 120/145] added mechanism for getting rid of spikes --- contrib/adaptive-compression/adapt.c | 38 +++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index f6bcddd1..32f54e67 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -26,6 +26,7 @@ #define DEFAULT_COMPRESSION_LEVEL 6 #define DEFAULT_ADAPT_PARAM 0 #define MAX_COMPRESSION_LEVEL_CHANGE 2 +#define CONVERGENCE_LOWER_BOUND 3 static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; @@ -77,6 +78,7 @@ typedef struct { unsigned jobWriteID; unsigned allJobsCompleted; unsigned adaptParam; + unsigned convergenceCounter; double createWaitCompressionCompletion; double compressWaitCreateCompletion; double compressWaitWriteCompletion; @@ -213,6 +215,7 @@ static int initCCtx(adaptCCtx* ctx, unsigned numJobs) ctx->createCompletion = 1; ctx->writeCompletion = 1; ctx->compressionCompletion = 1; + ctx->convergenceCounter = 0; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); @@ -323,6 +326,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) double compressWaitWriteCompletion; double writeWaitCompressionCompletion; double const threshold = 0.00001; + unsigned const prevCompressionLevel = ctx->compressionLevel; DEBUG(2, "adapting compression level %u\n", ctx->compressionLevel); /* read and reset completion measurements */ @@ -347,7 +351,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) compressWaitCreateCompletion = ctx->compressWaitCreateCompletion; ctx->compressWaitCreateCompletion = 1; pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); - + DEBUG(2, "convergence counter: %u\n", ctx->convergenceCounter); /* adaptation logic */ if (1-createWaitCompressionCompletion > threshold || 1-writeWaitCompressionCompletion > threshold) { /* create or write waiting on compression */ @@ -355,7 +359,15 @@ static void adaptCompressionLevel(adaptCCtx* ctx) double const completion = MAX(createWaitCompressionCompletion, writeWaitCompressionCompletion); unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ctx->compressionLevel - 1); - ctx->compressionLevel -= boundChange; + if (ctx->convergenceCounter > CONVERGENCE_LOWER_BOUND && boundChange != 0) { + /* reset convergence counter, might have been a spike */ + ctx->convergenceCounter = 0; + } + else if (boundChange != 0) { + ctx->compressionLevel -= boundChange; + ctx->convergenceCounter = 1; + } + DEBUG(2, "create or write threads waiting on compression, tried to decrease compression level by %u\n\n", boundChange); } else if (1-compressWaitWriteCompletion > threshold) { @@ -363,7 +375,14 @@ static void adaptCompressionLevel(adaptCCtx* ctx) double const completion = compressWaitWriteCompletion; unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); - ctx->compressionLevel += boundChange; + if (ctx->convergenceCounter > CONVERGENCE_LOWER_BOUND && boundChange != 0) { + ctx->convergenceCounter = 0; + } + else if (boundChange != 0) { + ctx->compressionLevel += boundChange; + ctx->convergenceCounter = 1; + } + DEBUG(2, "compress waiting on write, tried to increase compression level by %u\n\n", boundChange); } else if (1-compressWaitCreateCompletion > threshold) { @@ -372,10 +391,21 @@ static void adaptCompressionLevel(adaptCCtx* ctx) double const completion = compressWaitCreateCompletion; unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); - ctx->compressionLevel += boundChange; + if (ctx->convergenceCounter > CONVERGENCE_LOWER_BOUND && boundChange != 0) { + ctx->convergenceCounter = 0; + } + else if (boundChange != 0) { + ctx->compressionLevel += boundChange; + ctx->convergenceCounter = 1; + } + DEBUG(2, "compression waiting on create, tried to increase compression level by %u\n\n", boundChange); } + if (ctx->compressionLevel == prevCompressionLevel) { + ctx->convergenceCounter++; + } + if (g_forceCompressionLevel) { ctx->compressionLevel = g_compressionLevel; } From 85d7c919f6b85ef4deb82bc7e4c2cdbfc49b2e10 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 10:32:14 -0700 Subject: [PATCH 121/145] created independent function for controlling how completion relates to compression level change --- contrib/adaptive-compression/adapt.c | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 32f54e67..87082be4 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -311,6 +311,19 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); } +static unsigned convertCompletionToChange(double completion) +{ + if (completion < 0.05) { + return 2; + } + else if (completion < 0.5) { + return 1; + } + else { + return 0; + } +} + /* * Compression level is changed depending on which part of the compression process is lagging * Currently, three theads exist for job creation, compression, and file writing respectively. @@ -357,7 +370,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) /* create or write waiting on compression */ /* use whichever one waited less because it was slower */ double const completion = MAX(createWaitCompressionCompletion, writeWaitCompressionCompletion); - unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); + unsigned const change = convertCompletionToChange(completion); unsigned const boundChange = MIN(change, ctx->compressionLevel - 1); if (ctx->convergenceCounter > CONVERGENCE_LOWER_BOUND && boundChange != 0) { /* reset convergence counter, might have been a spike */ @@ -373,7 +386,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) else if (1-compressWaitWriteCompletion > threshold) { /* compress waiting on write */ double const completion = compressWaitWriteCompletion; - unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); + unsigned const change = convertCompletionToChange(completion); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); if (ctx->convergenceCounter > CONVERGENCE_LOWER_BOUND && boundChange != 0) { ctx->convergenceCounter = 0; @@ -389,7 +402,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) /* compress waiting on create*/ /* use compressWaitCreateCompletion */ double const completion = compressWaitCreateCompletion; - unsigned const change = (unsigned)((1-completion) * MAX_COMPRESSION_LEVEL_CHANGE); + unsigned const change = convertCompletionToChange(completion); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); if (ctx->convergenceCounter > CONVERGENCE_LOWER_BOUND && boundChange != 0) { ctx->convergenceCounter = 0; From e02c79f8336f4057b9f01a5e9565e9269d701e01 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 11:16:27 -0700 Subject: [PATCH 122/145] started using decrease cooldown so that compression level would not decrease several times in a row --- contrib/adaptive-compression/adapt.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 87082be4..fac93d27 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -26,7 +26,8 @@ #define DEFAULT_COMPRESSION_LEVEL 6 #define DEFAULT_ADAPT_PARAM 0 #define MAX_COMPRESSION_LEVEL_CHANGE 2 -#define CONVERGENCE_LOWER_BOUND 3 +#define CONVERGENCE_LOWER_BOUND 5 +#define CLEVEL_DECREASE_COOLDOWN 5 static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; @@ -79,6 +80,7 @@ typedef struct { unsigned allJobsCompleted; unsigned adaptParam; unsigned convergenceCounter; + unsigned cooldown; double createWaitCompressionCompletion; double compressWaitCreateCompletion; double compressWaitWriteCompletion; @@ -216,6 +218,7 @@ static int initCCtx(adaptCCtx* ctx, unsigned numJobs) ctx->writeCompletion = 1; ctx->compressionCompletion = 1; ctx->convergenceCounter = 0; + ctx->cooldown = 0; ctx->jobs = calloc(1, numJobs*sizeof(jobDescription)); @@ -365,8 +368,11 @@ static void adaptCompressionLevel(adaptCCtx* ctx) ctx->compressWaitCreateCompletion = 1; pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); DEBUG(2, "convergence counter: %u\n", ctx->convergenceCounter); + /* adaptation logic */ - if (1-createWaitCompressionCompletion > threshold || 1-writeWaitCompressionCompletion > threshold) { + if (ctx->cooldown) ctx->cooldown--; + + if ((1-createWaitCompressionCompletion > threshold || 1-writeWaitCompressionCompletion > threshold) && ctx->cooldown == 0) { /* create or write waiting on compression */ /* use whichever one waited less because it was slower */ double const completion = MAX(createWaitCompressionCompletion, writeWaitCompressionCompletion); @@ -378,6 +384,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) } else if (boundChange != 0) { ctx->compressionLevel -= boundChange; + ctx->cooldown = CLEVEL_DECREASE_COOLDOWN; ctx->convergenceCounter = 1; } From 310c12d07eb5a982fdcd2c025b5e3c98ec181c1b Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 14:08:39 -0700 Subject: [PATCH 123/145] moved debug statements to a compiler flag --- contrib/adaptive-compression/Makefile | 3 +++ contrib/adaptive-compression/adapt.c | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index f2059a19..9bc19ee1 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -24,6 +24,9 @@ all: adapt datagen adapt: $(ZSTD_FILES) adapt.c $(CC) $(FLAGS) $^ -o $@ +adapt-debug: $(ZSTD_FILES) adapt.c + $(CC) $(FLAGS) -DDEBUG_MODE=2 $^ -o adapt + datagen : $(PRGDIR)/datagen.c datagencli.c $(CC) $(FLAGS) $^ -o $@$(EXT) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index fac93d27..d1fe753c 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -29,7 +29,12 @@ #define CONVERGENCE_LOWER_BOUND 5 #define CLEVEL_DECREASE_COOLDOWN 5 +#ifndef DEBUG_MODE static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; +#else +static int g_displayLevel = DEBUG_MODE; +#endif + static unsigned g_compressionLevel = DEFAULT_COMPRESSION_LEVEL; static UTIL_time_t g_startTime; static size_t g_streamedSize = 0; @@ -949,7 +954,6 @@ static void help() PRINT("\n"); PRINT("Options:\n"); PRINT(" -oFILE : specify the output file name\n"); - PRINT(" -v : display debug information\n"); PRINT(" -i# : provide initial compression level\n"); PRINT(" -h : display help/information\n"); PRINT(" -f : force the compression level to stay constant\n"); @@ -984,9 +988,6 @@ int main(int argCount, const char* argv[]) argument += 2; outFilename = argument; break; - case 'v': - g_displayLevel++; - break; case 'i': argument += 2; g_compressionLevel = readU32FromChar(&argument); From 0882cd1981dd59dff606d379a4cf294785a5594a Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 14:26:55 -0700 Subject: [PATCH 124/145] progress bar -- don't print num jobs, time elapsed shown in seconds --- contrib/adaptive-compression/adapt.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index d1fe753c..098f6fc9 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -600,7 +600,7 @@ static void* compressionThread(void* arg) return arg; } -static void displayProgress(unsigned jobDoneID, unsigned cLevel, unsigned last) +static void displayProgress(unsigned cLevel, unsigned last) { if (!g_useProgressBar) return; UTIL_time_t currTime; @@ -608,7 +608,7 @@ static void displayProgress(unsigned jobDoneID, unsigned cLevel, unsigned last) double const timeElapsed = (double)(UTIL_getSpanTimeMicro(g_ticksPerSecond, g_startTime, currTime) / 1000.0); double const sizeMB = (double)g_streamedSize / (1 << 20); double const avgCompRate = sizeMB * 1000 / timeElapsed; - fprintf(stderr, "\r| %4u jobs completed | Current Compresion Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB | Avg Compression Rate: %6.2f MB/s |", jobDoneID, cLevel, timeElapsed, sizeMB, avgCompRate); + fprintf(stderr, "\r| Comp. Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB | Avg Comp. Rate: %6.2f MB/s |", cLevel, timeElapsed/1000.0, sizeMB, avgCompRate); if (last) { fprintf(stderr, "\n"); } @@ -681,7 +681,7 @@ static void* outputThread(void* arg) } } DEBUG(3, "finished job write %u\n", currJob); - displayProgress(currJob, ctx->compressionLevel, job->lastJobPlusOne == currJob + 1); + displayProgress(ctx->compressionLevel, job->lastJobPlusOne == currJob + 1); DEBUG(3, "locking job write mutex\n"); pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); ctx->jobWriteID++; From 5cfbf609a4d6afeaf7b2f101f9bf838b1d20f9e5 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 14:31:48 -0700 Subject: [PATCH 125/145] removed old debug statements no longer being used --- contrib/adaptive-compression/adapt.c | 35 ---------------------------- 1 file changed, 35 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 098f6fc9..722ae243 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -504,10 +504,6 @@ static void* compressionThread(void* arg) ctx->compressionCompletion = 0; pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); - DEBUG(3, "compressionThread(): continuing after job ready\n"); - DEBUG(3, "DICTIONARY ENDED\n"); - DEBUG(3, "%.*s", (int)job->src.size, (char*)job->src.start); - /* adapt compression level */ if (currJob) adaptCompressionLevel(ctx); @@ -520,15 +516,12 @@ static void* compressionThread(void* arg) size_t remaining = job->src.size; size_t srcPos = 0; size_t dstPos = 0; - DEBUG(3, "cLevel used: %u\n", cLevel); - DEBUG(3, "compression level used: %u\n", cLevel); /* reset compressed size */ job->compressedSize = 0; DEBUG(2, "calling ZSTD_compressBegin()\n"); /* begin compression */ { size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); - DEBUG(3, "useDictSize: %zu, job->dictSize: %zu\n", useDictSize, job->dictSize); size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start + job->dictSize - useDictSize, useDictSize, cLevel); size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); @@ -542,8 +535,6 @@ static void* compressionThread(void* arg) do { size_t const actualBlockSize = MIN(remaining, compressionBlockSize); - DEBUG(3, "remaining: %zu\n", remaining); - DEBUG(3, "actualBlockSize: %zu\n", actualBlockSize); /* continue compression */ if (currJob != 0 || blockNum != 0) { /* not first block of first job flush/overwrite the frame header */ @@ -557,9 +548,6 @@ static void* compressionThread(void* arg) ZSTD_invalidateRepCodes(ctx->cctx); } { - DEBUG(3, "write out ending: %d\n", (job->lastJobPlusOne == currJob + 1) && (remaining == actualBlockSize)); - DEBUG(3, "lastJobPlusOne %u\n", job->lastJobPlusOne); - DEBUG(3, "compressionBlockSize %zu\n", compressionBlockSize); size_t const ret = (job->lastJobPlusOne == currJob + 1 && remaining == actualBlockSize) ? ZSTD_compressEnd (ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, actualBlockSize) : ZSTD_compressContinue(ctx->cctx, job->dst.start + dstPos, job->dst.capacity - dstPos, job->src.start + job->dictSize + srcPos, actualBlockSize); @@ -577,7 +565,6 @@ static void* compressionThread(void* arg) /* update completion */ pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); ctx->compressionCompletion = 1 - (double)remaining/job->src.size; - DEBUG(3, "compression completion %u %f\n", currJob, ctx->compressionCompletion); pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); } } while (remaining != 0); @@ -585,13 +572,10 @@ static void* compressionThread(void* arg) } pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); ctx->jobCompressedID++; - DEBUG(3, "signaling for job %u\n", currJob); pthread_cond_broadcast(&ctx->jobCompressed_cond.pCond); pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); - DEBUG(3, "finished job compression %u\n", currJob); if (job->lastJobPlusOne == currJob + 1 || ctx->threadError) { /* finished compressing all jobs */ - DEBUG(3, "all jobs finished compressing\n"); break; } DEBUG(2, "finished compressing job %u\n", currJob); @@ -633,7 +617,6 @@ static void* outputThread(void* arg) pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); /* write thread is waiting on compression thread */ ctx->writeWaitCompressionCompletion = ctx->compressionCompletion; - DEBUG(3, "write thread waiting : writeWaitCompressionCompletion %f\n", ctx->writeWaitCompressionCompletion); DEBUG(2, "writer thread waiting for nextJob: %u, writeWaitCompressionCompletion %f\n", currJob, ctx->writeWaitCompressionCompletion); pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); @@ -645,7 +628,6 @@ static void* outputThread(void* arg) ctx->writeCompletion = 0; pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); - DEBUG(3, "outputThread(): continuing after job compressed\n"); { size_t const compressedSize = job->compressedSize; size_t remaining = compressedSize; @@ -668,7 +650,6 @@ static void* outputThread(void* arg) /* update completion variable for writing */ pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); ctx->writeCompletion = 1 - (double)remaining/compressedSize; - DEBUG(3, "write completion %u %f\n", currJob, ctx->writeCompletion); pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); if (remaining == 0) break; @@ -680,18 +661,14 @@ static void* outputThread(void* arg) } } } - DEBUG(3, "finished job write %u\n", currJob); displayProgress(ctx->compressionLevel, job->lastJobPlusOne == currJob + 1); - DEBUG(3, "locking job write mutex\n"); pthread_mutex_lock(&ctx->jobWrite_mutex.pMutex); ctx->jobWriteID++; pthread_cond_signal(&ctx->jobWrite_cond.pCond); pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); - DEBUG(3, "unlocking job write mutex\n"); if (job->lastJobPlusOne == currJob + 1 || ctx->threadError) { /* finished with all jobs */ - DEBUG(3, "all jobs finished writing\n"); pthread_mutex_lock(&ctx->allJobsCompleted_mutex.pMutex); ctx->allJobsCompleted = 1; pthread_cond_signal(&ctx->allJobsCompleted_cond.pCond); @@ -710,7 +687,6 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) unsigned const nextJob = ctx->nextJobID; unsigned const nextJobIndex = nextJob % ctx->numJobs; jobDescription* job = &ctx->jobs[nextJobIndex]; - DEBUG(3, "createCompressionJob(): wait for job write\n"); /* wait until the job has been compressed */ @@ -719,8 +695,6 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); /* creation thread is waiting, take measurement of completion */ ctx->createWaitCompressionCompletion = ctx->compressionCompletion; - DEBUG(3, "creation thread waiting : createWaitCompressionCompletion %f\n", ctx->createWaitCompressionCompletion); - DEBUG(3, "writeCompletion: %f\n", ctx->writeCompletion); DEBUG(2, "create thread waiting for nextJob: %u, createWaitCompressionCompletion %f\n", nextJob, ctx->createWaitCompressionCompletion); pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); @@ -730,9 +704,6 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); ctx->createCompletion = 0; pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); - DEBUG(3, "createCompressionJob(): continuing after job write\n"); - - DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); job->compressionLevel = ctx->compressionLevel; job->src.size = srcSize; job->jobID = nextJob; @@ -745,13 +716,10 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) } job->dictSize = ctx->lastDictSize; - DEBUG(3, "finished job creation %u\n", nextJob); ctx->nextJobID++; - DEBUG(3, "filled: %zu, srcSize: %zu\n", ctx->input.filled, srcSize); /* if not on the last job, reuse data as dictionary in next job */ if (!last) { size_t const oldDictSize = ctx->lastDictSize; - DEBUG(3, "oldDictSize %zu\n", oldDictSize); memcpy(ctx->input.buffer.start, job->src.start + oldDictSize, srcSize); ctx->lastDictSize = srcSize; ctx->input.filled = srcSize; @@ -812,7 +780,6 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA remaining -= ret; pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); ctx->createCompletion = 1 - (double)remaining/((size_t)FILE_CHUNK_SIZE); - DEBUG(3, "create completion %u %f\n", currJob, ctx->createCompletion); pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); } if (remaining != 0 && !feof(srcFile)) { @@ -833,7 +800,6 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA DEBUG(2, "finished creating job %u\n", currJob); currJob++; if (feof(srcFile)) { - DEBUG(3, "THE STREAM OF DATA ENDED %u\n", ctx->nextJobID); break; } } @@ -991,7 +957,6 @@ int main(int argCount, const char* argv[]) case 'i': argument += 2; g_compressionLevel = readU32FromChar(&argument); - DEBUG(3, "g_compressionLevel: %u\n", g_compressionLevel); break; case 'h': help(); From 31a9ed9883f8f4313fb5410a9bce9734ae77328d Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 14:53:40 -0700 Subject: [PATCH 126/145] updated const values, added more comments --- contrib/adaptive-compression/adapt.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 722ae243..9ebe2f9f 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -319,6 +319,7 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) pthread_mutex_unlock(&ctx->allJobsCompleted_mutex.pMutex); } +/* map completion percentages to values for changing compression level */ static unsigned convertCompletionToChange(double completion) { if (completion < 0.05) { @@ -350,11 +351,11 @@ static void adaptCompressionLevel(adaptCCtx* ctx) unsigned const prevCompressionLevel = ctx->compressionLevel; DEBUG(2, "adapting compression level %u\n", ctx->compressionLevel); - /* read and reset completion measurements */ + /* read and reset completion measurements */ pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); - DEBUG(2, "rc %f\n", ctx->createWaitCompressionCompletion); - DEBUG(2, "wc %f\n", ctx->writeWaitCompressionCompletion); + DEBUG(2, "createWaitCompressionCompletion %f\n", ctx->createWaitCompressionCompletion); + DEBUG(2, "writeWaitCompressionCompletion %f\n", ctx->writeWaitCompressionCompletion); createWaitCompressionCompletion = ctx->createWaitCompressionCompletion; writeWaitCompressionCompletion = ctx->writeWaitCompressionCompletion; ctx->createWaitCompressionCompletion = 1; @@ -362,13 +363,13 @@ static void adaptCompressionLevel(adaptCCtx* ctx) pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); - DEBUG(2, "cw %f\n", ctx->compressWaitWriteCompletion); + DEBUG(2, "compressWaitWriteCompletion %f\n", ctx->compressWaitWriteCompletion); compressWaitWriteCompletion = ctx->compressWaitWriteCompletion; ctx->compressWaitWriteCompletion = 1; pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); - DEBUG(2, "cr %f\n", ctx->compressWaitCreateCompletion); + DEBUG(2, "compressWaitCreateCompletion %f\n", ctx->compressWaitCreateCompletion); compressWaitCreateCompletion = ctx->compressWaitCreateCompletion; ctx->compressWaitCreateCompletion = 1; pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); @@ -439,8 +440,8 @@ static void adaptCompressionLevel(adaptCCtx* ctx) static size_t getUseableDictSize(unsigned compressionLevel) { ZSTD_parameters params = ZSTD_getParams(compressionLevel, 0, 0); - unsigned overlapLog = compressionLevel >= (unsigned)ZSTD_maxCLevel() ? 0 : 3; - size_t overlapSize = 1 << (params.cParams.windowLog - overlapLog); + unsigned const overlapLog = compressionLevel >= (unsigned)ZSTD_maxCLevel() ? 0 : 3; + size_t const overlapSize = 1 << (params.cParams.windowLog - overlapLog); return overlapSize; } @@ -450,7 +451,7 @@ static void* compressionThread(void* arg) unsigned currJob = 0; for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; - jobDescription* job = &ctx->jobs[currJobIndex]; + jobDescription* const job = &ctx->jobs[currJobIndex]; DEBUG(2, "starting compression for job %u\n", currJob); { @@ -610,7 +611,7 @@ static void* outputThread(void* arg) unsigned currJob = 0; for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; - jobDescription* job = &ctx->jobs[currJobIndex]; + jobDescription* const job = &ctx->jobs[currJobIndex]; DEBUG(2, "starting write for job %u\n", currJob); pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { @@ -686,7 +687,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) { unsigned const nextJob = ctx->nextJobID; unsigned const nextJobIndex = nextJob % ctx->numJobs; - jobDescription* job = &ctx->jobs[nextJobIndex]; + jobDescription* const job = &ctx->jobs[nextJobIndex]; /* wait until the job has been compressed */ @@ -736,6 +737,7 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadArg* otArg) { + /* early error check to exit */ if (!ctx || !srcFile || !otArg) { return 1; } From 9a132707aff6ece4b77864db7078c5c123b7b421 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 15:26:26 -0700 Subject: [PATCH 127/145] changing time units to seconds --- contrib/adaptive-compression/adapt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 9ebe2f9f..91950222 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -593,7 +593,7 @@ static void displayProgress(unsigned cLevel, unsigned last) double const timeElapsed = (double)(UTIL_getSpanTimeMicro(g_ticksPerSecond, g_startTime, currTime) / 1000.0); double const sizeMB = (double)g_streamedSize / (1 << 20); double const avgCompRate = sizeMB * 1000 / timeElapsed; - fprintf(stderr, "\r| Comp. Level: %2u | Time Elapsed: %5.0f ms | Data Size: %7.1f MB | Avg Comp. Rate: %6.2f MB/s |", cLevel, timeElapsed/1000.0, sizeMB, avgCompRate); + fprintf(stderr, "\r| Comp. Level: %2u | Time Elapsed: %5.0f s | Data Size: %7.1f MB | Avg Comp. Rate: %6.2f MB/s |", cLevel, timeElapsed/1000.0, sizeMB, avgCompRate); if (last) { fprintf(stderr, "\n"); } From 8dbb07d822ec998fe92e485ab09deb6d26b6f6d5 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 16:03:43 -0700 Subject: [PATCH 128/145] updated progress bar with better representation of time, added const --- contrib/adaptive-compression/adapt.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 91950222..ead68bf4 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -439,7 +439,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) static size_t getUseableDictSize(unsigned compressionLevel) { - ZSTD_parameters params = ZSTD_getParams(compressionLevel, 0, 0); + ZSTD_parameters const params = ZSTD_getParams(compressionLevel, 0, 0); unsigned const overlapLog = compressionLevel >= (unsigned)ZSTD_maxCLevel() ? 0 : 3; size_t const overlapSize = 1 << (params.cParams.windowLog - overlapLog); return overlapSize; @@ -447,7 +447,7 @@ static size_t getUseableDictSize(unsigned compressionLevel) static void* compressionThread(void* arg) { - adaptCCtx* ctx = (adaptCCtx*)arg; + adaptCCtx* const ctx = (adaptCCtx*)arg; unsigned currJob = 0; for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; @@ -593,7 +593,7 @@ static void displayProgress(unsigned cLevel, unsigned last) double const timeElapsed = (double)(UTIL_getSpanTimeMicro(g_ticksPerSecond, g_startTime, currTime) / 1000.0); double const sizeMB = (double)g_streamedSize / (1 << 20); double const avgCompRate = sizeMB * 1000 / timeElapsed; - fprintf(stderr, "\r| Comp. Level: %2u | Time Elapsed: %5.0f s | Data Size: %7.1f MB | Avg Comp. Rate: %6.2f MB/s |", cLevel, timeElapsed/1000.0, sizeMB, avgCompRate); + fprintf(stderr, "\r| Comp. Level: %2u | Time Elapsed: %7.2f s | Data Size: %7.1f MB | Avg Comp. Rate: %6.2f MB/s |", cLevel, timeElapsed/1000.0, sizeMB, avgCompRate); if (last) { fprintf(stderr, "\n"); } @@ -638,7 +638,6 @@ static void* outputThread(void* arg) return arg; } { - // size_t const writeSize = fwrite(job->dst.start, 1, compressedSize, dstFile); size_t const blockSize = MAX(compressedSize >> 7, 1 << 10); size_t pos = 0; for ( ; ; ) { From 7cc74e0b27fbf6db0644d14b9861231c5f7486d8 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 16:55:16 -0700 Subject: [PATCH 129/145] adding more to readme --- contrib/adaptive-compression/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/README.md b/contrib/adaptive-compression/README.md index 1d261337..09923d19 100644 --- a/contrib/adaptive-compression/README.md +++ b/contrib/adaptive-compression/README.md @@ -1,2 +1,8 @@ -ZSTD_adapt is a compression tool targeted at optimizing performance across network connections. The tool aims at sensing network speeds and adapting compression level based on network or pipe speeds. -In many scenarios, using ZSTD without a properly adjusted compression level results in a pipe bottleneck, or a compressed file that could have been reduced in size. +###Summary + +`adapt` is a new compression tool targeted at optimizing performance across network connections. The tool aims at sensing network speeds and adapting compression level based on network or pipe speeds. +In situations where the compression level does not appropriately match the network/pipe speed, the compression may be bottlenecking the entire pipeline or the files may not be compressed as much as they potentially could be, therefore losing efficiency. It also becomes quite impractical to manually measure and set compression level, therefore the tool does it for you. + +###Using `adapt` + +In order to build and use the tool, you can simply run `make adapt` in the `adaptive-compression` directory under `contrib`. This will generate an executable available for use. From 0b18d21e03b9dd161cd27b8fd616dccb7f602e30 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Tue, 25 Jul 2017 17:47:02 -0700 Subject: [PATCH 130/145] building on readme, added another help tip in the menu --- contrib/adaptive-compression/README.md | 17 +++++++++++++++++ contrib/adaptive-compression/adapt.c | 1 + 2 files changed, 18 insertions(+) diff --git a/contrib/adaptive-compression/README.md b/contrib/adaptive-compression/README.md index 09923d19..fadb071f 100644 --- a/contrib/adaptive-compression/README.md +++ b/contrib/adaptive-compression/README.md @@ -6,3 +6,20 @@ In situations where the compression level does not appropriately match the netwo ###Using `adapt` In order to build and use the tool, you can simply run `make adapt` in the `adaptive-compression` directory under `contrib`. This will generate an executable available for use. + +###Options +`-oFILE` : write output to `FILE` + +`-i#` : provide initial compression level + +`-h` : display help/information + +`-f` : force the compression level to stay constant + +`-c` : force write to `stdout` + +`-p` : hide progress bar + +`-q` : quiet mode -- do not show progress bar or other information + +###Benchmarking / Test results diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index ead68bf4..16701e75 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -924,6 +924,7 @@ static void help() PRINT(" -i# : provide initial compression level\n"); PRINT(" -h : display help/information\n"); PRINT(" -f : force the compression level to stay constant\n"); + PRINT(" -c : force write to stdout\n"); PRINT(" -p : hide progress bar\n"); PRINT(" -q : quiet mode -- do not show progress bar or other information\n"); } From be92a38d6a76ed987c44a4a364161190a8715b36 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 26 Jul 2017 10:05:10 -0700 Subject: [PATCH 131/145] decrease completion requirements for change, move create thread wait, merge cases where compression thread should wait --- contrib/adaptive-compression/adapt.c | 54 +++++++++++----------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 16701e75..1633b6bf 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -322,10 +322,10 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) /* map completion percentages to values for changing compression level */ static unsigned convertCompletionToChange(double completion) { - if (completion < 0.05) { + if (completion < 0.1) { return 2; } - else if (completion < 0.5) { + else if (completion < 0.65) { return 1; } else { @@ -396,9 +396,9 @@ static void adaptCompressionLevel(adaptCCtx* ctx) DEBUG(2, "create or write threads waiting on compression, tried to decrease compression level by %u\n\n", boundChange); } - else if (1-compressWaitWriteCompletion > threshold) { + else if (1-compressWaitWriteCompletion > threshold || 1-compressWaitCreateCompletion > threshold) { /* compress waiting on write */ - double const completion = compressWaitWriteCompletion; + double const completion = MIN(compressWaitWriteCompletion, compressWaitCreateCompletion); unsigned const change = convertCompletionToChange(completion); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); if (ctx->convergenceCounter > CONVERGENCE_LOWER_BOUND && boundChange != 0) { @@ -406,26 +406,11 @@ static void adaptCompressionLevel(adaptCCtx* ctx) } else if (boundChange != 0) { ctx->compressionLevel += boundChange; + ctx->cooldown = 0; ctx->convergenceCounter = 1; } - DEBUG(2, "compress waiting on write, tried to increase compression level by %u\n\n", boundChange); - } - else if (1-compressWaitCreateCompletion > threshold) { - /* compress waiting on create*/ - /* use compressWaitCreateCompletion */ - double const completion = compressWaitCreateCompletion; - unsigned const change = convertCompletionToChange(completion); - unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); - if (ctx->convergenceCounter > CONVERGENCE_LOWER_BOUND && boundChange != 0) { - ctx->convergenceCounter = 0; - } - else if (boundChange != 0) { - ctx->compressionLevel += boundChange; - ctx->convergenceCounter = 1; - } - - DEBUG(2, "compression waiting on create, tried to increase compression level by %u\n\n", boundChange); + DEBUG(2, "compress waiting on write or create, tried to increase compression level by %u\n\n", boundChange); } if (ctx->compressionLevel == prevCompressionLevel) { @@ -689,17 +674,6 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) jobDescription* const job = &ctx->jobs[nextJobIndex]; - /* wait until the job has been compressed */ - pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); - while (nextJob - ctx->jobCompressedID >= ctx->numJobs && !ctx->threadError) { - pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); - /* creation thread is waiting, take measurement of completion */ - ctx->createWaitCompressionCompletion = ctx->compressionCompletion; - DEBUG(2, "create thread waiting for nextJob: %u, createWaitCompressionCompletion %f\n", nextJob, ctx->createWaitCompressionCompletion); - pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); - pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); - } - pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); /* reset create completion */ pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); ctx->createCompletion = 0; @@ -767,8 +741,22 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA size_t pos = 0; size_t const readBlockSize = 1 << 15; size_t remaining = FILE_CHUNK_SIZE; - + unsigned const nextJob = ctx->nextJobID; DEBUG(2, "starting creation of job %u\n", currJob); + + + /* wait until the job has been compressed */ + pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); + while (nextJob - ctx->jobCompressedID >= ctx->numJobs && !ctx->threadError) { + pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); + /* creation thread is waiting, take measurement of completion */ + ctx->createWaitCompressionCompletion = ctx->compressionCompletion; + DEBUG(2, "create thread waiting for nextJob: %u, createWaitCompressionCompletion %f\n", nextJob, ctx->createWaitCompressionCompletion); + pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); + pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); + } + pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); + while (remaining != 0 && !feof(srcFile)) { size_t const ret = fread(ctx->input.buffer.start + ctx->input.filled + pos, 1, readBlockSize, srcFile); if (ret != readBlockSize && !feof(srcFile)) { From 305d5ee70f41cb89f2d373abf703b09dfedcda61 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 26 Jul 2017 10:20:29 -0700 Subject: [PATCH 132/145] change to >= convergence counter --- contrib/adaptive-compression/adapt.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 1633b6bf..d7566080 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -384,33 +384,37 @@ static void adaptCompressionLevel(adaptCCtx* ctx) double const completion = MAX(createWaitCompressionCompletion, writeWaitCompressionCompletion); unsigned const change = convertCompletionToChange(completion); unsigned const boundChange = MIN(change, ctx->compressionLevel - 1); - if (ctx->convergenceCounter > CONVERGENCE_LOWER_BOUND && boundChange != 0) { + if (ctx->convergenceCounter >= CONVERGENCE_LOWER_BOUND && boundChange != 0) { /* reset convergence counter, might have been a spike */ ctx->convergenceCounter = 0; + DEBUG(2, "convergence counter reset, no change applied\n"); } else if (boundChange != 0) { ctx->compressionLevel -= boundChange; ctx->cooldown = CLEVEL_DECREASE_COOLDOWN; ctx->convergenceCounter = 1; - } - DEBUG(2, "create or write threads waiting on compression, tried to decrease compression level by %u\n\n", boundChange); + DEBUG(2, "create or write threads waiting on compression, tried to decrease compression level by %u\n\n", boundChange); + } } else if (1-compressWaitWriteCompletion > threshold || 1-compressWaitCreateCompletion > threshold) { /* compress waiting on write */ double const completion = MIN(compressWaitWriteCompletion, compressWaitCreateCompletion); unsigned const change = convertCompletionToChange(completion); unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); - if (ctx->convergenceCounter > CONVERGENCE_LOWER_BOUND && boundChange != 0) { + if (ctx->convergenceCounter >= CONVERGENCE_LOWER_BOUND && boundChange != 0) { + /* reset convergence counter, might have been a spike */ ctx->convergenceCounter = 0; + DEBUG(2, "convergence counter reset, no change applied\n"); } else if (boundChange != 0) { ctx->compressionLevel += boundChange; ctx->cooldown = 0; ctx->convergenceCounter = 1; + + DEBUG(2, "compress waiting on write or create, tried to increase compression level by %u\n\n", boundChange); } - DEBUG(2, "compress waiting on write or create, tried to increase compression level by %u\n\n", boundChange); } if (ctx->compressionLevel == prevCompressionLevel) { From a959cc881a65b4ef250b4f963924c04c6760be0d Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 26 Jul 2017 10:34:48 -0700 Subject: [PATCH 133/145] moved reset of completion to right after wait --- contrib/adaptive-compression/adapt.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index d7566080..237bb430 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -678,10 +678,6 @@ static int createCompressionJob(adaptCCtx* ctx, size_t srcSize, int last) jobDescription* const job = &ctx->jobs[nextJobIndex]; - /* reset create completion */ - pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); - ctx->createCompletion = 0; - pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); job->compressionLevel = ctx->compressionLevel; job->src.size = srcSize; job->jobID = nextJob; @@ -761,6 +757,11 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA } pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); + /* reset create completion */ + pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); + ctx->createCompletion = 0; + pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); + while (remaining != 0 && !feof(srcFile)) { size_t const ret = fread(ctx->input.buffer.start + ctx->input.filled + pos, 1, readBlockSize, srcFile); if (ret != readBlockSize && !feof(srcFile)) { From 6c1c1242fce69018710d30c785285f0cbaa850cc Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 26 Jul 2017 14:29:59 -0700 Subject: [PATCH 134/145] set the window log value before performing compression --- contrib/adaptive-compression/adapt.c | 16 ++++++++++------ contrib/adaptive-compression/test-correctness.sh | 5 +++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 237bb430..5ff2e418 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -513,12 +513,16 @@ static void* compressionThread(void* arg) { size_t const useDictSize = MIN(getUseableDictSize(cLevel), job->dictSize); size_t const dictModeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceRawDict, 1); - size_t const initError = ZSTD_compressBegin_usingDict(ctx->cctx, job->src.start + job->dictSize - useDictSize, useDictSize, cLevel); - size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); - if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { - DISPLAY("Error: something went wrong while starting compression\n"); - signalErrorToThreads(ctx); - return arg; + ZSTD_parameters params = ZSTD_getParams(cLevel, 0, useDictSize); + params.cParams.windowLog = 23; + { + size_t const initError = ZSTD_compressBegin_advanced(ctx->cctx, job->src.start + job->dictSize - useDictSize, useDictSize, params, 0); + size_t const windowSizeError = ZSTD_setCCtxParameter(ctx->cctx, ZSTD_p_forceWindow, 1); + if (ZSTD_isError(dictModeError) || ZSTD_isError(initError) || ZSTD_isError(windowSizeError)) { + DISPLAY("Error: something went wrong while starting compression\n"); + signalErrorToThreads(ctx); + return arg; + } } } DEBUG(2, "finished with ZSTD_compressBegin()\n"); diff --git a/contrib/adaptive-compression/test-correctness.sh b/contrib/adaptive-compression/test-correctness.sh index 86d39ee4..8ae6604a 100755 --- a/contrib/adaptive-compression/test-correctness.sh +++ b/contrib/adaptive-compression/test-correctness.sh @@ -237,4 +237,9 @@ rm tmp* zstd -d tmp.zst -o tmp2 diff -s -q tmp tmp2 rm tmp* + +echo -e "\ncorrectness tests -- window size test" +./datagen -s39 -g1GB | pv -L 25m -q | ./adapt -i1 | pv -q > tmp.zst +zstd -d tmp.zst +rm tmp* make clean From 715f36ca8165d3d89f974567c883dfb0e2bc4e36 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 26 Jul 2017 15:52:15 -0700 Subject: [PATCH 135/145] added definitions for conversion constants, moved forced compression check to top of adaptCompressionLevel, used ZSTD_BLOCKSIZE_MAX --- contrib/adaptive-compression/adapt.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 5ff2e418..05da8b7f 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -28,6 +28,8 @@ #define MAX_COMPRESSION_LEVEL_CHANGE 2 #define CONVERGENCE_LOWER_BOUND 5 #define CLEVEL_DECREASE_COOLDOWN 5 +#define CHANGE_BY_TWO_THRESHOLD 0.1 +#define CHANGE_BY_ONE_THRESHOLD 0.65 #ifndef DEBUG_MODE static int g_displayLevel = DEFAULT_DISPLAY_LEVEL; @@ -322,10 +324,10 @@ static void waitUntilAllJobsCompleted(adaptCCtx* ctx) /* map completion percentages to values for changing compression level */ static unsigned convertCompletionToChange(double completion) { - if (completion < 0.1) { + if (completion < CHANGE_BY_TWO_THRESHOLD) { return 2; } - else if (completion < 0.65) { + else if (completion < CHANGE_BY_ONE_THRESHOLD) { return 1; } else { @@ -350,6 +352,13 @@ static void adaptCompressionLevel(adaptCCtx* ctx) double const threshold = 0.00001; unsigned const prevCompressionLevel = ctx->compressionLevel; + + if (g_forceCompressionLevel) { + ctx->compressionLevel = g_compressionLevel; + return; + } + + DEBUG(2, "adapting compression level %u\n", ctx->compressionLevel); /* read and reset completion measurements */ @@ -420,10 +429,6 @@ static void adaptCompressionLevel(adaptCCtx* ctx) if (ctx->compressionLevel == prevCompressionLevel) { ctx->convergenceCounter++; } - - if (g_forceCompressionLevel) { - ctx->compressionLevel = g_compressionLevel; - } } static size_t getUseableDictSize(unsigned compressionLevel) @@ -500,7 +505,7 @@ static void* compressionThread(void* arg) DEBUG(2, "job %u compressed with level %u\n", currJob, ctx->compressionLevel); /* compress the data */ { - size_t const compressionBlockSize = 1 << 17; /* 128 KB */ + size_t const compressionBlockSize = ZSTD_BLOCKSIZE_MAX; /* 128 KB */ unsigned const cLevel = ctx->compressionLevel; unsigned blockNum = 0; size_t remaining = job->src.size; From ab5a78547e62c9a80936e998f4db94fe43e6d232 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 26 Jul 2017 16:40:05 -0700 Subject: [PATCH 136/145] fix leaky abstraction regarding measuring completion --- contrib/adaptive-compression/adapt.c | 57 ++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 05da8b7f..a16ab40c 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -367,20 +367,16 @@ static void adaptCompressionLevel(adaptCCtx* ctx) DEBUG(2, "writeWaitCompressionCompletion %f\n", ctx->writeWaitCompressionCompletion); createWaitCompressionCompletion = ctx->createWaitCompressionCompletion; writeWaitCompressionCompletion = ctx->writeWaitCompressionCompletion; - ctx->createWaitCompressionCompletion = 1; - ctx->writeWaitCompressionCompletion = 1; pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); DEBUG(2, "compressWaitWriteCompletion %f\n", ctx->compressWaitWriteCompletion); compressWaitWriteCompletion = ctx->compressWaitWriteCompletion; - ctx->compressWaitWriteCompletion = 1; pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); DEBUG(2, "compressWaitCreateCompletion %f\n", ctx->compressWaitCreateCompletion); compressWaitCreateCompletion = ctx->compressWaitCreateCompletion; - ctx->compressWaitCreateCompletion = 1; pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); DEBUG(2, "convergence counter: %u\n", ctx->convergenceCounter); @@ -462,22 +458,28 @@ static void* compressionThread(void* arg) pthread_mutex_unlock(&ctx->jobWrite_mutex.pMutex); + pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); if (willWaitForCreate) { DEBUG(2, "compression will wait for create on job %u\n", currJob); - pthread_mutex_lock(&ctx->createCompletion_mutex.pMutex); ctx->compressWaitCreateCompletion = ctx->createCompletion; DEBUG(2, "create completion %f\n", ctx->compressWaitCreateCompletion); - pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); } + else { + ctx->compressWaitCreateCompletion = 1; + } + pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); + pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); if (willWaitForWrite) { DEBUG(2, "compression will wait for write on job %u\n", currJob); - pthread_mutex_lock(&ctx->writeCompletion_mutex.pMutex); ctx->compressWaitWriteCompletion = ctx->writeCompletion; DEBUG(2, "write completion %f\n", ctx->compressWaitWriteCompletion); - pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); } + else { + ctx->compressWaitWriteCompletion = 1; + } + pthread_mutex_unlock(&ctx->writeCompletion_mutex.pMutex); } @@ -610,14 +612,27 @@ static void* outputThread(void* arg) for ( ; ; ) { unsigned const currJobIndex = currJob % ctx->numJobs; jobDescription* const job = &ctx->jobs[currJobIndex]; + unsigned willWaitForCompress = 0; DEBUG(2, "starting write for job %u\n", currJob); + pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); - while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { - pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); + if (currJob + 1 > ctx->jobCompressedID) willWaitForCompress = 1; + pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); + + + pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); + if (willWaitForCompress) { /* write thread is waiting on compression thread */ ctx->writeWaitCompressionCompletion = ctx->compressionCompletion; DEBUG(2, "writer thread waiting for nextJob: %u, writeWaitCompressionCompletion %f\n", currJob, ctx->writeWaitCompressionCompletion); - pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); + } + else { + ctx->writeWaitCompressionCompletion = 1; + } + pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); + + pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); + while (currJob + 1 > ctx->jobCompressedID && !ctx->threadError) { pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); @@ -751,17 +766,27 @@ static int performCompression(adaptCCtx* ctx, FILE* const srcFile, outputThreadA size_t const readBlockSize = 1 << 15; size_t remaining = FILE_CHUNK_SIZE; unsigned const nextJob = ctx->nextJobID; + unsigned willWaitForCompress = 0; DEBUG(2, "starting creation of job %u\n", currJob); + pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); + if (nextJob - ctx->jobCompressedID >= ctx->numJobs) willWaitForCompress = 1; + pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); + + pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); + if (willWaitForCompress) { + /* creation thread is waiting, take measurement of completion */ + ctx->createWaitCompressionCompletion = ctx->compressionCompletion; + DEBUG(2, "create thread waiting for nextJob: %u, createWaitCompressionCompletion %f\n", nextJob, ctx->createWaitCompressionCompletion); + } + else { + ctx->createWaitCompressionCompletion = 1; + } + pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); /* wait until the job has been compressed */ pthread_mutex_lock(&ctx->jobCompressed_mutex.pMutex); while (nextJob - ctx->jobCompressedID >= ctx->numJobs && !ctx->threadError) { - pthread_mutex_lock(&ctx->compressionCompletion_mutex.pMutex); - /* creation thread is waiting, take measurement of completion */ - ctx->createWaitCompressionCompletion = ctx->compressionCompletion; - DEBUG(2, "create thread waiting for nextJob: %u, createWaitCompressionCompletion %f\n", nextJob, ctx->createWaitCompressionCompletion); - pthread_mutex_unlock(&ctx->compressionCompletion_mutex.pMutex); pthread_cond_wait(&ctx->jobCompressed_cond.pCond, &ctx->jobCompressed_mutex.pMutex); } pthread_mutex_unlock(&ctx->jobCompressed_mutex.pMutex); From 2320e7378af2c62e576d4ce0fa4296580172177b Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Wed, 26 Jul 2017 17:02:47 -0700 Subject: [PATCH 137/145] remove unused variable, add documentation for context fields --- contrib/adaptive-compression/adapt.c | 38 +++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index a16ab40c..5cf3b970 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -24,7 +24,6 @@ #define MAX_PATH 256 #define DEFAULT_DISPLAY_LEVEL 1 #define DEFAULT_COMPRESSION_LEVEL 6 -#define DEFAULT_ADAPT_PARAM 0 #define MAX_COMPRESSION_LEVEL_CHANGE 2 #define CONVERGENCE_LOWER_BOUND 5 #define CLEVEL_DECREASE_COOLDOWN 5 @@ -81,20 +80,54 @@ typedef struct { unsigned numJobs; unsigned nextJobID; unsigned threadError; + + /* + * JobIDs for the next jobs to be created, compressed, and written + */ unsigned jobReadyID; unsigned jobCompressedID; unsigned jobWriteID; unsigned allJobsCompleted; - unsigned adaptParam; + + /* + * counter for how many jobs in a row the compression level has not changed + * if the counter becomes >= CONVERGENCE_LOWER_BOUND, the next time the + * compression level tries to change (by non-zero amount) resets the counter + * to 1 and does not apply the change + */ unsigned convergenceCounter; + + /* + * cooldown counter in order to prevent rapid successive decreases in compression level + * whenever compression level is decreased, cooldown is set to CLEVEL_DECREASE_COOLDOWN + * whenever adaptCompressionLevel() is called and cooldown != 0, it is decremented + * as long as cooldown != 0, the compression level cannot be decreased + */ unsigned cooldown; + + /* + * XWaitYCompletion + * Range from 0.0 to 1.0 + * if the value is not 1.0, then this implies that thread X waited on thread Y to finish + * and thread Y was XWaitYCompletion finished at the time of the wait (i.e. compressWaitWriteCompletion=0.5 + * implies that the compression thread waited on the write thread and it was only 50% finished writing a job) + */ double createWaitCompressionCompletion; double compressWaitCreateCompletion; double compressWaitWriteCompletion; double writeWaitCompressionCompletion; + + /* + * Completion values + * Range from 0.0 to 1.0 + * Jobs are divided into mini-chunks in order to measure completion + * these values are updated each time a thread finishes its operation on the + * mini-chunk (i.e. finishes writing out, compressing, etc. this mini-chunk). + */ double compressionCompletion; double writeCompletion; double createCompletion; + mutex_t jobCompressed_mutex; cond_t jobCompressed_cond; mutex_t jobReady_mutex; @@ -254,7 +287,6 @@ static int initCCtx(adaptCCtx* ctx, unsigned numJobs) ctx->nextJobID = 0; ctx->threadError = 0; ctx->allJobsCompleted = 0; - ctx->adaptParam = DEFAULT_ADAPT_PARAM; ctx->cctx = ZSTD_createCCtx(); if (!ctx->cctx) { From ff54fced641bac595a4120108b563ee523650adc Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 28 Jul 2017 15:30:46 -0700 Subject: [PATCH 138/145] patched style errors, add ability to bound compression level variation --- contrib/adaptive-compression/adapt.c | 46 ++++++++++++++++++---------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 5cf3b970..5e26a0db 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -42,6 +42,8 @@ static size_t g_streamedSize = 0; static unsigned g_useProgressBar = 1; static UTIL_freq_t g_ticksPerSecond; static unsigned g_forceCompressionLevel = 0; +static unsigned g_minCLevel = 1; +static unsigned g_maxCLevel = 22; typedef struct { void* start; @@ -420,7 +422,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) /* use whichever one waited less because it was slower */ double const completion = MAX(createWaitCompressionCompletion, writeWaitCompressionCompletion); unsigned const change = convertCompletionToChange(completion); - unsigned const boundChange = MIN(change, ctx->compressionLevel - 1); + unsigned const boundChange = ctx->compressionLevel >= g_minCLevel ? MIN(change, ctx->compressionLevel - g_minCLevel) : 0; if (ctx->convergenceCounter >= CONVERGENCE_LOWER_BOUND && boundChange != 0) { /* reset convergence counter, might have been a spike */ ctx->convergenceCounter = 0; @@ -438,7 +440,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) /* compress waiting on write */ double const completion = MIN(compressWaitWriteCompletion, compressWaitCreateCompletion); unsigned const change = convertCompletionToChange(completion); - unsigned const boundChange = MIN(change, ZSTD_maxCLevel() - ctx->compressionLevel); + unsigned const boundChange = g_maxCLevel >= ctx->compressionLevel ? MIN(change, g_maxCLevel - ctx->compressionLevel) : 0; if (ctx->convergenceCounter >= CONVERGENCE_LOWER_BOUND && boundChange != 0) { /* reset convergence counter, might have been a spike */ ctx->convergenceCounter = 0; @@ -620,17 +622,19 @@ static void* compressionThread(void* arg) static void displayProgress(unsigned cLevel, unsigned last) { if (!g_useProgressBar) return; - UTIL_time_t currTime; - UTIL_getTime(&currTime); - double const timeElapsed = (double)(UTIL_getSpanTimeMicro(g_ticksPerSecond, g_startTime, currTime) / 1000.0); - double const sizeMB = (double)g_streamedSize / (1 << 20); - double const avgCompRate = sizeMB * 1000 / timeElapsed; - fprintf(stderr, "\r| Comp. Level: %2u | Time Elapsed: %7.2f s | Data Size: %7.1f MB | Avg Comp. Rate: %6.2f MB/s |", cLevel, timeElapsed/1000.0, sizeMB, avgCompRate); - if (last) { - fprintf(stderr, "\n"); - } - else { - fflush(stderr); + { + UTIL_time_t currTime; + UTIL_getTime(&currTime); + double const timeElapsed = (double)(UTIL_getSpanTimeMicro(g_ticksPerSecond, g_startTime, currTime) / 1000.0); + double const sizeMB = (double)g_streamedSize / (1 << 20); + double const avgCompRate = sizeMB * 1000 / timeElapsed; + fprintf(stderr, "\r| Comp. Level: %2u | Time Elapsed: %7.2f s | Data Size: %7.1f MB | Avg Comp. Rate: %6.2f MB/s |", cLevel, timeElapsed/1000.0, sizeMB, avgCompRate); + if (last) { + fprintf(stderr, "\n"); + } + else { + fflush(stderr); + } } } @@ -928,9 +932,9 @@ static int freeFileCompressionResources(fcResources* fcr) static int compressFilename(const char* const srcFilename, const char* const dstFilenameOrNull) { int ret = 0; + fcResources fcr = createFileCompressionResources(srcFilename, dstFilenameOrNull); UTIL_getTime(&g_startTime); g_streamedSize = 0; - fcResources fcr = createFileCompressionResources(srcFilename, dstFilenameOrNull); ret |= performCompression(fcr.ctx, fcr.srcFile, fcr.otArg); ret |= freeFileCompressionResources(&fcr); return ret; @@ -973,7 +977,7 @@ static unsigned readU32FromChar(const char** stringPtr) return result; } -static void help() +static void help(void) { PRINT("Usage:\n"); PRINT(" ./multi [options] [file(s)]\n"); @@ -986,6 +990,8 @@ static void help() PRINT(" -c : force write to stdout\n"); PRINT(" -p : hide progress bar\n"); PRINT(" -q : quiet mode -- do not show progress bar or other information\n"); + PRINT(" -l# : provide lower bound for compression level\n"); + PRINT(" -u# : provide upper bound for compression level\n"); } /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) @@ -993,10 +999,10 @@ int main(int argCount, const char* argv[]) const char* outFilename = NULL; const char** filenameTable = (const char**)malloc(argCount*sizeof(const char*)); unsigned filenameIdx = 0; - filenameTable[0] = stdinmark; unsigned forceStdout = 0; int ret = 0; int argNum; + filenameTable[0] = stdinmark; UTIL_initTimer(&g_ticksPerSecond); @@ -1036,6 +1042,14 @@ int main(int argCount, const char* argv[]) g_useProgressBar = 0; g_displayLevel = 0; break; + case 'l': + argument += 2; + g_minCLevel = readU32FromChar(&argument); + break; + case 'u': + argument += 2; + g_maxCLevel = readU32FromChar(&argument); + break; default: DISPLAY("Error: invalid argument provided\n"); ret = 1; From 0f4cb67b0050503aeb89a8c91e738c3d685cf89a Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 28 Jul 2017 15:55:02 -0700 Subject: [PATCH 139/145] add tests for compression bounds, fix another warning --- contrib/adaptive-compression/adapt.c | 5 +++-- contrib/adaptive-compression/test-correctness.sh | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 5e26a0db..55f4148f 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -621,10 +621,11 @@ static void* compressionThread(void* arg) static void displayProgress(unsigned cLevel, unsigned last) { + + UTIL_time_t currTime; + UTIL_getTime(&currTime); if (!g_useProgressBar) return; { - UTIL_time_t currTime; - UTIL_getTime(&currTime); double const timeElapsed = (double)(UTIL_getSpanTimeMicro(g_ticksPerSecond, g_startTime, currTime) / 1000.0); double const sizeMB = (double)g_streamedSize / (1 << 20); double const avgCompRate = sizeMB * 1000 / timeElapsed; diff --git a/contrib/adaptive-compression/test-correctness.sh b/contrib/adaptive-compression/test-correctness.sh index 8ae6604a..3bea867b 100755 --- a/contrib/adaptive-compression/test-correctness.sh +++ b/contrib/adaptive-compression/test-correctness.sh @@ -242,4 +242,11 @@ echo -e "\ncorrectness tests -- window size test" ./datagen -s39 -g1GB | pv -L 25m -q | ./adapt -i1 | pv -q > tmp.zst zstd -d tmp.zst rm tmp* + +echo -e "\ncorrectness tests -- testing bounds" +./datagen -s40 -g1GB | pv -L 25m -q | ./adapt -i1 -u4 | pv -q > tmp.zst +rm tmp* + +./datagen -s41 -g1GB | ./adapt -i14 -l4 > tmp.zst +rm tmp* make clean From 4d904ac800920286fc6d22b28e132577572cd4de Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 28 Jul 2017 16:12:58 -0700 Subject: [PATCH 140/145] add flags for multithreading --- contrib/adaptive-compression/Makefile | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index 9bc19ee1..0e081a67 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -6,6 +6,15 @@ ZSTDCOMP_FILES := $(ZSTDDIR)/compress/*.c ZSTDDECOMP_FILES := $(ZSTDDIR)/decompress/*.c ZSTD_FILES := $(ZSTDDECOMP_FILES) $(ZSTDCOMMON_FILES) $(ZSTDCOMP_FILES) +# Define *.exe as extension for Windows systems +ifneq (,$(filter Windows%,$(OS))) +EXT =.exe +MULTITHREAD_LD = +else +EXT = +MULTITHREAD_LD = -pthread +endif + DEBUGFLAGS= -g -DZSTD_DEBUG=1 CPPFLAGS += -I$(ZSTDDIR) -I$(ZSTDDIR)/common -I$(ZSTDDIR)/compress \ -I$(ZSTDDIR)/dictBuilder -I$(ZSTDDIR)/deprecated -I$(PRGDIR) @@ -17,7 +26,7 @@ CFLAGS += -Wall -Wextra -Wcast-qual -Wcast-align -Wshadow \ -Wredundant-decls CFLAGS += $(DEBUGFLAGS) CFLAGS += $(MOREFLAGS) -FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) +FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(MULTITHREAD_LD) all: adapt datagen From 51788225db8d3a2fac58614d72e0dd918fa380fb Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 28 Jul 2017 17:27:36 -0700 Subject: [PATCH 141/145] remove exe extension from makefile, reinclude pthread flag --- contrib/adaptive-compression/Makefile | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index 0e081a67..b1c498c9 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -6,15 +6,7 @@ ZSTDCOMP_FILES := $(ZSTDDIR)/compress/*.c ZSTDDECOMP_FILES := $(ZSTDDIR)/decompress/*.c ZSTD_FILES := $(ZSTDDECOMP_FILES) $(ZSTDCOMMON_FILES) $(ZSTDCOMP_FILES) -# Define *.exe as extension for Windows systems -ifneq (,$(filter Windows%,$(OS))) -EXT =.exe -MULTITHREAD_LD = -else -EXT = -MULTITHREAD_LD = -pthread -endif - +MULTITHREAD_LDFLAGS = -pthread DEBUGFLAGS= -g -DZSTD_DEBUG=1 CPPFLAGS += -I$(ZSTDDIR) -I$(ZSTDDIR)/common -I$(ZSTDDIR)/compress \ -I$(ZSTDDIR)/dictBuilder -I$(ZSTDDIR)/deprecated -I$(PRGDIR) @@ -26,7 +18,7 @@ CFLAGS += -Wall -Wextra -Wcast-qual -Wcast-align -Wshadow \ -Wredundant-decls CFLAGS += $(DEBUGFLAGS) CFLAGS += $(MOREFLAGS) -FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(MULTITHREAD_LD) +FLAGS = $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(MULTITHREAD_LDFLAGS) all: adapt datagen From cb9af53e773c1fd1df9765028c2ffec636838158 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 28 Jul 2017 17:28:25 -0700 Subject: [PATCH 142/145] delete empty line --- contrib/adaptive-compression/adapt.c | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 55f4148f..755b7e97 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -621,7 +621,6 @@ static void* compressionThread(void* arg) static void displayProgress(unsigned cLevel, unsigned last) { - UTIL_time_t currTime; UTIL_getTime(&currTime); if (!g_useProgressBar) return; From e22b60cb76a09af2dc0365bca2ee20437003f2db Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Fri, 28 Jul 2017 17:46:51 -0700 Subject: [PATCH 143/145] removed ternary operation, added assert statement, check to make sure initial compression level is within bounds --- contrib/adaptive-compression/adapt.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 755b7e97..3a57c372 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -414,6 +414,8 @@ static void adaptCompressionLevel(adaptCCtx* ctx) pthread_mutex_unlock(&ctx->createCompletion_mutex.pMutex); DEBUG(2, "convergence counter: %u\n", ctx->convergenceCounter); + assert(g_minCLevel <= ctx->compressionLevel && g_maxCLevel >= ctx->compressionLevel); + /* adaptation logic */ if (ctx->cooldown) ctx->cooldown--; @@ -422,7 +424,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) /* use whichever one waited less because it was slower */ double const completion = MAX(createWaitCompressionCompletion, writeWaitCompressionCompletion); unsigned const change = convertCompletionToChange(completion); - unsigned const boundChange = ctx->compressionLevel >= g_minCLevel ? MIN(change, ctx->compressionLevel - g_minCLevel) : 0; + unsigned const boundChange = MIN(change, ctx->compressionLevel - g_minCLevel); if (ctx->convergenceCounter >= CONVERGENCE_LOWER_BOUND && boundChange != 0) { /* reset convergence counter, might have been a spike */ ctx->convergenceCounter = 0; @@ -440,7 +442,7 @@ static void adaptCompressionLevel(adaptCCtx* ctx) /* compress waiting on write */ double const completion = MIN(compressWaitWriteCompletion, compressWaitCreateCompletion); unsigned const change = convertCompletionToChange(completion); - unsigned const boundChange = g_maxCLevel >= ctx->compressionLevel ? MIN(change, g_maxCLevel - ctx->compressionLevel) : 0; + unsigned const boundChange = MIN(change, g_maxCLevel - ctx->compressionLevel); if (ctx->convergenceCounter >= CONVERGENCE_LOWER_BOUND && boundChange != 0) { /* reset convergence counter, might have been a spike */ ctx->convergenceCounter = 0; @@ -1000,6 +1002,7 @@ int main(int argCount, const char* argv[]) const char** filenameTable = (const char**)malloc(argCount*sizeof(const char*)); unsigned filenameIdx = 0; unsigned forceStdout = 0; + unsigned providedInitialCLevel = 0; int ret = 0; int argNum; filenameTable[0] = stdinmark; @@ -1024,6 +1027,7 @@ int main(int argCount, const char* argv[]) case 'i': argument += 2; g_compressionLevel = readU32FromChar(&argument); + providedInitialCLevel = 1; break; case 'h': help(); @@ -1062,6 +1066,20 @@ int main(int argCount, const char* argv[]) filenameTable[filenameIdx++] = argument; } + /* check initial, max, and min compression levels */ + { + unsigned const minMaxInconsistent = g_minCLevel > g_maxCLevel; + unsigned const initialNotInRange = g_minCLevel > g_compressionLevel || g_maxCLevel < g_compressionLevel; + if (minMaxInconsistent || (initialNotInRange && providedInitialCLevel)) { + DISPLAY("Error: provided compression level parameters are invalid\n"); + ret = 1; + goto _main_exit; + } + else if (initialNotInRange) { + g_compressionLevel = g_minCLevel; + } + } + /* error checking with number of files */ if (filenameIdx > 1 && (outFilename != NULL && strcmp(outFilename, stdoutmark))) { DISPLAY("Error: multiple input files provided, cannot use specified output file\n"); From f60cd3f99bccc482d89c1afac73b329a2d9adb29 Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 31 Jul 2017 09:47:09 -0700 Subject: [PATCH 144/145] print defaults and range, remove EXT --- contrib/adaptive-compression/Makefile | 2 +- contrib/adaptive-compression/adapt.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index b1c498c9..87b61b41 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -29,7 +29,7 @@ adapt-debug: $(ZSTD_FILES) adapt.c $(CC) $(FLAGS) -DDEBUG_MODE=2 $^ -o adapt datagen : $(PRGDIR)/datagen.c datagencli.c - $(CC) $(FLAGS) $^ -o $@$(EXT) + $(CC) $(FLAGS) $^ -o $@ test-adapt-correctness: datagen adapt @./test-correctness.sh diff --git a/contrib/adaptive-compression/adapt.c b/contrib/adaptive-compression/adapt.c index 3a57c372..5cec227e 100644 --- a/contrib/adaptive-compression/adapt.c +++ b/contrib/adaptive-compression/adapt.c @@ -986,14 +986,14 @@ static void help(void) PRINT("\n"); PRINT("Options:\n"); PRINT(" -oFILE : specify the output file name\n"); - PRINT(" -i# : provide initial compression level\n"); + PRINT(" -i# : provide initial compression level -- default %d, must be in the range [L, U] where L and U are bound values (see below for defaults)\n", DEFAULT_COMPRESSION_LEVEL); PRINT(" -h : display help/information\n"); PRINT(" -f : force the compression level to stay constant\n"); PRINT(" -c : force write to stdout\n"); PRINT(" -p : hide progress bar\n"); PRINT(" -q : quiet mode -- do not show progress bar or other information\n"); - PRINT(" -l# : provide lower bound for compression level\n"); - PRINT(" -u# : provide upper bound for compression level\n"); + PRINT(" -l# : provide lower bound for compression level -- default 1\n"); + PRINT(" -u# : provide upper bound for compression level -- default 22\n"); } /* return 0 if successful, else return error */ int main(int argCount, const char* argv[]) From 9ea7df03de87a581b3f3be1eb6e555aece56888c Mon Sep 17 00:00:00 2001 From: Paul Cruz Date: Mon, 31 Jul 2017 11:04:17 -0700 Subject: [PATCH 145/145] add install target in makefile --- contrib/adaptive-compression/Makefile | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/contrib/adaptive-compression/Makefile b/contrib/adaptive-compression/Makefile index 87b61b41..c64fce95 100644 --- a/contrib/adaptive-compression/Makefile +++ b/contrib/adaptive-compression/Makefile @@ -46,3 +46,31 @@ clean: @$(RM) -f tests/*.zst @$(RM) -f tests/tmp* @echo "finished cleaning" + +#----------------------------------------------------------------------------- +# make install is validated only for Linux, OSX, BSD, Hurd and Solaris targets +#----------------------------------------------------------------------------- +ifneq (,$(filter $(shell uname),Linux Darwin GNU/kFreeBSD GNU OpenBSD FreeBSD NetBSD DragonFly SunOS)) + +ifneq (,$(filter $(shell uname),SunOS)) +INSTALL ?= ginstall +else +INSTALL ?= install +endif + +PREFIX ?= /usr/local +DESTDIR ?= +BINDIR ?= $(PREFIX)/bin + +INSTALL_PROGRAM ?= $(INSTALL) -m 755 + +install: adapt + @echo Installing binaries + @$(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR)/ + @$(INSTALL_PROGRAM) adapt $(DESTDIR)$(BINDIR)/zstd-adaptive + @echo zstd-adaptive installation completed + +uninstall: + @$(RM) $(DESTDIR)$(BINDIR)/zstd-adaptive + @echo zstd-adaptive programs successfully uninstalled +endif