Skip to content

Commit c27e1ef

Browse files
committed
ALSA: control: Use xarray for faster lookups
The control elements are managed in a single linked list and we traverse the whole list for matching each numid or ctl id per every inquiry of a control element. This is OK-ish for a small number of elements but obviously it doesn't scale. Especially the matching with the ctl id takes time because it checks each field of the snd_ctl_id element, e.g. the name string is matched with strcmp(). This patch adds the hash tables with Xarray for improving the lookup speed of a control element. There are two xarray tables added to the card; one for numid and another for ctl id. For the numid, we use the numid as the index, while for the ctl id, we calculate a hash key. The lookup is done via a single xa_load() execution. As long as the given control element is found on the Xarray table, that's fine, we can give back a quick lookup result. The problem is when no entry hits on the table, and for this case, we have a slight optimization. Namely, the driver checks whether we had a collision on Xarray table, and do a fallback search (linear lookup of the full entries) only if a hash key collision happened beforehand. So, in theory, the inquiry for a non-existing element might take still time even with this patch in a worst case, but this must be pretty rare. The feature is enabled via CONFIG_SND_CTL_FAST_LOOKUP, which is turned on as default. For simplicity, the option can be turned off only when CONFIG_EXPERT is set ("You are expert? Then you manage 1000 knobs"). Link: https://lore.kernel.org/r/[email protected] Link: https://lore.kernel.org/r/[email protected] Link: https://lore.kernel.org/all/[email protected]/ Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Takashi Iwai <[email protected]>
1 parent b13bacc commit c27e1ef

File tree

4 files changed

+168
-32
lines changed

4 files changed

+168
-32
lines changed

include/sound/core.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <linux/pm.h> /* pm_message_t */
1515
#include <linux/stringify.h>
1616
#include <linux/printk.h>
17+
#include <linux/xarray.h>
1718

1819
/* number of supported soundcards */
1920
#ifdef CONFIG_SND_DYNAMIC_MINORS
@@ -103,6 +104,11 @@ struct snd_card {
103104
size_t user_ctl_alloc_size; // current memory allocation by user controls.
104105
struct list_head controls; /* all controls for this card */
105106
struct list_head ctl_files; /* active control files */
107+
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
108+
struct xarray ctl_numids; /* hash table for numids */
109+
struct xarray ctl_hash; /* hash table for ctl id matching */
110+
bool ctl_hash_collision; /* ctl_hash collision seen? */
111+
#endif
106112

107113
struct snd_info_entry *proc_root; /* root for soundcard specific files */
108114
struct proc_dir_entry *proc_root_link; /* number link to real id */

sound/core/Kconfig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,16 @@ config SND_VERBOSE_PRINTK
154154

155155
You don't need this unless you're debugging ALSA.
156156

157+
config SND_CTL_FAST_LOOKUP
158+
bool "Fast lookup of control elements" if EXPERT
159+
default y
160+
select XARRAY_MULTI
161+
help
162+
This option enables the faster lookup of control elements.
163+
It will consume more memory because of the additional Xarray.
164+
If you want to choose the memory footprint over the performance
165+
inevitably, turn this off.
166+
157167
config SND_DEBUG
158168
bool "Debug"
159169
help

sound/core/control.c

Lines changed: 148 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,93 @@ static int snd_ctl_find_hole(struct snd_card *card, unsigned int count)
364364
return 0;
365365
}
366366

367+
/* check whether the given id is contained in the given kctl */
368+
static bool elem_id_matches(const struct snd_kcontrol *kctl,
369+
const struct snd_ctl_elem_id *id)
370+
{
371+
return kctl->id.iface == id->iface &&
372+
kctl->id.device == id->device &&
373+
kctl->id.subdevice == id->subdevice &&
374+
!strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)) &&
375+
kctl->id.index <= id->index &&
376+
kctl->id.index + kctl->count > id->index;
377+
}
378+
379+
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
380+
/* Compute a hash key for the corresponding ctl id
381+
* It's for the name lookup, hence the numid is excluded.
382+
* The hash key is bound in LONG_MAX to be used for Xarray key.
383+
*/
384+
#define MULTIPLIER 37
385+
static unsigned long get_ctl_id_hash(const struct snd_ctl_elem_id *id)
386+
{
387+
unsigned long h;
388+
const unsigned char *p;
389+
390+
h = id->iface;
391+
h = MULTIPLIER * h + id->device;
392+
h = MULTIPLIER * h + id->subdevice;
393+
for (p = id->name; *p; p++)
394+
h = MULTIPLIER * h + *p;
395+
h = MULTIPLIER * h + id->index;
396+
h &= LONG_MAX;
397+
return h;
398+
}
399+
400+
/* add hash entries to numid and ctl xarray tables */
401+
static void add_hash_entries(struct snd_card *card,
402+
struct snd_kcontrol *kcontrol)
403+
{
404+
struct snd_ctl_elem_id id = kcontrol->id;
405+
int i;
406+
407+
xa_store_range(&card->ctl_numids, kcontrol->id.numid,
408+
kcontrol->id.numid + kcontrol->count - 1,
409+
kcontrol, GFP_KERNEL);
410+
411+
for (i = 0; i < kcontrol->count; i++) {
412+
id.index = kcontrol->id.index + i;
413+
if (xa_insert(&card->ctl_hash, get_ctl_id_hash(&id),
414+
kcontrol, GFP_KERNEL)) {
415+
/* skip hash for this entry, noting we had collision */
416+
card->ctl_hash_collision = true;
417+
dev_dbg(card->dev, "ctl_hash collision %d:%s:%d\n",
418+
id.iface, id.name, id.index);
419+
}
420+
}
421+
}
422+
423+
/* remove hash entries that have been added */
424+
static void remove_hash_entries(struct snd_card *card,
425+
struct snd_kcontrol *kcontrol)
426+
{
427+
struct snd_ctl_elem_id id = kcontrol->id;
428+
struct snd_kcontrol *matched;
429+
unsigned long h;
430+
int i;
431+
432+
for (i = 0; i < kcontrol->count; i++) {
433+
xa_erase(&card->ctl_numids, id.numid);
434+
h = get_ctl_id_hash(&id);
435+
matched = xa_load(&card->ctl_hash, h);
436+
if (matched && (matched == kcontrol ||
437+
elem_id_matches(matched, &id)))
438+
xa_erase(&card->ctl_hash, h);
439+
id.index++;
440+
id.numid++;
441+
}
442+
}
443+
#else /* CONFIG_SND_CTL_FAST_LOOKUP */
444+
static inline void add_hash_entries(struct snd_card *card,
445+
struct snd_kcontrol *kcontrol)
446+
{
447+
}
448+
static inline void remove_hash_entries(struct snd_card *card,
449+
struct snd_kcontrol *kcontrol)
450+
{
451+
}
452+
#endif /* CONFIG_SND_CTL_FAST_LOOKUP */
453+
367454
enum snd_ctl_add_mode {
368455
CTL_ADD_EXCLUSIVE, CTL_REPLACE, CTL_ADD_ON_REPLACE,
369456
};
@@ -408,6 +495,8 @@ static int __snd_ctl_add_replace(struct snd_card *card,
408495
kcontrol->id.numid = card->last_numid + 1;
409496
card->last_numid += kcontrol->count;
410497

498+
add_hash_entries(card, kcontrol);
499+
411500
for (idx = 0; idx < kcontrol->count; idx++)
412501
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx);
413502

@@ -479,6 +568,26 @@ int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol,
479568
}
480569
EXPORT_SYMBOL(snd_ctl_replace);
481570

571+
static int __snd_ctl_remove(struct snd_card *card,
572+
struct snd_kcontrol *kcontrol,
573+
bool remove_hash)
574+
{
575+
unsigned int idx;
576+
577+
if (snd_BUG_ON(!card || !kcontrol))
578+
return -EINVAL;
579+
list_del(&kcontrol->list);
580+
581+
if (remove_hash)
582+
remove_hash_entries(card, kcontrol);
583+
584+
card->controls_count -= kcontrol->count;
585+
for (idx = 0; idx < kcontrol->count; idx++)
586+
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx);
587+
snd_ctl_free_one(kcontrol);
588+
return 0;
589+
}
590+
482591
/**
483592
* snd_ctl_remove - remove the control from the card and release it
484593
* @card: the card instance
@@ -492,16 +601,7 @@ EXPORT_SYMBOL(snd_ctl_replace);
492601
*/
493602
int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol)
494603
{
495-
unsigned int idx;
496-
497-
if (snd_BUG_ON(!card || !kcontrol))
498-
return -EINVAL;
499-
list_del(&kcontrol->list);
500-
card->controls_count -= kcontrol->count;
501-
for (idx = 0; idx < kcontrol->count; idx++)
502-
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx);
503-
snd_ctl_free_one(kcontrol);
504-
return 0;
604+
return __snd_ctl_remove(card, kcontrol, true);
505605
}
506606
EXPORT_SYMBOL(snd_ctl_remove);
507607

