[PATCH v1 4/6] tools/libs/guest: adaptively disable LZ4 on poor compression ratio
Marcus Granado <[email protected]>
| Newsgroups | org.xenproject.lists.xen-devel |
|---|---|
| Message-ID | <[email protected]> |
If a VM's memory contain mostly incompressible page content, avoid wasting CPU on both sender and receiver. Add a per-iteration ratio tracker so that, after each precopy iteration, the average compression ratio across all batches of that iteration is computed; if it is worse than XC_SR_COMPRESS_ITER_THRESHOLD (90%), ctx->compression is downgraded to XC_SR_COMPRESS_NONE for the remainder of the migration. Signed-off-by: Marcus Granado <[email protected]> --- tools/libs/guest/xg_sr_common.h | 18 ++++++ tools/libs/guest/xg_sr_save.c | 109 ++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/tools/libs/guest/xg_sr_common.h b/tools/libs/guest/xg_sr_common.h index ccb02511ec..2b5b2ce26a 100644 --- a/tools/libs/guest/xg_sr_common.h +++ b/tools/libs/guest/xg_sr_common.h @@ -269,6 +269,24 @@ struct xc_sr_context unsigned long nr_deferred_pages; xc_hypercall_buffer_t dirty_bitmap_hbuf; + /* + * Compression ratio tracking per iteration. Accumulated by + * the compression function for each batch and evaluated at + * the end of every precopy iteration to decide whether to + * disable compression for the remaining iterations + */ + uint64_t compress_raw_total; /* uncompressed, this iter */ + uint64_t compress_out_total; /* compressed, this iter */ + + /* + * Cumulative compression totals across the whole save (not + * reset per iteration). Used for the final compression summary + * logged during teardown, remain valid even if adaptive + * disabling turned compression off partway through + */ + uint64_t compress_raw_all_total; /* uncompressed, all iters */ + uint64_t compress_out_all_total; /* compressed, all iters */ + #ifdef HAVE_LZ4 /* * Persistent LZ4 output buffer (avoids repeated malloc/free), diff --git a/tools/libs/guest/xg_sr_save.c b/tools/libs/guest/xg_sr_save.c index 8f661f0168..dbbcddf6fd 100644 --- a/tools/libs/guest/xg_sr_save.c +++ b/tools/libs/guest/xg_sr_save.c @@ -19,6 +19,15 @@ */ #define XC_SR_COMPRESS_BATCH_THRESHOLD 97 +/* + * Per-iteration compression ratio threshold (percentage). After each + * precopy iteration, the average compression ratio across all batches is + * calculated. If it exceeds this value, compression is disabled for all + * remaining iterations, as the data is not compressible enough to + * justify the CPU cost. Set to >= 100 to disable this adaptive behaviour + */ +#define XC_SR_COMPRESS_ITER_THRESHOLD 90 + /* * Writes an Image header and Domain header into the stream. */ @@ -89,6 +98,99 @@ static int write_checkpoint_record(struct xc_sr_context *ctx) return write_record(ctx, &checkpoint); } +/* + * Reset accumulators for compression ratio per iteration. Called at the + * start of each precopy iteration so that the ratio reflects only the + * current iteration's batches + */ +static void compress_stats_reset(struct xc_sr_context *ctx) +{ + ctx->save.compress_raw_total = 0; + ctx->save.compress_out_total = 0; +} + +/* + * Accumulate compression statistics for a single batch. Called from + * the compression function after every LZ4_compress_default() (or any + * future compressor) so the totals per iteration stay up to date + */ +static inline void compress_stats_accumulate(struct xc_sr_context *ctx, + size_t raw_len, + size_t compressed_len) +{ + ctx->save.compress_raw_total += raw_len; + ctx->save.compress_out_total += compressed_len; + ctx->save.compress_raw_all_total += raw_len; + ctx->save.compress_out_all_total += compressed_len; +} + +static void cleanup_compression(struct xc_sr_context *ctx); + +/* + * Evaluate the compression ratio per iteration. If the average ratio + * across all batches in the completed iteration exceeds the threshold, + * the data is not compressible enough to justify the CPU cost, then + * disable compression for all remaining iterations + */ +static void compress_stats_evaluate(struct xc_sr_context *ctx, + unsigned int iteration) +{ + xc_interface *xch = ctx->xch; + unsigned long ratio_permil; + + if ( ctx->compression == XC_SR_COMPRESS_NONE ) + return; + + if ( ctx->save.compress_raw_total == 0 ) + return; + + ratio_permil = (unsigned long)(ctx->save.compress_out_total * 1000 / + ctx->save.compress_raw_total); + + IPRINTF("Iteration %u: compressed %lu -> %lu bytes " + "(%lu.%lu%% average ratio)", + iteration, + (unsigned long)ctx->save.compress_raw_total, + (unsigned long)ctx->save.compress_out_total, + ratio_permil / 10, ratio_permil % 10); + + if ( ctx->save.compress_out_total * 100 >= + ctx->save.compress_raw_total * XC_SR_COMPRESS_ITER_THRESHOLD ) + { + IPRINTF("Iteration %u: average compression ratio %lu.%lu%% " + "(>= %u%% threshold) - disabling compression for " + "remaining iterations", + iteration, ratio_permil / 10, ratio_permil % 10, + XC_SR_COMPRESS_ITER_THRESHOLD); + cleanup_compression(ctx); + ctx->compression = XC_SR_COMPRESS_NONE; + } +} + +/* + * Log a final compression summary covering the whole save. Called once + * during teardown. compress_*_all_total accumulate across all iterations + * (never reset), so this is meaningful even if adaptive disabling turned + * compression off partway through + */ +static void compress_stats_summary(struct xc_sr_context *ctx) +{ + xc_interface *xch = ctx->xch; + unsigned long ratio_permil; + + if ( ctx->save.compress_raw_all_total == 0 ) + return; + + ratio_permil = (unsigned long)(ctx->save.compress_out_all_total * 1000 / + ctx->save.compress_raw_all_total); + + IPRINTF("Compression summary: compressed %lu -> %lu bytes " + "(%lu.%lu%% average ratio) over the whole save", + (unsigned long)ctx->save.compress_raw_all_total, + (unsigned long)ctx->save.compress_out_all_total, + ratio_permil / 10, ratio_permil % 10); +} + #ifdef HAVE_LZ4 /* * Allocate LZ4 compression buffers during setup_compression() @@ -196,6 +298,8 @@ static bool lz4_compress_batch(struct xc_sr_context *ctx, unsigned int nr_pages, compressed_len = off; } + compress_stats_accumulate(ctx, raw_len, compressed_len); + /* * Decline: if compression didn't shrink the data enough, send * uncompressed page data instead. This avoids transmitting extra @@ -702,6 +806,8 @@ static int send_memory_live(struct xc_sr_context *ctx) policy_decision = precopy_policy(*policy_stats, data); x++; + compress_stats_reset(ctx); + if ( stats.dirty_count > 0 && policy_decision != XGS_POLICY_ABORT ) { rc = update_progress_string(ctx, &progress_str); @@ -713,6 +819,8 @@ static int send_memory_live(struct xc_sr_context *ctx) goto out; } + compress_stats_evaluate(ctx, x); + if ( policy_decision != XGS_POLICY_CONTINUE_PRECOPY ) break; @@ -1055,6 +1163,7 @@ static void cleanup(struct xc_sr_context *ctx) free(ctx->save.deferred_pages); free(ctx->save.batch_pfns); + compress_stats_summary(ctx); cleanup_compression(ctx); } -- 2.43.0