Commit 3e45c91e authored by Joe Thornber's avatar Joe Thornber Committed by Mike Snitzer

dm cache policy mq: track entries hit this 'tick' via sentinel objects

A sentinel object is placed on each level of the multiqueues.  When an
object is hit it is requeued behind the sentinel.  When the tick is
incremented we iterate through all objects behind the sentinel and
update the hit_count, then reposition the sentinel at the very back.

This saves memory by avoiding tracking the tick explicitly for every
struct entry object in the multiqueues.
Signed-off-by: default avatarJoe Thornber <ejt@redhat.com>
Signed-off-by: default avatarMike Snitzer <snitzer@redhat.com>
parent c74ffc5c
...@@ -124,10 +124,12 @@ static void iot_examine_bio(struct io_tracker *t, struct bio *bio) ...@@ -124,10 +124,12 @@ static void iot_examine_bio(struct io_tracker *t, struct bio *bio)
* sorted queue. * sorted queue.
*/ */
#define NR_QUEUE_LEVELS 16u #define NR_QUEUE_LEVELS 16u
#define NR_SENTINELS NR_QUEUE_LEVELS * 3
struct queue { struct queue {
unsigned nr_elts; unsigned nr_elts;
struct list_head qs[NR_QUEUE_LEVELS]; struct list_head qs[NR_QUEUE_LEVELS];
struct list_head sentinels[NR_SENTINELS];
}; };
static void queue_init(struct queue *q) static void queue_init(struct queue *q)
...@@ -135,8 +137,10 @@ static void queue_init(struct queue *q) ...@@ -135,8 +137,10 @@ static void queue_init(struct queue *q)
unsigned i; unsigned i;
q->nr_elts = 0; q->nr_elts = 0;
for (i = 0; i < NR_QUEUE_LEVELS; i++) for (i = 0; i < NR_QUEUE_LEVELS; i++) {
INIT_LIST_HEAD(q->qs + i); INIT_LIST_HEAD(q->qs + i);
INIT_LIST_HEAD(q->sentinels + i);
}
} }
static bool queue_empty(struct queue *q) static bool queue_empty(struct queue *q)
...@@ -159,6 +163,11 @@ static void queue_remove(struct queue *q, struct list_head *elt) ...@@ -159,6 +163,11 @@ static void queue_remove(struct queue *q, struct list_head *elt)
list_del(elt); list_del(elt);
} }
static bool is_sentinel(struct queue *q, struct list_head *h)
{
return (h >= q->sentinels) && (h < (q->sentinels + NR_SENTINELS));
}
/* /*
* Gives us the oldest entry of the lowest popoulated level. If the first * Gives us the oldest entry of the lowest popoulated level. If the first
* level is emptied then we shift down one level. * level is emptied then we shift down one level.
...@@ -166,10 +175,12 @@ static void queue_remove(struct queue *q, struct list_head *elt) ...@@ -166,10 +175,12 @@ static void queue_remove(struct queue *q, struct list_head *elt)
static struct list_head *queue_peek(struct queue *q) static struct list_head *queue_peek(struct queue *q)
{ {
unsigned level; unsigned level;
struct list_head *h;
for (level = 0; level < NR_QUEUE_LEVELS; level++) for (level = 0; level < NR_QUEUE_LEVELS; level++)
if (!list_empty(q->qs + level)) list_for_each(h, q->qs + level)
return q->qs[level].next; if (!is_sentinel(q, h))
return h;
return NULL; return NULL;
} }
...@@ -196,6 +207,37 @@ static struct list_head *list_pop(struct list_head *lh) ...@@ -196,6 +207,37 @@ static struct list_head *list_pop(struct list_head *lh)
return r; return r;
} }
/*
* Sometimes we want to iterate through entries that have been pushed since
* a certain event. We use sentinel entries on the queues to delimit these
* 'tick' events.
*/
static void queue_tick(struct queue *q)
{
unsigned i;
for (i = 0; i < NR_QUEUE_LEVELS; i++) {
list_del(q->sentinels + i);
list_add_tail(q->sentinels + i, q->qs + i);
}
}
typedef void (*iter_fn)(struct list_head *, void *);
static void queue_iterate_tick(struct queue *q, iter_fn fn, void *context)
{
unsigned i;
struct list_head *h;
for (i = 0; i < NR_QUEUE_LEVELS; i++) {
list_for_each_prev(h, q->qs + i) {
if (is_sentinel(q, h))
break;
fn(h, context);
}
}
}
/*----------------------------------------------------------------*/ /*----------------------------------------------------------------*/
/* /*
...@@ -212,7 +254,6 @@ struct entry { ...@@ -212,7 +254,6 @@ struct entry {
bool dirty:1; bool dirty:1;
unsigned hit_count; unsigned hit_count;
unsigned generation; unsigned generation;
unsigned tick;
}; };
/* /*
...@@ -460,7 +501,6 @@ static bool in_cache(struct mq_policy *mq, struct entry *e) ...@@ -460,7 +501,6 @@ static bool in_cache(struct mq_policy *mq, struct entry *e)
*/ */
static void push(struct mq_policy *mq, struct entry *e) static void push(struct mq_policy *mq, struct entry *e)
{ {
e->tick = mq->tick;
hash_insert(mq, e); hash_insert(mq, e);
if (in_cache(mq, e)) if (in_cache(mq, e))
...@@ -507,14 +547,6 @@ static struct entry *peek(struct queue *q) ...@@ -507,14 +547,6 @@ static struct entry *peek(struct queue *q)
return h ? container_of(h, struct entry, list) : NULL; return h ? container_of(h, struct entry, list) : NULL;
} }
/*
* Has this entry already been updated?
*/
static bool updated_this_tick(struct mq_policy *mq, struct entry *e)
{
return mq->tick == e->tick;
}
/* /*
* The promotion threshold is adjusted every generation. As are the counts * The promotion threshold is adjusted every generation. As are the counts
* of the entries. * of the entries.
...@@ -566,20 +598,9 @@ static void check_generation(struct mq_policy *mq) ...@@ -566,20 +598,9 @@ static void check_generation(struct mq_policy *mq)
* Whenever we use an entry we bump up it's hit counter, and push it to the * Whenever we use an entry we bump up it's hit counter, and push it to the
* back to it's current level. * back to it's current level.
*/ */
static void requeue_and_update_tick(struct mq_policy *mq, struct entry *e) static void requeue(struct mq_policy *mq, struct entry *e)
{ {
if (updated_this_tick(mq, e))
return;
e->hit_count++;
mq->hit_count++;
check_generation(mq); check_generation(mq);
/* generation adjustment, to stop the counts increasing forever. */
/* FIXME: divide? */
/* e->hit_count -= min(e->hit_count - 1, mq->generation - e->generation); */
e->generation = mq->generation;
del(mq, e); del(mq, e);
push(mq, e); push(mq, e);
} }
...@@ -686,7 +707,7 @@ static int cache_entry_found(struct mq_policy *mq, ...@@ -686,7 +707,7 @@ static int cache_entry_found(struct mq_policy *mq,
struct entry *e, struct entry *e,
struct policy_result *result) struct policy_result *result)
{ {
requeue_and_update_tick(mq, e); requeue(mq, e);
if (in_cache(mq, e)) { if (in_cache(mq, e)) {
result->op = POLICY_HIT; result->op = POLICY_HIT;
...@@ -724,7 +745,6 @@ static int pre_cache_to_cache(struct mq_policy *mq, struct entry *e, ...@@ -724,7 +745,6 @@ static int pre_cache_to_cache(struct mq_policy *mq, struct entry *e,
new_e->dirty = false; new_e->dirty = false;
new_e->hit_count = e->hit_count; new_e->hit_count = e->hit_count;
new_e->generation = e->generation; new_e->generation = e->generation;
new_e->tick = e->tick;
del(mq, e); del(mq, e);
free_entry(&mq->pre_cache_pool, e); free_entry(&mq->pre_cache_pool, e);
...@@ -740,18 +760,16 @@ static int pre_cache_entry_found(struct mq_policy *mq, struct entry *e, ...@@ -740,18 +760,16 @@ static int pre_cache_entry_found(struct mq_policy *mq, struct entry *e,
int data_dir, struct policy_result *result) int data_dir, struct policy_result *result)
{ {
int r = 0; int r = 0;
bool updated = updated_this_tick(mq, e);
if ((!discarded_oblock && updated) || if (!should_promote(mq, e, discarded_oblock, data_dir)) {
!should_promote(mq, e, discarded_oblock, data_dir)) { requeue(mq, e);
requeue_and_update_tick(mq, e);
result->op = POLICY_MISS; result->op = POLICY_MISS;
} else if (!can_migrate) } else if (!can_migrate)
r = -EWOULDBLOCK; r = -EWOULDBLOCK;
else { else {
requeue_and_update_tick(mq, e); requeue(mq, e);
r = pre_cache_to_cache(mq, e, result); r = pre_cache_to_cache(mq, e, result);
} }
...@@ -888,12 +906,36 @@ static void mq_destroy(struct dm_cache_policy *p) ...@@ -888,12 +906,36 @@ static void mq_destroy(struct dm_cache_policy *p)
kfree(mq); kfree(mq);
} }
static void update_pre_cache_hits(struct list_head *h, void *context)
{
struct entry *e = container_of(h, struct entry, list);
e->hit_count++;
}
static void update_cache_hits(struct list_head *h, void *context)
{
struct mq_policy *mq = context;
struct entry *e = container_of(h, struct entry, list);
e->hit_count++;
mq->hit_count++;
}
static void copy_tick(struct mq_policy *mq) static void copy_tick(struct mq_policy *mq)
{ {
unsigned long flags; unsigned long flags, tick;
spin_lock_irqsave(&mq->tick_lock, flags); spin_lock_irqsave(&mq->tick_lock, flags);
mq->tick = mq->tick_protected; tick = mq->tick_protected;
if (tick != mq->tick) {
queue_iterate_tick(&mq->pre_cache, update_pre_cache_hits, mq);
queue_iterate_tick(&mq->cache_dirty, update_cache_hits, mq);
queue_iterate_tick(&mq->cache_clean, update_cache_hits, mq);
mq->tick = tick;
}
queue_tick(&mq->pre_cache);
queue_tick(&mq->cache_dirty);
queue_tick(&mq->cache_clean);
spin_unlock_irqrestore(&mq->tick_lock, flags); spin_unlock_irqrestore(&mq->tick_lock, flags);
} }
...@@ -995,10 +1037,15 @@ static int mq_save_hints(struct mq_policy *mq, struct queue *q, ...@@ -995,10 +1037,15 @@ static int mq_save_hints(struct mq_policy *mq, struct queue *q,
{ {
int r; int r;
unsigned level; unsigned level;
struct list_head *h;
struct entry *e; struct entry *e;
for (level = 0; level < NR_QUEUE_LEVELS; level++) for (level = 0; level < NR_QUEUE_LEVELS; level++)
list_for_each_entry(e, q->qs + level, list) { list_for_each(h, q->qs + level) {
if (is_sentinel(q, h))
continue;
e = container_of(h, struct entry, list);
r = fn(context, infer_cblock(&mq->cache_pool, e), r = fn(context, infer_cblock(&mq->cache_pool, e),
e->oblock, e->hit_count); e->oblock, e->hit_count);
if (r) if (r)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment