[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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.