powerpc/64s/hash: Add a SLB preload cache
When switching processes, currently all user SLBEs are cleared, and a few (exec_base, pc, and stack) are preloaded. In trivial testing with small apps, this tends to miss the heap and low 256MB segments, and it will also miss commonly accessed segments on large memory workloads. Add a simple round-robin preload cache that just inserts the last SLB miss into the head of the cache and preloads those at context switch time. Every 256 context switches, the oldest entry is removed from the cache to shrink the cache and require fewer slbmte if they are unused. Much more could go into this, including into the SLB entry reclaim side to track some LRU information etc, which would require a study of large memory workloads. But this is a simple thing we can do now that is an obvious win for common workloads. With the full series, process switching speed on the context_switch benchmark on POWER9/hash (with kernel speculation security masures disabled) increases from 140K/s to 178K/s (27%). POWER8 does not change much (within 1%), it's unclear why it does not see a big gain like POWER9. Booting to busybox init with 256MB segments has SLB misses go down from 945 to 69, and with 1T segments 900 to 21. These could almost all be eliminated by preloading a bit more carefully with ELF binary loading. Signed-off-by: Nicholas Piggin <npiggin@gmail.com> Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
This commit is contained in:
Родитель
2e1626744e
Коммит
89ca4e126a
|
@ -273,6 +273,7 @@ struct thread_struct {
|
||||||
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
|
#endif /* CONFIG_HAVE_HW_BREAKPOINT */
|
||||||
struct arch_hw_breakpoint hw_brk; /* info on the hardware breakpoint */
|
struct arch_hw_breakpoint hw_brk; /* info on the hardware breakpoint */
|
||||||
unsigned long trap_nr; /* last trap # on this thread */
|
unsigned long trap_nr; /* last trap # on this thread */
|
||||||
|
u8 load_slb; /* Ages out SLB preload cache entries */
|
||||||
u8 load_fp;
|
u8 load_fp;
|
||||||
#ifdef CONFIG_ALTIVEC
|
#ifdef CONFIG_ALTIVEC
|
||||||
u8 load_vec;
|
u8 load_vec;
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <asm/page.h>
|
#include <asm/page.h>
|
||||||
#include <asm/accounting.h>
|
#include <asm/accounting.h>
|
||||||
|
|
||||||
|
#define SLB_PRELOAD_NR 16U
|
||||||
/*
|
/*
|
||||||
* low level task data.
|
* low level task data.
|
||||||
*/
|
*/
|
||||||
|
@ -44,6 +45,10 @@ struct thread_info {
|
||||||
#if defined(CONFIG_VIRT_CPU_ACCOUNTING_NATIVE) && defined(CONFIG_PPC32)
|
#if defined(CONFIG_VIRT_CPU_ACCOUNTING_NATIVE) && defined(CONFIG_PPC32)
|
||||||
struct cpu_accounting_data accounting;
|
struct cpu_accounting_data accounting;
|
||||||
#endif
|
#endif
|
||||||
|
u8 slb_preload_nr;
|
||||||
|
u8 slb_preload_tail;
|
||||||
|
u32 slb_preload_esid[SLB_PRELOAD_NR];
|
||||||
|
|
||||||
/* low level flags - has atomic operations done on it */
|
/* low level flags - has atomic operations done on it */
|
||||||
unsigned long flags ____cacheline_aligned_in_smp;
|
unsigned long flags ____cacheline_aligned_in_smp;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1719,6 +1719,8 @@ int copy_thread(unsigned long clone_flags, unsigned long usp,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void preload_new_slb_context(unsigned long start, unsigned long sp);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set up a thread for executing a new program
|
* Set up a thread for executing a new program
|
||||||
*/
|
*/
|
||||||
|
@ -1726,6 +1728,10 @@ void start_thread(struct pt_regs *regs, unsigned long start, unsigned long sp)
|
||||||
{
|
{
|
||||||
#ifdef CONFIG_PPC64
|
#ifdef CONFIG_PPC64
|
||||||
unsigned long load_addr = regs->gpr[2]; /* saved by ELF_PLAT_INIT */
|
unsigned long load_addr = regs->gpr[2]; /* saved by ELF_PLAT_INIT */
|
||||||
|
|
||||||
|
#ifdef CONFIG_PPC_BOOK3S_64
|
||||||
|
preload_new_slb_context(start, sp);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1816,6 +1822,7 @@ void start_thread(struct pt_regs *regs, unsigned long start, unsigned long sp)
|
||||||
#ifdef CONFIG_VSX
|
#ifdef CONFIG_VSX
|
||||||
current->thread.used_vsr = 0;
|
current->thread.used_vsr = 0;
|
||||||
#endif
|
#endif
|
||||||
|
current->thread.load_slb = 0;
|
||||||
current->thread.load_fp = 0;
|
current->thread.load_fp = 0;
|
||||||
memset(¤t->thread.fp_state, 0, sizeof(current->thread.fp_state));
|
memset(¤t->thread.fp_state, 0, sizeof(current->thread.fp_state));
|
||||||
current->thread.fp_save_area = NULL;
|
current->thread.fp_save_area = NULL;
|
||||||
|
|
|
@ -53,6 +53,8 @@ int hash__alloc_context_id(void)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(hash__alloc_context_id);
|
EXPORT_SYMBOL_GPL(hash__alloc_context_id);
|
||||||
|
|
||||||
|
void slb_setup_new_exec(void);
|
||||||
|
|
||||||
static int hash__init_new_context(struct mm_struct *mm)
|
static int hash__init_new_context(struct mm_struct *mm)
|
||||||
{
|
{
|
||||||
int index;
|
int index;
|
||||||
|
@ -87,6 +89,8 @@ static int hash__init_new_context(struct mm_struct *mm)
|
||||||
void hash__setup_new_exec(void)
|
void hash__setup_new_exec(void)
|
||||||
{
|
{
|
||||||
slice_setup_new_exec();
|
slice_setup_new_exec();
|
||||||
|
|
||||||
|
slb_setup_new_exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
static int radix__init_new_context(struct mm_struct *mm)
|
static int radix__init_new_context(struct mm_struct *mm)
|
||||||
|
|
|
@ -257,41 +257,119 @@ void slb_vmalloc_update(void)
|
||||||
slb_flush_and_rebolt();
|
slb_flush_and_rebolt();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper function to compare esids. There are four cases to handle.
|
static bool preload_hit(struct thread_info *ti, unsigned long esid)
|
||||||
* 1. The system is not 1T segment size capable. Use the GET_ESID compare.
|
|
||||||
* 2. The system is 1T capable, both addresses are < 1T, use the GET_ESID compare.
|
|
||||||
* 3. The system is 1T capable, only one of the two addresses is > 1T. This is not a match.
|
|
||||||
* 4. The system is 1T capable, both addresses are > 1T, use the GET_ESID_1T macro to compare.
|
|
||||||
*/
|
|
||||||
static inline int esids_match(unsigned long addr1, unsigned long addr2)
|
|
||||||
{
|
{
|
||||||
int esid_1t_count;
|
u8 i;
|
||||||
|
|
||||||
/* System is not 1T segment size capable. */
|
for (i = 0; i < ti->slb_preload_nr; i++) {
|
||||||
if (!mmu_has_feature(MMU_FTR_1T_SEGMENT))
|
u8 idx;
|
||||||
return (GET_ESID(addr1) == GET_ESID(addr2));
|
|
||||||
|
|
||||||
esid_1t_count = (((addr1 >> SID_SHIFT_1T) != 0) +
|
idx = (ti->slb_preload_tail + i) % SLB_PRELOAD_NR;
|
||||||
((addr2 >> SID_SHIFT_1T) != 0));
|
if (esid == ti->slb_preload_esid[idx])
|
||||||
|
return true;
|
||||||
/* both addresses are < 1T */
|
}
|
||||||
if (esid_1t_count == 0)
|
return false;
|
||||||
return (GET_ESID(addr1) == GET_ESID(addr2));
|
|
||||||
|
|
||||||
/* One address < 1T, the other > 1T. Not a match */
|
|
||||||
if (esid_1t_count == 1)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* Both addresses are > 1T. */
|
|
||||||
return (GET_ESID_1T(addr1) == GET_ESID_1T(addr2));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool preload_add(struct thread_info *ti, unsigned long ea)
|
||||||
|
{
|
||||||
|
unsigned long esid;
|
||||||
|
u8 idx;
|
||||||
|
|
||||||
|
if (mmu_has_feature(MMU_FTR_1T_SEGMENT)) {
|
||||||
|
/* EAs are stored >> 28 so 256MB segments don't need clearing */
|
||||||
|
if (ea & ESID_MASK_1T)
|
||||||
|
ea &= ESID_MASK_1T;
|
||||||
|
}
|
||||||
|
|
||||||
|
esid = ea >> SID_SHIFT;
|
||||||
|
|
||||||
|
if (preload_hit(ti, esid))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
idx = (ti->slb_preload_tail + ti->slb_preload_nr) % SLB_PRELOAD_NR;
|
||||||
|
ti->slb_preload_esid[idx] = esid;
|
||||||
|
if (ti->slb_preload_nr == SLB_PRELOAD_NR)
|
||||||
|
ti->slb_preload_tail = (ti->slb_preload_tail + 1) % SLB_PRELOAD_NR;
|
||||||
|
else
|
||||||
|
ti->slb_preload_nr++;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void preload_age(struct thread_info *ti)
|
||||||
|
{
|
||||||
|
if (!ti->slb_preload_nr)
|
||||||
|
return;
|
||||||
|
ti->slb_preload_nr--;
|
||||||
|
ti->slb_preload_tail = (ti->slb_preload_tail + 1) % SLB_PRELOAD_NR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void slb_setup_new_exec(void)
|
||||||
|
{
|
||||||
|
struct thread_info *ti = current_thread_info();
|
||||||
|
struct mm_struct *mm = current->mm;
|
||||||
|
unsigned long exec = 0x10000000;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have no good place to clear the slb preload cache on exec,
|
||||||
|
* flush_thread is about the earliest arch hook but that happens
|
||||||
|
* after we switch to the mm and have aleady preloaded the SLBEs.
|
||||||
|
*
|
||||||
|
* For the most part that's probably okay to use entries from the
|
||||||
|
* previous exec, they will age out if unused. It may turn out to
|
||||||
|
* be an advantage to clear the cache before switching to it,
|
||||||
|
* however.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* preload some userspace segments into the SLB.
|
||||||
|
* Almost all 32 and 64bit PowerPC executables are linked at
|
||||||
|
* 0x10000000 so it makes sense to preload this segment.
|
||||||
|
*/
|
||||||
|
if (!is_kernel_addr(exec)) {
|
||||||
|
if (preload_add(ti, exec))
|
||||||
|
slb_allocate_user(mm, exec);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Libraries and mmaps. */
|
||||||
|
if (!is_kernel_addr(mm->mmap_base)) {
|
||||||
|
if (preload_add(ti, mm->mmap_base))
|
||||||
|
slb_allocate_user(mm, mm->mmap_base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void preload_new_slb_context(unsigned long start, unsigned long sp)
|
||||||
|
{
|
||||||
|
struct thread_info *ti = current_thread_info();
|
||||||
|
struct mm_struct *mm = current->mm;
|
||||||
|
unsigned long heap = mm->start_brk;
|
||||||
|
|
||||||
|
/* Userspace entry address. */
|
||||||
|
if (!is_kernel_addr(start)) {
|
||||||
|
if (preload_add(ti, start))
|
||||||
|
slb_allocate_user(mm, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Top of stack, grows down. */
|
||||||
|
if (!is_kernel_addr(sp)) {
|
||||||
|
if (preload_add(ti, sp))
|
||||||
|
slb_allocate_user(mm, sp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bottom of heap, grows up. */
|
||||||
|
if (heap && !is_kernel_addr(heap)) {
|
||||||
|
if (preload_add(ti, heap))
|
||||||
|
slb_allocate_user(mm, heap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Flush all user entries from the segment table of the current processor. */
|
/* Flush all user entries from the segment table of the current processor. */
|
||||||
void switch_slb(struct task_struct *tsk, struct mm_struct *mm)
|
void switch_slb(struct task_struct *tsk, struct mm_struct *mm)
|
||||||
{
|
{
|
||||||
unsigned long pc = KSTK_EIP(tsk);
|
struct thread_info *ti = task_thread_info(tsk);
|
||||||
unsigned long stack = KSTK_ESP(tsk);
|
u8 i;
|
||||||
unsigned long exec_base;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We need interrupts hard-disabled here, not just soft-disabled,
|
* We need interrupts hard-disabled here, not just soft-disabled,
|
||||||
|
@ -314,7 +392,6 @@ void switch_slb(struct task_struct *tsk, struct mm_struct *mm)
|
||||||
if (!mmu_has_feature(MMU_FTR_NO_SLBIE_B) &&
|
if (!mmu_has_feature(MMU_FTR_NO_SLBIE_B) &&
|
||||||
offset <= SLB_CACHE_ENTRIES) {
|
offset <= SLB_CACHE_ENTRIES) {
|
||||||
unsigned long slbie_data = 0;
|
unsigned long slbie_data = 0;
|
||||||
int i;
|
|
||||||
|
|
||||||
asm volatile("isync" : : : "memory");
|
asm volatile("isync" : : : "memory");
|
||||||
for (i = 0; i < offset; i++) {
|
for (i = 0; i < offset; i++) {
|
||||||
|
@ -354,24 +431,28 @@ void switch_slb(struct task_struct *tsk, struct mm_struct *mm)
|
||||||
get_paca()->slb_used_bitmap = get_paca()->slb_kern_bitmap;
|
get_paca()->slb_used_bitmap = get_paca()->slb_kern_bitmap;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* preload some userspace segments into the SLB.
|
* We gradually age out SLBs after a number of context switches to
|
||||||
* Almost all 32 and 64bit PowerPC executables are linked at
|
* reduce reload overhead of unused entries (like we do with FP/VEC
|
||||||
* 0x10000000 so it makes sense to preload this segment.
|
* reload). Each time we wrap 256 switches, take an entry out of the
|
||||||
|
* SLB preload cache.
|
||||||
*/
|
*/
|
||||||
exec_base = 0x10000000;
|
tsk->thread.load_slb++;
|
||||||
|
if (!tsk->thread.load_slb) {
|
||||||
|
unsigned long pc = KSTK_EIP(tsk);
|
||||||
|
|
||||||
if (is_kernel_addr(pc) || is_kernel_addr(stack) ||
|
preload_age(ti);
|
||||||
is_kernel_addr(exec_base))
|
preload_add(ti, pc);
|
||||||
return;
|
}
|
||||||
|
|
||||||
slb_allocate_user(mm, pc);
|
for (i = 0; i < ti->slb_preload_nr; i++) {
|
||||||
|
unsigned long ea;
|
||||||
|
u8 idx;
|
||||||
|
|
||||||
if (!esids_match(pc, stack))
|
idx = (ti->slb_preload_tail + i) % SLB_PRELOAD_NR;
|
||||||
slb_allocate_user(mm, stack);
|
ea = (unsigned long)ti->slb_preload_esid[idx] << SID_SHIFT;
|
||||||
|
|
||||||
if (!esids_match(pc, exec_base) &&
|
slb_allocate_user(mm, ea);
|
||||||
!esids_match(stack, exec_base))
|
}
|
||||||
slb_allocate_user(mm, exec_base);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void slb_set_size(u16 size)
|
void slb_set_size(u16 size)
|
||||||
|
@ -644,11 +725,16 @@ long do_slb_fault(struct pt_regs *regs, unsigned long ea)
|
||||||
return slb_allocate_kernel(ea, id);
|
return slb_allocate_kernel(ea, id);
|
||||||
} else {
|
} else {
|
||||||
struct mm_struct *mm = current->mm;
|
struct mm_struct *mm = current->mm;
|
||||||
|
long err;
|
||||||
|
|
||||||
if (unlikely(!mm))
|
if (unlikely(!mm))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
return slb_allocate_user(mm, ea);
|
err = slb_allocate_user(mm, ea);
|
||||||
|
if (!err)
|
||||||
|
preload_add(current_thread_info(), ea);
|
||||||
|
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче