Hi Peter On 2025-04-24 18:07, Peter Xu wrote: > The preempt mode postcopy has been introduced for a while. From latency > POV, it should always win the vanilla postcopy. > > However there's one thing missing when preempt mode is enabled right now, > which is the spatial locality hint when there're page requests from the > destination side. > > In vanilla postcopy, as long as a page request was unqueued, it will update > the PSS of the precopy background stream, so that after a page request the > background thread will move the pages after whatever was requested. It's > pretty much a natural behavior when there's only one channel anyway, and > one scanner to send the pages. > > Preempt mode didn't follow that, because preempt mode has its own channel > and its own PSS (which doesn't linearly scan the guest memory, but > dedicated to resolve page requested from destination). So the page request > process and the background migration process are completely separate. > > This patch adds the hint explicitly for preempt mode. With that, whenever > the preempt mode receives a page request on the source, it will service the > remote page fault in the return path, then it'll provide a hint to the > background thread so that we'll start sending the pages right after the > requested ones in the background, assuming the follow up pages have a > higher chance to be accessed later. > > NOTE: since the background migration thread and return path thread run > completely concurrently, it doesn't always mean the hint will be applied > every single time. For example, it's possible that the return path thread > receives multiple page requests in a row without the background thread > getting the chance to consume one. In such case, the preempt thread only > provide the hint if the previous hint has been consumed. After all, > there's no point queuing hints when we only have one linear scanner. > > This could measureably improve the simple sequential memory access pattern > during postcopy (when preempt is on). For random accesses, I can measure a > slight increase of remote page fault latency from ~500us -> ~600us, that > could be a trade-off to have such hint mechanism, and after all that's > still greatly improved comparing to vanilla postcopy on random (~10ms). > > The patch is verified by our QE team in a video streaming test case, to > reduce the pause of the video from ~1min to a few seconds when switching > over to postcopy with preempt mode. > > Tested-by: Xiaohui Li <xiao...@redhat.com> > Signed-off-by: Peter Xu <pet...@redhat.com> > --- > migration/ram.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 96 insertions(+), 1 deletion(-) > > diff --git a/migration/ram.c b/migration/ram.c > index 424df6d9f1..21d2f87ff1 100644 > --- a/migration/ram.c > +++ b/migration/ram.c > @@ -91,6 +91,36 @@ > > XBZRLECacheStats xbzrle_counters; > > +/* > + * This structure locates a specific location of a guest page. In QEMU, > + * it's described in a tuple of (ramblock, offset). > + */ > +struct PageLocation { > + RAMBlock *block; > + unsigned long offset; > +}; > +typedef struct PageLocation PageLocation; > + > +/** > + * PageLocationHint: describes a hint to a page location > + * > + * @valid set if the hint is vaild and to be consumed > + * @location: the hint content > + * > + * In postcopy preempt mode, the urgent channel may provide hints to the > + * background channel, so that QEMU source can try to migrate whatever is > + * right after the requested urgent pages. > + * > + * This is based on the assumption that the VM (already running on the > + * destination side) tends to access the memory with spatial locality. > + * This is also the default behavior of vanilla postcopy (preempt off). > + */ > +struct PageLocationHint { > + bool valid; > + PageLocation location; > +}; > +typedef struct PageLocationHint PageLocationHint; > + > /* used by the search for pages to send */ > struct PageSearchStatus { > /* The migration channel used for a specific host page */ > @@ -395,6 +425,13 @@ struct RAMState { > * RAM migration. > */ > unsigned int postcopy_bmap_sync_requested; > + /* > + * Page hint during postcopy when preempt mode is on. Return path > + * thread sets it, while background migration thread consumes it. > + * > + * Protected by @bitmap_mutex. > + */ > + PageLocationHint page_hint; > }; > typedef struct RAMState RAMState; > > @@ -2039,6 +2076,21 @@ static void pss_host_page_finish(PageSearchStatus *pss) > pss->host_page_start = pss->host_page_end = 0; > } > > +static void ram_page_hint_update(RAMState *rs, PageSearchStatus *pss) > +{ > + PageLocationHint *hint = &rs->page_hint; > + > + /* If there's a pending hint not consumed, don't bother */ > + if (hint->valid) { > + return; > + } > + > + /* Provide a hint to the background stream otherwise */ > + hint->location.block = pss->block; > + hint->location.offset = pss->page; > + hint->valid = true; > +} > + > /* > * Send an urgent host page specified by `pss'. Need to be called with > * bitmap_mutex held. > @@ -2084,6 +2136,7 @@ out: > /* For urgent requests, flush immediately if sent */ > if (sent) { > qemu_fflush(pss->pss_channel); > + ram_page_hint_update(rs, pss); > } > return ret; > } > @@ -2171,6 +2224,30 @@ static int ram_save_host_page(RAMState *rs, > PageSearchStatus *pss) > return (res < 0 ? res : pages); > } > > +static bool ram_page_hint_valid(RAMState *rs) > +{ > + /* There's only page hint during postcopy preempt mode */ > + if (!postcopy_preempt_active()) { > + return false; > + } > + > + return rs->page_hint.valid; > +} > + > +static void ram_page_hint_collect(RAMState *rs, RAMBlock **block, > + unsigned long *page) > +{ > + PageLocationHint *hint = &rs->page_hint; > + > + assert(hint->valid); > + > + *block = hint->location.block; > + *page = hint->location.offset; > + > + /* Mark the hint consumed */ > + hint->valid = false; > +} > + > /** > * ram_find_and_save_block: finds a dirty page and sends it to f > * > @@ -2187,6 +2264,8 @@ static int ram_save_host_page(RAMState *rs, > PageSearchStatus *pss) > static int ram_find_and_save_block(RAMState *rs) > { > PageSearchStatus *pss = &rs->pss[RAM_CHANNEL_PRECOPY]; > + unsigned long next_page; > + RAMBlock *next_block; > int pages = 0; > > /* No dirty page as there is zero RAM */ > @@ -2206,7 +2285,14 @@ static int ram_find_and_save_block(RAMState *rs) > rs->last_page = 0; > } > > - pss_init(pss, rs->last_seen_block, rs->last_page); > + if (ram_page_hint_valid(rs)) { > + ram_page_hint_collect(rs, &next_block, &next_page); > + } else { > + next_block = rs->last_seen_block; > + next_page = rs->last_page; > + } > + > + pss_init(pss, next_block, next_page); > > while (true){ > if (!get_queued_page(rs, pss)) { > @@ -2339,6 +2425,13 @@ static void ram_save_cleanup(void *opaque) > ram_state_cleanup(rsp); > } > > +static void ram_page_hint_reset(PageLocationHint *hint) > +{ > + hint->location.block = NULL; > + hint->location.offset = 0; > + hint->valid = false; > +} > + > static void ram_state_reset(RAMState *rs) > { > int i; > @@ -2351,6 +2444,8 @@ static void ram_state_reset(RAMState *rs) > rs->last_page = 0; > rs->last_version = ram_list.version; > rs->xbzrle_started = false; > + > + ram_page_hint_reset(&rs->page_hint); > } > > #define MAX_WAIT 50 /* ms, half buffered_file limit */ > -- > 2.48.1 >
Reviewed-by: Juraj Marcin <jmar...@redhat.com>