Cache the notdirty_write region lookup: 3.1% faster loading
notdirty_write called uc->memory_mapping() on every guest write, before
testing whether the region is executable, and used the result for that one
bit. A probe counted over a million such calls per 3 seconds with
exec_region=0 - the full find_memory_mapping -> address_space_translate ->
flatview_translate chain running and the answer then discarded.
The map is static once CreateConfiguredEngine has mapped its five spans, so
a 512-entry direct-mapped page -> MemoryRegion cache removes all but cold
misses.
interleaved A/B, engine start to first OnCarLoaded
cache on: 31.27 / 31.34 / 31.22 s mean 31.28
cache off: 32.27 / 32.12 / 32.44 s mean 32.28
3.1%, ranges fully separate, spreads under 0.35s. The profile confirms the
mechanism rather than just the outcome: flatview_translate 4.46% -> 3.17%,
find_memory_mapping 3.02% -> 2.36%.
THE FIRST VERSION OF THIS CACHE WAS SLOWER, and the measurement is why it
was caught. It used `static __thread`, which on Android resolves through
emulated TLS: __emutls_get_address appeared at 8.11% and pthread_getspecific
at 3.07% - together more than the 7.5% being removed. Storage now lives in
uc_struct, which is already per-thread here (one uc_engine per host thread),
so no TLS is involved. Do not reintroduce __thread on this path.
INVARIANT: the cache assumes mappings and permissions are fixed after engine
setup. Anyone changing them at runtime must clear mr_cache_ready.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -361,6 +361,17 @@ struct uc_struct {
|
||||
// linked lists containing hooks per type
|
||||
struct list hook[UC_HOOK_MAX];
|
||||
struct list hooks_to_del;
|
||||
// NFSMW arm64-poc: page -> MemoryRegion cache for notdirty_write, which
|
||||
// otherwise calls memory_mapping() on EVERY guest write just to test one
|
||||
// permission bit (see cputlb.c). Lives here rather than in a __thread
|
||||
// variable because this struct is already per-thread in that engine (one
|
||||
// uc_engine per host thread) AND because emulated TLS on Android turned
|
||||
// out to cost more than the lookup it replaced: __emutls_get_address
|
||||
// 8.11% + pthread_getspecific 3.07%, measured, against the 7.5% being
|
||||
// removed.
|
||||
uint64_t mr_cache_tag[512];
|
||||
void *mr_cache_val[512];
|
||||
int mr_cache_ready;
|
||||
int hooks_count[UC_HOOK_MAX];
|
||||
|
||||
// hook to count number of instructions for uc_emu_start()
|
||||
|
||||
+26
-23
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#define UC_MR_CACHE_ENTRIES 512
|
||||
#define UC_MR_CACHE_ENABLED 0
|
||||
#define UC_MR_CACHE_ENABLED 1
|
||||
#include "cpu.h"
|
||||
#include "exec/exec-all.h"
|
||||
#include "exec/memory.h"
|
||||
@@ -1220,36 +1220,39 @@ static void notdirty_write(CPUState *cpu, vaddr mem_vaddr, unsigned size,
|
||||
// permissions at RUNTIME must clear this cache, or stale permissions will
|
||||
// be used. It is per-thread, so no locking is needed.
|
||||
//
|
||||
// NOT YET VERIFIED. Written and built, but the Pixel 6a became
|
||||
// unmeasurable (screen dozing within seconds, NotificationShade holding
|
||||
// focus) and the A9 cannot run this build at all - Android kills it with
|
||||
// "failed to attach / start timeout" because startup work exceeds the
|
||||
// attach deadline on that hardware. Defaults OFF so an unmeasured change
|
||||
// does not reach the hot path. To evaluate: set to 1, then interleaved
|
||||
// A/B with loadtime (engine start -> first OnCarLoaded) plus a profile
|
||||
// check that flatview_translate and find_memory_mapping actually fall.
|
||||
// A profile-share drop alone is NOT a speedup - that has been established
|
||||
// three separate times in this project.
|
||||
// VERIFIED 2026-09-20, interleaved A/B on a rebooted Pixel 6a, engine
|
||||
// start to first OnCarLoaded:
|
||||
// cache on: 31.27 / 31.34 / 31.22 s mean 31.28
|
||||
// cache off: 32.27 / 32.12 / 32.44 s mean 32.28
|
||||
// 3.1% faster, ranges fully separate. Profile confirms the mechanism:
|
||||
// flatview_translate 4.46% -> 3.17%, find_memory_mapping 3.02% -> 2.36%.
|
||||
//
|
||||
// The first attempt at this cache used `static __thread` and was SLOWER,
|
||||
// because emulated TLS on Android routes every access through
|
||||
// __emutls_get_address (8.11%) + pthread_getspecific (3.07%) - more than
|
||||
// the 7.5% it was removing. Hence the storage living in uc_struct, which
|
||||
// is already per-thread here (one uc_engine per host thread). Do not
|
||||
// reintroduce __thread in this path.
|
||||
MemoryRegion *mr;
|
||||
if (!UC_MR_CACHE_ENABLED) {
|
||||
mr = cpu->uc->memory_mapping(cpu->uc, tlbe->paddr | (mem_vaddr & ~TARGET_PAGE_MASK));
|
||||
} else {
|
||||
struct uc_struct *ucc = cpu->uc;
|
||||
target_ulong page = tlbe->paddr | (mem_vaddr & ~TARGET_PAGE_MASK);
|
||||
target_ulong tag = page >> TARGET_PAGE_BITS;
|
||||
uint64_t tag = (uint64_t)(page >> TARGET_PAGE_BITS);
|
||||
unsigned idx = (unsigned)(tag & (UC_MR_CACHE_ENTRIES - 1));
|
||||
static __thread target_ulong mr_cache_tag[UC_MR_CACHE_ENTRIES];
|
||||
static __thread MemoryRegion *mr_cache_val[UC_MR_CACHE_ENTRIES];
|
||||
static __thread int mr_cache_ready;
|
||||
if (!mr_cache_ready) {
|
||||
for (unsigned i = 0; i < UC_MR_CACHE_ENTRIES; i++) mr_cache_tag[i] = (target_ulong)-1;
|
||||
mr_cache_ready = 1;
|
||||
if (!ucc->mr_cache_ready) {
|
||||
for (unsigned i = 0; i < UC_MR_CACHE_ENTRIES; i++) {
|
||||
ucc->mr_cache_tag[i] = (uint64_t)-1;
|
||||
}
|
||||
ucc->mr_cache_ready = 1;
|
||||
}
|
||||
if (mr_cache_tag[idx] == tag) {
|
||||
mr = mr_cache_val[idx];
|
||||
if (ucc->mr_cache_tag[idx] == tag) {
|
||||
mr = (MemoryRegion *)ucc->mr_cache_val[idx];
|
||||
} else {
|
||||
mr = cpu->uc->memory_mapping(cpu->uc, page);
|
||||
mr_cache_tag[idx] = tag;
|
||||
mr_cache_val[idx] = mr;
|
||||
mr = ucc->memory_mapping(ucc, page);
|
||||
ucc->mr_cache_tag[idx] = tag;
|
||||
ucc->mr_cache_val[idx] = mr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user