@@ -642,14 +742,30 @@ int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id,
642742
up_write(&card->controls_rwsem);
643743
return -ENOENT;
644744
}
745+
remove_hash_entries(card, kctl);
645746
kctl->id = *dst_id;
646747
kctl->id.numid = card->last_numid + 1;
647748
card->last_numid += kctl->count;
749+
add_hash_entries(card, kctl);
648750
up_write(&card->controls_rwsem);
649751
return 0;
650752
}
651753
EXPORT_SYMBOL(snd_ctl_rename_id);
652754

755+
#ifndef CONFIG_SND_CTL_FAST_LOOKUP
756+
static struct snd_kcontrol *
757+
snd_ctl_find_numid_slow(struct snd_card *card, unsigned int numid)
758+
{
759+
struct snd_kcontrol *kctl;
760+
761+
list_for_each_entry(kctl, &card->controls, list) {
762+
if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid)
763+
return kctl;
764+
}
765+
return NULL;
766+
}
767+
#endif /* !CONFIG_SND_CTL_FAST_LOOKUP */
768+
653769
/**
654770
* snd_ctl_find_numid - find the control instance with the given number-id
655771
* @card: the card instance
@@ -665,15 +781,13 @@ EXPORT_SYMBOL(snd_ctl_rename_id);
665781
*/
666782
struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, unsigned int numid)
667783
{
668-
struct snd_kcontrol *kctl;
669-
670784
if (snd_BUG_ON(!card || !numid))
671785
return NULL;
672-
list_for_each_entry(kctl, &card->controls, list) {
673-
if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid)
674-
return kctl;
675-
}
676-
return NULL;
786+
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
787+
return xa_load(&card->ctl_numids, numid);
788+
#else
789+
return snd_ctl_find_numid_slow(card, numid);
790+
#endif
677791
}
678792
EXPORT_SYMBOL(snd_ctl_find_numid);
679793

@@ -699,21 +813,18 @@ struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card,
699813
return NULL;
700814
if (id->numid != 0)
701815
return snd_ctl_find_numid(card, id->numid);
702-
list_for_each_entry(kctl, &card->controls, list) {
703-
if (kctl->id.iface != id->iface)
704-
continue;
705-
if (kctl->id.device != id->device)
706-
continue;
707-
if (kctl->id.subdevice != id->subdevice)
708-
continue;
709-
if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)))
710-
continue;
711-
if (kctl->id.index > id->index)
712-
continue;
713-
if (kctl->id.index + kctl->count <= id->index)
714-
continue;
816+
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
817+
kctl = xa_load(&card->ctl_hash, get_ctl_id_hash(id));
818+
if (kctl && elem_id_matches(kctl, id))
715819
return kctl;
716-
}
820+
if (!card->ctl_hash_collision)
821+
return NULL; /* we can rely on only hash table */
822+
#endif
823+
/* no matching in hash table - try all as the last resort */
824+
list_for_each_entry(kctl, &card->controls, list)
825+
if (elem_id_matches(kctl, id))
826+
return kctl;
827+
717828
return NULL;
718829
}
719830
EXPORT_SYMBOL(snd_ctl_find_id);
@@ -2195,8 +2306,13 @@ static int snd_ctl_dev_free(struct snd_device *device)
21952306
down_write(&card->controls_rwsem);
21962307
while (!list_empty(&card->controls)) {
21972308
control = snd_kcontrol(card->controls.next);
2198-
snd_ctl_remove(card, control);
2309+
__snd_ctl_remove(card, control, false);
21992310
}
2311+
2312+
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
2313+
xa_destroy(&card->ctl_numids);
2314+
xa_destroy(&card->ctl_hash);
2315+
#endif
22002316
up_write(&card->controls_rwsem);
22012317
put_device(&card->ctl_dev);
22022318
return 0;

sound/core/init.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ static int snd_card_init(struct snd_card *card, struct device *parent,
310310
rwlock_init(&card->ctl_files_rwlock);
311311
INIT_LIST_HEAD(&card->controls);
312312
INIT_LIST_HEAD(&card->ctl_files);
313+
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
314+
xa_init(&card->ctl_numids);
315+
xa_init(&card->ctl_hash);
316+
#endif
313317
spin_lock_init(&card->files_lock);
314318
INIT_LIST_HEAD(&card->files_list);
315319
mutex_init(&card->memory_mutex);

0 commit comments

Comments
 (0)