MEDIUM: stick-table: make stktable_get_entry() look up under a read lock

On a 24-core machine doing lots of track-sc, it was found that the lock
in stktable_get_entry() was responsible for 25% of the CPU alone. It's
sad because most of its job is to protect the table during the lookup.

Here we're taking a slightly different approach: the lock is first taken
for reads during the lookup, and only in case of failure we switch it for
a write lock. We don't even perform an upgrade here since an allocation
is needed between the two, it would be wasted to do it under the lock,
and is generally not a good idea, so better release the read lock and
try again.

Here the performance under 48 threads with 3 trackers on the same table
jumped from 455k to 2.07M, or 4.55x! Note that the same approach should
be possible for stktable_set_entry().
This commit is contained in:
Willy Tarreau 2022-10-11 15:22:42 +02:00
parent a7d6a1396e
commit 47f229702e
1 changed files with 30 additions and 31 deletions

View File

@ -519,45 +519,44 @@ struct stksess *__stktable_store(struct stktable *t, struct stksess *ts)
/* Returns a valid or initialized stksess for the specified stktable_key in the /* Returns a valid or initialized stksess for the specified stktable_key in the
* specified table, or NULL if the key was NULL, or if no entry was found nor * specified table, or NULL if the key was NULL, or if no entry was found nor
* could be created. The entry's expiration is updated. * could be created. The entry's expiration is updated. This function locks the
* table, and the refcount of the entry is increased.
*/ */
struct stksess *__stktable_get_entry(struct stktable *table, struct stktable_key *key) struct stksess *stktable_get_entry(struct stktable *table, struct stktable_key *key)
{ {
struct stksess *ts, *ts2; struct stksess *ts, *ts2;
if (!key) if (!key)
return NULL; return NULL;
ts = __stktable_lookup_key(table, key); ts = stktable_lookup_key(table, key);
if (ts == NULL) {
/* entry does not exist, initialize a new one */
ts = __stksess_new(table, key);
if (!ts)
return NULL;
ts2 = __stktable_store(table, ts);
if (unlikely(ts2 != ts)) {
/* another entry was added in the mean time, let's
* switch to it.
*/
__stksess_free(table, ts);
ts = ts2;
}
}
return ts;
}
/* Returns a valid or initialized stksess for the specified stktable_key in the
* specified table, or NULL if the key was NULL, or if no entry was found nor
* could be created. The entry's expiration is updated.
* This function locks the table, and the refcount of the entry is increased.
*/
struct stksess *stktable_get_entry(struct stktable *table, struct stktable_key *key)
{
struct stksess *ts;
HA_RWLOCK_WRLOCK(STK_TABLE_LOCK, &table->lock);
ts = __stktable_get_entry(table, key);
if (ts) if (ts)
ts->ref_cnt++; return ts;
/* No such entry exists, let's try to create a new one. For this we'll
* need an exclusive access. We don't need an atomic upgrade, this is
* rare and an unlock+lock sequence will do the job fine. Given that
* this will not be atomic, the missing entry might appear in the mean
* tome so we have to be careful that the one we try to insert is the
* one we find.
*/
HA_RWLOCK_WRLOCK(STK_TABLE_LOCK, &table->lock);
ts = __stksess_new(table, key);
if (!ts)
goto end;
ts2 = __stktable_store(table, ts);
if (unlikely(ts2 != ts)) {
/* another entry was added in the mean time, let's
* switch to it.
*/
__stksess_free(table, ts);
ts = ts2;
}
HA_ATOMIC_INC(&ts->ref_cnt);
end:
HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &table->lock); HA_RWLOCK_WRUNLOCK(STK_TABLE_LOCK, &table->lock);
return ts; return ts;