diff options
| author | Christoph Lameter <cl@linux.com> | 2011-08-09 17:12:27 -0400 |
|---|---|---|
| committer | Pekka Enberg <penberg@kernel.org> | 2011-08-19 12:34:27 -0400 |
| commit | 49e2258586b423684f03c278149ab46d8f8b6700 (patch) | |
| tree | d4404d1b09d6fe505da29a32602d193c4ef56ac9 | |
| parent | 497b66f2ecc97844493e6a147fd5a7e73f73f408 (diff) | |
slub: per cpu cache for partial pages
Allow filling out the rest of the kmem_cache_cpu cacheline with pointers to
partial pages. The partial page list is used in slab_free() to avoid
per node lock taking.
In __slab_alloc() we can then take multiple partial pages off the per
node partial list in one go reducing node lock pressure.
We can also use the per cpu partial list in slab_alloc() to avoid scanning
partial lists for pages with free objects.
The main effect of a per cpu partial list is that the per node list_lock
is taken for batches of partial pages instead of individual ones.
Potential future enhancements:
1. The pickup from the partial list could be perhaps be done without disabling
interrupts with some work. The free path already puts the page into the
per cpu partial list without disabling interrupts.
2. __slab_free() may have some code paths that could use optimization.
Performance:
Before After
./hackbench 100 process 200000
Time: 1953.047 1564.614
./hackbench 100 process 20000
Time: 207.176 156.940
./hackbench 100 process 20000
Time: 204.468 156.940
./hackbench 100 process 20000
Time: 204.879 158.772
./hackbench 10 process 20000
Time: 20.153 15.853
./hackbench 10 process 20000
Time: 20.153 15.986
./hackbench 10 process 20000
Time: 19.363 16.111
./hackbench 1 process 20000
Time: 2.518 2.307
./hackbench 1 process 20000
Time: 2.258 2.339
./hackbench 1 process 20000
Time: 2.864 2.163
Signed-off-by: Christoph Lameter <cl@linux.com>
Signed-off-by: Pekka Enberg <penberg@kernel.org>
| -rw-r--r-- | include/linux/mm_types.h | 14 | ||||
| -rw-r--r-- | include/linux/slub_def.h | 4 | ||||
| -rw-r--r-- | mm/slub.c | 339 |
3 files changed, 309 insertions, 48 deletions
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h index 774b8952deb4..7870e473033c 100644 --- a/include/linux/mm_types.h +++ b/include/linux/mm_types.h | |||
| @@ -79,9 +79,21 @@ struct page { | |||
| 79 | }; | 79 | }; |
| 80 | 80 | ||
| 81 | /* Third double word block */ | 81 | /* Third double word block */ |
| 82 | struct list_head lru; /* Pageout list, eg. active_list | 82 | union { |
| 83 | struct list_head lru; /* Pageout list, eg. active_list | ||
| 83 | * protected by zone->lru_lock ! | 84 | * protected by zone->lru_lock ! |
| 84 | */ | 85 | */ |
| 86 | struct { /* slub per cpu partial pages */ | ||
| 87 | struct page *next; /* Next partial slab */ | ||
| 88 | #ifdef CONFIG_64BIT | ||
| 89 | int pages; /* Nr of partial slabs left */ | ||
| 90 | int pobjects; /* Approximate # of objects */ | ||
| 91 | #else | ||
| 92 | short int pages; | ||
| 93 | short int pobjects; | ||
| 94 | #endif | ||
| 95 | }; | ||
| 96 | }; | ||
| 85 | 97 | ||
| 86 | /* Remainder is not double word aligned */ | 98 | /* Remainder is not double word aligned */ |
| 87 | union { | 99 | union { |
diff --git a/include/linux/slub_def.h b/include/linux/slub_def.h index f58d6413d230..4890ef79d752 100644 --- a/include/linux/slub_def.h +++ b/include/linux/slub_def.h | |||
| @@ -36,12 +36,15 @@ enum stat_item { | |||
| 36 | ORDER_FALLBACK, /* Number of times fallback was necessary */ | 36 | ORDER_FALLBACK, /* Number of times fallback was necessary */ |
| 37 | CMPXCHG_DOUBLE_CPU_FAIL,/* Failure of this_cpu_cmpxchg_double */ | 37 | CMPXCHG_DOUBLE_CPU_FAIL,/* Failure of this_cpu_cmpxchg_double */ |
| 38 | CMPXCHG_DOUBLE_FAIL, /* Number of times that cmpxchg double did not match */ | 38 | CMPXCHG_DOUBLE_FAIL, /* Number of times that cmpxchg double did not match */ |
| 39 | CPU_PARTIAL_ALLOC, /* Used cpu partial on alloc */ | ||
| 40 | CPU_PARTIAL_FREE, /* USed cpu partial on free */ | ||
| 39 | NR_SLUB_STAT_ITEMS }; | 41 | NR_SLUB_STAT_ITEMS }; |
| 40 | 42 | ||
| 41 | struct kmem_cache_cpu { | 43 | struct kmem_cache_cpu { |
| 42 | void **freelist; /* Pointer to next available object */ | 44 | void **freelist; /* Pointer to next available object */ |
| 43 | unsigned long tid; /* Globally unique transaction id */ | 45 | unsigned long tid; /* Globally unique transaction id */ |
| 44 | struct page *page; /* The slab from which we are allocating */ | 46 | struct page *page; /* The slab from which we are allocating */ |
| 47 | struct page *partial; /* Partially allocated frozen slabs */ | ||
| 45 | int node; /* The node of the page (or -1 for debug) */ | 48 | int node; /* The node of the page (or -1 for debug) */ |
| 46 | #ifdef CONFIG_SLUB_STATS | 49 | #ifdef CONFIG_SLUB_STATS |
| 47 | unsigned stat[NR_SLUB_STAT_ITEMS]; | 50 | unsigned stat[NR_SLUB_STAT_ITEMS]; |
| @@ -79,6 +82,7 @@ struct kmem_cache { | |||
| 79 | int size; /* The size of an object including meta data */ | 82 | int size; /* The size of an object including meta data */ |
| 80 | int objsize; /* The size of an object without meta data */ | 83 | int objsize; /* The size of an object without meta data */ |
| 81 | int offset; /* Free pointer offset. */ | 84 | int offset; /* Free pointer offset. */ |
| 85 | int cpu_partial; /* Number of per cpu partial pages to keep around */ | ||
| 82 | struct kmem_cache_order_objects oo; | 86 | struct kmem_cache_order_objects oo; |
| 83 | 87 | ||
| 84 | /* Allocation and freeing of slabs */ | 88 | /* Allocation and freeing of slabs */ |
| @@ -1560,7 +1560,7 @@ static inline void remove_partial(struct kmem_cache_node *n, | |||
| 1560 | */ | 1560 | */ |
| 1561 | static inline void *acquire_slab(struct kmem_cache *s, | 1561 | static inline void *acquire_slab(struct kmem_cache *s, |
| 1562 | struct kmem_cache_node *n, struct page *page, | 1562 | struct kmem_cache_node *n, struct page *page, |
| 1563 | struct kmem_cache_cpu *c) | 1563 | int mode) |
| 1564 | { | 1564 | { |
| 1565 | void *freelist; | 1565 | void *freelist; |
| 1566 | unsigned long counters; | 1566 | unsigned long counters; |
| @@ -1575,7 +1575,8 @@ static inline void *acquire_slab(struct kmem_cache *s, | |||
| 1575 | freelist = page->freelist; | 1575 | freelist = page->freelist; |
| 1576 | counters = page->counters; | 1576 | counters = page->counters; |
| 1577 | new.counters = counters; | 1577 | new.counters = counters; |
| 1578 | new.inuse = page->objects; | 1578 | if (mode) |
| 1579 | new.inuse = page->objects; | ||
| 1579 | 1580 | ||
| 1580 | VM_BUG_ON(new.frozen); | 1581 | VM_BUG_ON(new.frozen); |
| 1581 | new.frozen = 1; | 1582 | new.frozen = 1; |
| @@ -1586,34 +1587,20 @@ static inline void *acquire_slab(struct kmem_cache *s, | |||
| 1586 | "lock and freeze")); | 1587 | "lock and freeze")); |
| 1587 | 1588 | ||
| 1588 | remove_partial(n, page); | 1589 | remove_partial(n, page); |
| 1589 | 1590 | return freelist; | |
| 1590 | if (freelist) { | ||
| 1591 | /* Populate the per cpu freelist */ | ||
| 1592 | c->page = page; | ||
| 1593 | c->node = page_to_nid(page); | ||
| 1594 | stat(s, ALLOC_FROM_PARTIAL); | ||
| 1595 | |||
| 1596 | return freelist; | ||
| 1597 | } else { | ||
| 1598 | /* | ||
| 1599 | * Slab page came from the wrong list. No object to allocate | ||
| 1600 | * from. Put it onto the correct list and continue partial | ||
| 1601 | * scan. | ||
| 1602 | */ | ||
| 1603 | printk(KERN_ERR "SLUB: %s : Page without available objects on" | ||
| 1604 | " partial list\n", s->name); | ||
| 1605 | return NULL; | ||
| 1606 | } | ||
| 1607 | } | 1591 | } |
| 1608 | 1592 | ||
| 1593 | static int put_cpu_partial(struct kmem_cache *s, struct page *page, int drain); | ||
| 1594 | |||
| 1609 | /* | 1595 | /* |
| 1610 | * Try to allocate a partial slab from a specific node. | 1596 | * Try to allocate a partial slab from a specific node. |
| 1611 | */ | 1597 | */ |
| 1612 | static void *get_partial_node(struct kmem_cache *s, | 1598 | static void *get_partial_node(struct kmem_cache *s, |
| 1613 | struct kmem_cache_node *n, struct kmem_cache_cpu *c) | 1599 | struct kmem_cache_node *n, struct kmem_cache_cpu *c) |
| 1614 | { | 1600 | { |
| 1615 | struct page *page; | 1601 | struct page *page, *page2; |
| 1616 | void *object; | 1602 | void *object = NULL; |
| 1603 | int count = 0; | ||
| 1617 | 1604 | ||
| 1618 | /* | 1605 | /* |
| 1619 | * Racy check. If we mistakenly see no partial slabs then we | 1606 | * Racy check. If we mistakenly see no partial slabs then we |
| @@ -1625,13 +1612,28 @@ static void *get_partial_node(struct kmem_cache *s, | |||
| 1625 | return NULL; | 1612 | return NULL; |
| 1626 | 1613 | ||
| 1627 | spin_lock(&n->list_lock); | 1614 | spin_lock(&n->list_lock); |
| 1628 | list_for_each_entry(page, &n->partial, lru) { | 1615 | list_for_each_entry_safe(page, page2, &n->partial, lru) { |
| 1629 | object = acquire_slab(s, n, page, c); | 1616 | void *t = acquire_slab(s, n, page, count == 0); |
| 1630 | if (object) | 1617 | int available; |
| 1631 | goto out; | 1618 | |
| 1619 | if (!t) | ||
| 1620 | break; | ||
| 1621 | |||
| 1622 | if (!count) { | ||
| 1623 | c->page = page; | ||
| 1624 | c->node = page_to_nid(page); | ||
| 1625 | stat(s, ALLOC_FROM_PARTIAL); | ||
| 1626 | count++; | ||
| 1627 | object = t; | ||
| 1628 | available = page->objects - page->inuse; | ||
| 1629 | } else { | ||
| 1630 | page->freelist = t; | ||
| 1631 | available = put_cpu_partial(s, page, 0); | ||
| 1632 | } | ||
| 1633 | if (kmem_cache_debug(s) || available > s->cpu_partial / 2) | ||
| 1634 | break; | ||
| 1635 | |||
| 1632 | } | 1636 | } |
| 1633 | object = NULL; | ||
| 1634 | out: | ||
| 1635 | spin_unlock(&n->list_lock); | 1637 | spin_unlock(&n->list_lock); |
| 1636 | return object; | 1638 | return object; |
| 1637 | } | 1639 | } |
| @@ -1926,6 +1928,123 @@ redo: | |||
| 1926 | } | 1928 | } |
| 1927 | } | 1929 | } |
| 1928 | 1930 | ||
| 1931 | /* Unfreeze all the cpu partial slabs */ | ||
| 1932 | static void unfreeze_partials(struct kmem_cache *s) | ||
| 1933 | { | ||
| 1934 | struct kmem_cache_node *n = NULL; | ||
| 1935 | struct kmem_cache_cpu *c = this_cpu_ptr(s->cpu_slab); | ||
| 1936 | struct page *page; | ||
| 1937 | |||
| 1938 | while ((page = c->partial)) { | ||
| 1939 | enum slab_modes { M_PARTIAL, M_FREE }; | ||
| 1940 | enum slab_modes l, m; | ||
| 1941 | struct page new; | ||
| 1942 | struct page old; | ||
| 1943 | |||
| 1944 | c->partial = page->next; | ||
| 1945 | l = M_FREE; | ||
| 1946 | |||
| 1947 | do { | ||
| 1948 | |||
| 1949 | old.freelist = page->freelist; | ||
