Browse Source

timer: cache the minimal value.

We spend a lot of time searching for the next timer to expire: by caching
the minimum, we can skip most of this work.  Even if timers are deleted,
the minimum will be a starting point for searching.

The expected-usage benchmark has to be increased by a factor of 100,
otherwise it's now too short.

Before:
	$ ./expected-usage
	1000000 in 12.701647935

After:
	$ ./expected-usage  1000000
	1000000 in 0.061095153

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Rusty Russell 13 years ago
parent
commit
a40fc5a89e
4 changed files with 64 additions and 29 deletions
  1. 1 1
      ccan/timer/benchmarks/expected-usage.c
  2. 1 2
      ccan/timer/test/run-add.c
  3. 60 25
      ccan/timer/timer.c
  4. 2 1
      ccan/timer/timer.h

+ 1 - 1
ccan/timer/benchmarks/expected-usage.c

@@ -24,7 +24,7 @@ int main(int argc, char *argv[])
 
 
 	opt_parse(&argc, argv, opt_log_stderr_exit);
 	opt_parse(&argc, argv, opt_log_stderr_exit);
 
 
-	num = argv[1] ? atoi(argv[1]) : (check ? 10000 : 1000000);
+	num = argv[1] ? atoi(argv[1]) : (check ? 100000 : 100000000);
 
 
 	list_head_init(&expired);
 	list_head_init(&expired);
 	curr = start = time_now();
 	curr = start = time_now();

+ 1 - 2
ccan/timer/test/run-add.c

@@ -36,8 +36,7 @@ int main(void)
 		for (timers.base = 0;
 		for (timers.base = 0;
 		     timers.base < (1ULL << MAX_ORD)+2;
 		     timers.base < (1ULL << MAX_ORD)+2;
 		     timers.base = next(timers.base)) {
 		     timers.base = next(timers.base)) {
-			t.time = timers.base + diff;
-			timer_add_raw(&timers, &t);
+			timer_add(&timers, &t, grains_to_time(timers.base + diff));
 			ok1(timers_check(&timers, NULL));
 			ok1(timers_check(&timers, NULL));
 			timer_del(&timers, &t);
 			timer_del(&timers, &t);
 		}
 		}

+ 60 - 25
ccan/timer/timer.c

@@ -34,19 +34,24 @@ void timers_init(struct timers *timers, struct timespec start)
 
 
 	list_head_init(&timers->far);
 	list_head_init(&timers->far);
 	timers->base = time_to_grains(start);
 	timers->base = time_to_grains(start);
+	timers->first = -1ULL;
 	for (i = 0; i < ARRAY_SIZE(timers->level); i++)
 	for (i = 0; i < ARRAY_SIZE(timers->level); i++)
 		timers->level[i] = NULL;
 		timers->level[i] = NULL;
 }
 }
 
 
-static void timer_add_raw(struct timers *timers, struct timer *t)
+static unsigned int level_of(const struct timers *timers, uint64_t time)
 {
 {
-	struct list_head *l;
 	uint64_t diff;
 	uint64_t diff;
-	unsigned int level;
 
 
 	/* Level depends how far away it is. */
 	/* Level depends how far away it is. */
-	diff = t->time - timers->base;
-	level = ilog64(diff / 2) / TIMER_LEVEL_BITS;
+	diff = time - timers->base;
+	return ilog64(diff / 2) / TIMER_LEVEL_BITS;
+}
+
+static void timer_add_raw(struct timers *timers, struct timer *t)
+{
+	struct list_head *l;
+	unsigned int level = level_of(timers, t->time);
 
 
 	if (!timers->level[level])
 	if (!timers->level[level])
 		l = &timers->far;
 		l = &timers->far;
@@ -65,6 +70,8 @@ void timer_add(struct timers *timers, struct timer *t, struct timespec when)
 	/* Added in the past?  Treat it as imminent. */
 	/* Added in the past?  Treat it as imminent. */
 	if (t->time < timers->base)
 	if (t->time < timers->base)
 		t->time = timers->base;
 		t->time = timers->base;
+	if (t->time < timers->first)
+		timers->first = t->time;
 
 
 	timer_add_raw(timers, t);
 	timer_add_raw(timers, t);
 }
 }
