qubes-linux-kernel/patches.suse/slab-handle-memoryless-nodes-v2a.patch
2011-04-19 22:09:59 +02:00

309 lines
10 KiB
Diff

From: Lee Schermerhorn <lee.schermerhorn@hp.com>
Subject: slab - handle memoryless nodes V2a
References: bnc#436025, bnc#570492
Patch-mainline: not yet
The slab cache, since [apparently] 2.6.21, does not handle memoryless
nodes well. Specifically, the "fast path"-- ____cache_alloc()--will
never succeed, but will be called twice: once speculatively [expected
to succeed] and once in the fallback path. This adds significant
overhead to all kmem cache allocations, incurring a significant
regression relative to earlier kernels [from before slab.c was
reorganized].
This patch addresses the regression by modifying slab.c to treat the
first fallback node in a memoryless node's general zonelist as the "slab
local node" -- i.e., the local node for the purpose of slab allocations.
This is, in fact, the node from which all "local" allocations for cpus
attached to a memoryless node will be satisfied.
The new function numa_slab_nid(gfp_t) replaces all calls to
numa_node_id() in slab.c. numa_slab_id() will simply return
numa_node_id() for nodes with memory, but will return the first
node in the local node's zonelist selected by the gfp flags.
Effects of the patch:
We first noticed the effects of the slab reorganization running the
AIM benchmark on a distro based on 2.6.27. The effect is even more
pronounced in the hackbench results. The platform in an HP rx8640
numa platform, configured with "0% Cell Local Memory". In this
configuration, all memory appears in a "pseudo-node"--an artifact
of the firmware--and is interleaved across all the physical nodes'
memory on a cacheline granularity. All cpus are presented as
attached to memoryless nodes.
Here are the results of running hackbench at various load levels
with and without the patch on the same platform configured for
0% CLM and "100% CLM".
Command: hackbench N process 100, for N = 10..100 by 10
100% CLM 0% CLM
Tasks no with no with
patch patch %diff patch patch %diff
400 0.246 0.281 14.23% 2.962 0.410 -86.16%
800 0.418 0.421 0.72% 6.224 0.793 -87.26%
1200 0.548 0.532 -2.92% 9.058 1.090 -87.97%
1600 0.654 0.716 9.48% 12.473 1.562 -87.48%
2000 0.871 0.859 -1.38% 15.484 1.889 -87.80%
2400 0.994 1.043 4.93% 18.689 2.309 -87.65%
2800 1.196 1.195 -0.08% 22.069 2.606 -88.19%
3200 1.322 1.344 1.66% 25.642 2.988 -88.35%
3600 1.474 1.519 3.05% 28.003 3.418 -87.79%
4000 1.682 1.750 4.04% 30.887 3.858 -87.51%
In the 100% CLM case, the regression does not appear, because
all nodes have local memory. Note that the patch has >10%
overhead on the first run, but then varies widely from run
to run [more below]. For the 0%CLM configuration, the patch
reduced the run time by 86-88%.
The following runs extend the number of hackbench tasks using:
hackbench N process 100, for N = 100 to 400 by 20
We didn't run the 0%CLM/no-patch runs as they were taking too
long for our liking. We wanted to see how the patched kernel
performed as we extended the range.
100% CLM 0% CLM
Tasks no with no with
patch patch %diff patch patch %diff
4800 1.879 2.117 12.67% not-run 4.458
5600 2.100 2.352 12.00% not-run 5.207
6400 2.532 2.447 -3.36% not-run 5.669
8000 2.799 2.792 -0.25% not-run 6.651
8000 3.244 3.030 -6.60% not-run 7.366
8800 3.282 3.550 8.17% not-run 8.169
9600 3.595 3.738 3.98% not-run 8.582
10400 3.811 4.045 6.14% not-run 9.705
11200 4.090 4.162 1.76% not-run 9.760
12000 4.408 4.419 0.25% not-run 10.141
12800 4.665 4.787 2.62% not-run 11.628
13600 5.095 5.069 -0.51% not-run 11.735
14400 5.347 5.464 2.19% not-run 12.621
15200 5.620 5.831 3.75% not-run 13.452
16000 5.870 6.161 4.96% not-run 14.069
The 0% CLM configuration with the patch performs worse than
the 100% CLM configuration. In the 0% CLM case we had 64
ia64 cores beating on a single zone in the interleaved
memory-only pseudo-node. In the 100% CLM case, we have 16
cores allocating memory locally to each of 4 nodes,
demonstating the difference between [pseudo-]SMP and NUMA
behavior.
Note, again, that the first run[s] have higher % difference
between the patched and unpatched kernels for the 100% CLM
config, and then vary quite a bit run to run. To get a feel
for the average overhead, we ran 40 runs at the 16000 task
load point with more interations to increase the runtime
per run:
hackbench 400 process 200
These were run on the 100% CLM configuration, as this best represents
most NUMA platforms:
No patch with Patch %diff
Average of 40: 9.796 9.857 0.623
Signed-off-by: Lee Schermerhorn <lee.schermerhorn@hp.com>
Acked-by: Nick Piggin <npiggin@suse.de>
mm/slab.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 76 insertions(+), 12 deletions(-)
--- a/mm/slab.c
+++ b/mm/slab.c
@@ -281,7 +281,7 @@ struct kmem_list3 {
struct array_cache **alien; /* on other nodes */
unsigned long next_reap; /* updated without locking */
int free_touched; /* updated without locking */
-};
+} __attribute__((aligned(sizeof(long))));
/*
* Need this for bootstrapping a per node allocator.
@@ -944,6 +944,11 @@ static int transfer_objects(struct array
#define drain_alien_cache(cachep, alien) do { } while (0)
#define reap_alien(cachep, l3) do { } while (0)
+static inline int numa_slab_nid(struct kmem_cache *cachep, gfp_t flags)
+{
+ return 0;
+}
+
static inline struct array_cache **alloc_alien_cache(int node, int limit, gfp_t gfp)
{
return (struct array_cache **)BAD_ALIEN_MAGIC;
@@ -975,6 +980,64 @@ static inline void *____cache_alloc_node
static void *____cache_alloc_node(struct kmem_cache *, gfp_t, int);
static void *alternate_node_alloc(struct kmem_cache *, gfp_t);
+/*
+ * slow path for numa_slab_nid(), below
+ */
+static noinline int __numa_slab_nid(struct kmem_cache *cachep,
+ int node, gfp_t flags)
+{
+ struct zonelist *zonelist;
+ struct zone *zone;
+ enum zone_type highest_zoneidx = gfp_zone(flags);
+
+ if (likely(node_state(node, N_NORMAL_MEMORY)))
+ return node;
+
+ /*
+ * memoryless node: consult its zonelist.
+ * Cache the fallback node, if cache pointer provided.
+ */
+ zonelist = &NODE_DATA(node)->node_zonelists[0];
+ (void)first_zones_zonelist(zonelist, highest_zoneidx,
+ NULL,
+ &zone);
+ if (cachep)
+ cachep->nodelists[node] =
+ (struct kmem_list3 *)((unsigned long)zone->node << 1 | 1);
+ return zone->node;
+}
+
+/*
+ * "Local" node for slab is first node in zonelist with memory.
+ * For nodes with memory this will be the actual local node.
+ *
+ * Use nodelist[numa_node_id()] to cache the fallback node for
+ * memoryless nodes. We'll be loading that member soon anyway,
+ * or already have, when called for cache refill, ... Use low
+ * bit of "pointer" as flag for "memoryless_node", indicating
+ * that the fallback nodes is stored here [<<1].
+ */
+#define memoryless_node(L3L) ((L3L) & 1)
+static inline int numa_slab_nid(struct kmem_cache *cachep, gfp_t flags)
+{
+ int node = numa_mem_id();
+
+ if (likely(cachep)){
+ unsigned long l3l = (unsigned long)cachep->nodelists[node];
+
+ if (likely(l3l)) {
+ if (unlikely(memoryless_node(l3l)))
+ node = (int)(l3l >> 1);
+ return node;
+ }
+ }
+
+ /*
+ * !cachep || !l3l - the slow path
+ */
+ return __numa_slab_nid(cachep, node, flags);
+}
+
static struct array_cache **alloc_alien_cache(int node, int limit, gfp_t gfp)
{
struct array_cache **ac_ptr;
@@ -1074,7 +1137,7 @@ static inline int cache_free_alien(struc
struct array_cache *alien = NULL;
int node;
- node = numa_mem_id();
+ node = numa_slab_nid(cachep, GFP_KERNEL);
/*
* Make sure we are not freeing a object from another node to the array
@@ -1503,7 +1566,7 @@ void __init kmem_cache_init(void)
* 6) Resize the head arrays of the kmalloc caches to their final sizes.
*/
- node = numa_mem_id();
+ node = numa_slab_nid(NULL, GFP_KERNEL);
/* 1) create the cache_cache */
INIT_LIST_HEAD(&cache_chain);
@@ -2147,7 +2210,7 @@ static int __init_refok setup_cpu_cache(
}
}
}
- cachep->nodelists[numa_mem_id()]->next_reap =
+ cachep->nodelists[numa_slab_nid(cachep, GFP_KERNEL)]->next_reap =
jiffies + REAPTIMEOUT_LIST3 +
((unsigned long)cachep) % REAPTIMEOUT_LIST3;
@@ -2479,7 +2542,7 @@ static void check_spinlock_acquired(stru
{
#ifdef CONFIG_SMP
check_irq_off();
- assert_spin_locked(&cachep->nodelists[numa_mem_id()]->list_lock);
+ assert_spin_locked(&cachep->nodelists[numa_slab_nid(cachep, GFP_KERNEL)]->list_lock);
#endif
}
@@ -2506,7 +2569,7 @@ static void do_drain(void *arg)
{
struct kmem_cache *cachep = arg;
struct array_cache *ac;
- int node = numa_mem_id();
+ int node = numa_slab_nid(cachep, GFP_KERNEL);
check_irq_off();
ac = cpu_cache_get(cachep);
@@ -3043,7 +3106,7 @@ static void *cache_alloc_refill(struct k
retry:
check_irq_off();
- node = numa_mem_id();
+ node = numa_slab_nid(cachep, flags);
if (unlikely(must_refill))
goto force_grow;
ac = cpu_cache_get(cachep);
@@ -3253,7 +3316,7 @@ static void *alternate_node_alloc(struct
if (in_interrupt() || (flags & __GFP_THISNODE))
return NULL;
- nid_alloc = nid_here = numa_mem_id();
+ nid_alloc = nid_here = numa_slab_nid(cachep, flags);
get_mems_allowed();
if (cpuset_do_slab_mem_spread() && (cachep->flags & SLAB_MEM_SPREAD))
nid_alloc = cpuset_slab_spread_node();
@@ -3432,7 +3495,7 @@ __cache_alloc_node(struct kmem_cache *ca
{
unsigned long save_flags;
void *ptr;
- int slab_node = numa_mem_id();
+ int slab_node = numa_slab_nid(cachep, flags);
flags &= gfp_allowed_mask;
@@ -3498,7 +3561,8 @@ __do_cache_alloc(struct kmem_cache *cach
* ____cache_alloc_node() knows how to locate memory on other nodes
*/
if (!objp)
- objp = ____cache_alloc_node(cache, flags, numa_mem_id());
+ objp = ____cache_alloc_node(cache, flags,
+ numa_slab_nid(cache, flags));
out:
return objp;
@@ -3595,7 +3659,7 @@ static void cache_flusharray(struct kmem
{
int batchcount;
struct kmem_list3 *l3;
- int node = numa_mem_id();
+ int node = numa_slab_nid(cachep, GFP_KERNEL);
batchcount = ac->batchcount;
#if DEBUG
@@ -4234,7 +4298,7 @@ static void cache_reap(struct work_struc
{
struct kmem_cache *searchp;
struct kmem_list3 *l3;
- int node = numa_mem_id();
+ int node = numa_slab_nid(NULL, GFP_KERNEL);
struct delayed_work *work = to_delayed_work(w);
if (!mutex_trylock(&cache_chain_mutex))