@@ -159,17 +166,26 @@ static const struct timer *find_first(const struct list_head *list,
 	return prev;
 	return prev;
 }
 }
 
 
-static struct timer *get_first(const struct timers *timers)
+static const struct timer *get_first(const struct timers *timers)
 {
 {
-	unsigned int level = 0, i, off;
+	unsigned int level, i, off;
 	bool need_next;
 	bool need_next;
-	uint64_t base = timers->base;
+	uint64_t base;
 	const struct timer *found = NULL;
 	const struct timer *found = NULL;
 	struct list_head *h;
 	struct list_head *h;
 
 
+	if (timers->first < timers->base) {
+		base = timers->base;
+		level = 0;
+	} else {
+		/* May not be accurate, due to timer_del / expiry. */
+		level = level_of(timers, timers->first);
+		base = timers->first >> (TIMER_LEVEL_BITS * level);
+	}
+
 next:
 next:
 	if (!timers->level[level])
 	if (!timers->level[level])
-		return (struct timer *)find_first(&timers->far, NULL);
+		return find_first(&timers->far, NULL);
 
 
 	need_next = false;
 	need_next = false;
 	off = base % PER_LEVEL;
 	off = base % PER_LEVEL;
@@ -206,17 +222,28 @@ next:
 			found = find_first(h, found);
 			found = find_first(h, found);
 		}
 		}
 	}
 	}
-
-	return (struct timer *)found;
+	return found;
 }
 }
 
 
-bool timer_earliest(const struct timers *timers, struct timespec *first)
+static bool update_first(struct timers *timers)
 {
 {
-	struct timer *found = get_first(timers);
+	const struct timer *found = get_first(timers);
+
+	if (!found) {
+		timers->first = -1ULL;
+		return false;
+	}
+
+	timers->first = found->time;
+	return true;
+}
 
 
-	if (!found)
+bool timer_earliest(struct timers *timers, struct timespec *first)
+{
+	if (!update_first(timers))
 		return false;
 		return false;
-	*first = grains_to_time(found->time);
+
+	*first = grains_to_time(timers->first);
 	return true;
 	return true;
 }
 }
 
 
@@ -274,7 +301,6 @@ void timers_expire(struct timers *timers,
 {
 {
 	uint64_t now = time_to_grains(expire);
 	uint64_t now = time_to_grains(expire);
 	unsigned int off;
 	unsigned int off;
-	const struct timer *first;
 
 
 	assert(now >= timers->base);
 	assert(now >= timers->base);
 
 
@@ -286,24 +312,23 @@ void timers_expire(struct timers *timers,
 		add_level(timers, 0);
 		add_level(timers, 0);
 	}
 	}
 
 
-	while ((first = get_first(timers)) != NULL) {
-		assert(first->time >= timers->base);
-		if (first->time > now) {
+	do {
+		if (timers->first > now) {
 			timer_fast_forward(timers, now);
 			timer_fast_forward(timers, now);
 			break;
 			break;
 		}
 		}
 
 
-		timer_fast_forward(timers, first->time);
+		timer_fast_forward(timers, timers->first);
 		off = timers->base % PER_LEVEL;
 		off = timers->base % PER_LEVEL;
 
 
 		list_append_list(list, &timers->level[0]->list[off]);
 		list_append_list(list, &timers->level[0]->list[off]);
 		if (timers->base == now)
 		if (timers->base == now)
 			break;
 			break;
-	}
+	} while (update_first(timers));
 }
 }
 
 
 static bool timer_list_check(const struct list_head *l,
 static bool timer_list_check(const struct list_head *l,
-			     uint64_t min, uint64_t max,
+			     uint64_t min, uint64_t max, uint64_t first,
 			     const char *abortstr)
 			     const char *abortstr)
 {
 {
 	const struct timer *t;
 	const struct timer *t;
@@ -321,6 +346,15 @@ static bool timer_list_check(const struct list_head *l,
 			}
 			}
 			return false;
 			return false;
 		}
 		}
+		if (t->time < first) {
+			if (abortstr) {
+				fprintf(stderr,
+					"%s: timer %p %llu < minimum %llu\n",
+					abortstr, t, t->time, first);
+				abort();
+			}
+			return false;
+		}
 	}
 	}
 	return true;
 	return true;
 }
 }
@@ -341,7 +375,7 @@ struct timers *timers_check(const struct timers *timers, const char *abortstr)
 
 
 		h = &timers->level[l]->list[(i+off) % PER_LEVEL];
 		h = &timers->level[l]->list[(i+off) % PER_LEVEL];
 		if (!timer_list_check(h, timers->base + i, timers->base + i,
 		if (!timer_list_check(h, timers->base + i, timers->base + i,
-				      abortstr))
+				      timers->first, abortstr))
 			return NULL;
 			return NULL;
 	}
 	}
 
 
@@ -359,7 +393,7 @@ struct timers *timers_check(const struct timers *timers, const char *abortstr)
 
 
 			h = &timers->level[l]->list[(i+off) % PER_LEVEL];
 			h = &timers->level[l]->list[(i+off) % PER_LEVEL];
 			if (!timer_list_check(h, base, base + per_bucket - 1,
 			if (!timer_list_check(h, base, base + per_bucket - 1,
-					      abortstr))
+					      timers->first, abortstr))
 				return NULL;
 				return NULL;
 			base += per_bucket;
 			base += per_bucket;
 		}
 		}
@@ -368,7 +402,8 @@ struct timers *timers_check(const struct timers *timers, const char *abortstr)
 past_levels:
 past_levels:
 	base = (timers->base & ~((1ULL << (TIMER_LEVEL_BITS * l)) - 1))
 	base = (timers->base & ~((1ULL << (TIMER_LEVEL_BITS * l)) - 1))
 		+ (1ULL << (TIMER_LEVEL_BITS * l)) - 1;
 		+ (1ULL << (TIMER_LEVEL_BITS * l)) - 1;
-	if (!timer_list_check(&timers->far, base, -1ULL, abortstr))
+	if (!timer_list_check(&timers->far, base, -1ULL, timers->first,
+			      abortstr))
 		return NULL;
 		return NULL;
 
 
 	return (struct timers *)timers;
 	return (struct timers *)timers;

+ 2 - 1
ccan/timer/timer.h

@@ -61,7 +61,7 @@ void timer_del(struct timers *timers, struct timer *timer);
  * timers.  Otherwise, it sets @first to the expiry time of the first
  * timers.  Otherwise, it sets @first to the expiry time of the first
  * timer (rounded to TIMER_GRANULARITY nanoseconds), and returns true.
  * timer (rounded to TIMER_GRANULARITY nanoseconds), and returns true.
  */
  */
-bool timer_earliest(const struct timers *timers, struct timespec *first);
+bool timer_earliest(struct timers *timers, struct timespec *first);
 
 
 /**
 /**
  * timer_expire - update timers structure and remove expired timers.
  * timer_expire - update timers structure and remove expired timers.
@@ -123,6 +123,7 @@ struct timers {
 	/* Far in the future. */
 	/* Far in the future. */
 	struct list_head far;
 	struct list_head far;
 	uint64_t base;
 	uint64_t base;
+	uint64_t first;
 
 
 	struct timer_level *level[(64 + TIMER_LEVEL_BITS-1) / TIMER_LEVEL_BITS];
 	struct timer_level *level[(64 + TIMER_LEVEL_BITS-1) / TIMER_LEVEL_BITS];
 };
 };