mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-23 20:24:12 +08:00
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/20211028130027.18764-1-tiwai@suse.de Link: https://lore.kernel.org/r/20220609180504.775-1-tiwai@suse.de Link: https://lore.kernel.org/all/cover.1653813866.git.quic_rbankapu@quicinc.com/ Link: https://lore.kernel.org/r/20220610064537.18660-1-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
parent
b13baccc38
commit
c27e1efb61
@ -14,6 +14,7 @@
|
|||||||
#include <linux/pm.h> /* pm_message_t */
|
#include <linux/pm.h> /* pm_message_t */
|
||||||
#include <linux/stringify.h>
|
#include <linux/stringify.h>
|
||||||
#include <linux/printk.h>
|
#include <linux/printk.h>
|
||||||
|
#include <linux/xarray.h>
|
||||||
|
|
||||||
/* number of supported soundcards */
|
/* number of supported soundcards */
|
||||||
#ifdef CONFIG_SND_DYNAMIC_MINORS
|
#ifdef CONFIG_SND_DYNAMIC_MINORS
|
||||||
@ -103,6 +104,11 @@ struct snd_card {
|
|||||||
size_t user_ctl_alloc_size; // current memory allocation by user controls.
|
size_t user_ctl_alloc_size; // current memory allocation by user controls.
|
||||||
struct list_head controls; /* all controls for this card */
|
struct list_head controls; /* all controls for this card */
|
||||||
struct list_head ctl_files; /* active control files */
|
struct list_head ctl_files; /* active control files */
|
||||||
|
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
|
||||||
|
struct xarray ctl_numids; /* hash table for numids */
|
||||||
|
struct xarray ctl_hash; /* hash table for ctl id matching */
|
||||||
|
bool ctl_hash_collision; /* ctl_hash collision seen? */
|
||||||
|
#endif
|
||||||
|
|
||||||
struct snd_info_entry *proc_root; /* root for soundcard specific files */
|
struct snd_info_entry *proc_root; /* root for soundcard specific files */
|
||||||
struct proc_dir_entry *proc_root_link; /* number link to real id */
|
struct proc_dir_entry *proc_root_link; /* number link to real id */
|
||||||
|
@ -154,6 +154,16 @@ config SND_VERBOSE_PRINTK
|
|||||||
|
|
||||||
You don't need this unless you're debugging ALSA.
|
You don't need this unless you're debugging ALSA.
|
||||||
|
|
||||||
|
config SND_CTL_FAST_LOOKUP
|
||||||
|
bool "Fast lookup of control elements" if EXPERT
|
||||||
|
default y
|
||||||
|
select XARRAY_MULTI
|
||||||
|
help
|
||||||
|
This option enables the faster lookup of control elements.
|
||||||
|
It will consume more memory because of the additional Xarray.
|
||||||
|
If you want to choose the memory footprint over the performance
|
||||||
|
inevitably, turn this off.
|
||||||
|
|
||||||
config SND_DEBUG
|
config SND_DEBUG
|
||||||
bool "Debug"
|
bool "Debug"
|
||||||
help
|
help
|
||||||
|
@ -364,6 +364,93 @@ static int snd_ctl_find_hole(struct snd_card *card, unsigned int count)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* check whether the given id is contained in the given kctl */
|
||||||
|
static bool elem_id_matches(const struct snd_kcontrol *kctl,
|
||||||
|
const struct snd_ctl_elem_id *id)
|
||||||
|
{
|
||||||
|
return kctl->id.iface == id->iface &&
|
||||||
|
kctl->id.device == id->device &&
|
||||||
|
kctl->id.subdevice == id->subdevice &&
|
||||||
|
!strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)) &&
|
||||||
|
kctl->id.index <= id->index &&
|
||||||
|
kctl->id.index + kctl->count > id->index;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
|
||||||
|
/* Compute a hash key for the corresponding ctl id
|
||||||
|
* It's for the name lookup, hence the numid is excluded.
|
||||||
|
* The hash key is bound in LONG_MAX to be used for Xarray key.
|
||||||
|
*/
|
||||||
|
#define MULTIPLIER 37
|
||||||
|
static unsigned long get_ctl_id_hash(const struct snd_ctl_elem_id *id)
|
||||||
|
{
|
||||||
|
unsigned long h;
|
||||||
|
const unsigned char *p;
|
||||||
|
|
||||||
|
h = id->iface;
|
||||||
|
h = MULTIPLIER * h + id->device;
|
||||||
|
h = MULTIPLIER * h + id->subdevice;
|
||||||
|
for (p = id->name; *p; p++)
|
||||||
|
h = MULTIPLIER * h + *p;
|
||||||
|
h = MULTIPLIER * h + id->index;
|
||||||
|
h &= LONG_MAX;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add hash entries to numid and ctl xarray tables */
|
||||||
|
static void add_hash_entries(struct snd_card *card,
|
||||||
|
struct snd_kcontrol *kcontrol)
|
||||||
|
{
|
||||||
|
struct snd_ctl_elem_id id = kcontrol->id;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
xa_store_range(&card->ctl_numids, kcontrol->id.numid,
|
||||||
|
kcontrol->id.numid + kcontrol->count - 1,
|
||||||
|
kcontrol, GFP_KERNEL);
|
||||||
|
|
||||||
|
for (i = 0; i < kcontrol->count; i++) {
|
||||||
|
id.index = kcontrol->id.index + i;
|
||||||
|
if (xa_insert(&card->ctl_hash, get_ctl_id_hash(&id),
|
||||||
|
kcontrol, GFP_KERNEL)) {
|
||||||
|
/* skip hash for this entry, noting we had collision */
|
||||||
|
card->ctl_hash_collision = true;
|
||||||
|
dev_dbg(card->dev, "ctl_hash collision %d:%s:%d\n",
|
||||||
|
id.iface, id.name, id.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove hash entries that have been added */
|
||||||
|
static void remove_hash_entries(struct snd_card *card,
|
||||||
|
struct snd_kcontrol *kcontrol)
|
||||||
|
{
|
||||||
|
struct snd_ctl_elem_id id = kcontrol->id;
|
||||||
|
struct snd_kcontrol *matched;
|
||||||
|
unsigned long h;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < kcontrol->count; i++) {
|
||||||
|
xa_erase(&card->ctl_numids, id.numid);
|
||||||
|
h = get_ctl_id_hash(&id);
|
||||||
|
matched = xa_load(&card->ctl_hash, h);
|
||||||
|
if (matched && (matched == kcontrol ||
|
||||||
|
elem_id_matches(matched, &id)))
|
||||||
|
xa_erase(&card->ctl_hash, h);
|
||||||
|
id.index++;
|
||||||
|
id.numid++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else /* CONFIG_SND_CTL_FAST_LOOKUP */
|
||||||
|
static inline void add_hash_entries(struct snd_card *card,
|
||||||
|
struct snd_kcontrol *kcontrol)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
static inline void remove_hash_entries(struct snd_card *card,
|
||||||
|
struct snd_kcontrol *kcontrol)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_SND_CTL_FAST_LOOKUP */
|
||||||
|
|
||||||
enum snd_ctl_add_mode {
|
enum snd_ctl_add_mode {
|
||||||
CTL_ADD_EXCLUSIVE, CTL_REPLACE, CTL_ADD_ON_REPLACE,
|
CTL_ADD_EXCLUSIVE, CTL_REPLACE, CTL_ADD_ON_REPLACE,
|
||||||
};
|
};
|
||||||
@ -408,6 +495,8 @@ static int __snd_ctl_add_replace(struct snd_card *card,
|
|||||||
kcontrol->id.numid = card->last_numid + 1;
|
kcontrol->id.numid = card->last_numid + 1;
|
||||||
card->last_numid += kcontrol->count;
|
card->last_numid += kcontrol->count;
|
||||||
|
|
||||||
|
add_hash_entries(card, kcontrol);
|
||||||
|
|
||||||
for (idx = 0; idx < kcontrol->count; idx++)
|
for (idx = 0; idx < kcontrol->count; idx++)
|
||||||
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx);
|
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_ADD, kcontrol, idx);
|
||||||
|
|
||||||
@ -479,6 +568,26 @@ int snd_ctl_replace(struct snd_card *card, struct snd_kcontrol *kcontrol,
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_ctl_replace);
|
EXPORT_SYMBOL(snd_ctl_replace);
|
||||||
|
|
||||||
|
static int __snd_ctl_remove(struct snd_card *card,
|
||||||
|
struct snd_kcontrol *kcontrol,
|
||||||
|
bool remove_hash)
|
||||||
|
{
|
||||||
|
unsigned int idx;
|
||||||
|
|
||||||
|
if (snd_BUG_ON(!card || !kcontrol))
|
||||||
|
return -EINVAL;
|
||||||
|
list_del(&kcontrol->list);
|
||||||
|
|
||||||
|
if (remove_hash)
|
||||||
|
remove_hash_entries(card, kcontrol);
|
||||||
|
|
||||||
|
card->controls_count -= kcontrol->count;
|
||||||
|
for (idx = 0; idx < kcontrol->count; idx++)
|
||||||
|
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx);
|
||||||
|
snd_ctl_free_one(kcontrol);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* snd_ctl_remove - remove the control from the card and release it
|
* snd_ctl_remove - remove the control from the card and release it
|
||||||
* @card: the card instance
|
* @card: the card instance
|
||||||
@ -492,16 +601,7 @@ EXPORT_SYMBOL(snd_ctl_replace);
|
|||||||
*/
|
*/
|
||||||
int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol)
|
int snd_ctl_remove(struct snd_card *card, struct snd_kcontrol *kcontrol)
|
||||||
{
|
{
|
||||||
unsigned int idx;
|
return __snd_ctl_remove(card, kcontrol, true);
|
||||||
|
|
||||||
if (snd_BUG_ON(!card || !kcontrol))
|
|
||||||
return -EINVAL;
|
|
||||||
list_del(&kcontrol->list);
|
|
||||||
card->controls_count -= kcontrol->count;
|
|
||||||
for (idx = 0; idx < kcontrol->count; idx++)
|
|
||||||
snd_ctl_notify_one(card, SNDRV_CTL_EVENT_MASK_REMOVE, kcontrol, idx);
|
|
||||||
snd_ctl_free_one(kcontrol);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_ctl_remove);
|
EXPORT_SYMBOL(snd_ctl_remove);
|
||||||
|
|
||||||
@ -642,14 +742,30 @@ int snd_ctl_rename_id(struct snd_card *card, struct snd_ctl_elem_id *src_id,
|
|||||||
up_write(&card->controls_rwsem);
|
up_write(&card->controls_rwsem);
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
remove_hash_entries(card, kctl);
|
||||||
kctl->id = *dst_id;
|
kctl->id = *dst_id;
|
||||||
kctl->id.numid = card->last_numid + 1;
|
kctl->id.numid = card->last_numid + 1;
|
||||||
card->last_numid += kctl->count;
|
card->last_numid += kctl->count;
|
||||||
|
add_hash_entries(card, kctl);
|
||||||
up_write(&card->controls_rwsem);
|
up_write(&card->controls_rwsem);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_ctl_rename_id);
|
EXPORT_SYMBOL(snd_ctl_rename_id);
|
||||||
|
|
||||||
|
#ifndef CONFIG_SND_CTL_FAST_LOOKUP
|
||||||
|
static struct snd_kcontrol *
|
||||||
|
snd_ctl_find_numid_slow(struct snd_card *card, unsigned int numid)
|
||||||
|
{
|
||||||
|
struct snd_kcontrol *kctl;
|
||||||
|
|
||||||
|
list_for_each_entry(kctl, &card->controls, list) {
|
||||||
|
if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid)
|
||||||
|
return kctl;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif /* !CONFIG_SND_CTL_FAST_LOOKUP */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* snd_ctl_find_numid - find the control instance with the given number-id
|
* snd_ctl_find_numid - find the control instance with the given number-id
|
||||||
* @card: the card instance
|
* @card: the card instance
|
||||||
@ -665,15 +781,13 @@ EXPORT_SYMBOL(snd_ctl_rename_id);
|
|||||||
*/
|
*/
|
||||||
struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, unsigned int numid)
|
struct snd_kcontrol *snd_ctl_find_numid(struct snd_card *card, unsigned int numid)
|
||||||
{
|
{
|
||||||
struct snd_kcontrol *kctl;
|
|
||||||
|
|
||||||
if (snd_BUG_ON(!card || !numid))
|
if (snd_BUG_ON(!card || !numid))
|
||||||
return NULL;
|
return NULL;
|
||||||
list_for_each_entry(kctl, &card->controls, list) {
|
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
|
||||||
if (kctl->id.numid <= numid && kctl->id.numid + kctl->count > numid)
|
return xa_load(&card->ctl_numids, numid);
|
||||||
return kctl;
|
#else
|
||||||
}
|
return snd_ctl_find_numid_slow(card, numid);
|
||||||
return NULL;
|
#endif
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_ctl_find_numid);
|
EXPORT_SYMBOL(snd_ctl_find_numid);
|
||||||
|
|
||||||
@ -699,21 +813,18 @@ struct snd_kcontrol *snd_ctl_find_id(struct snd_card *card,
|
|||||||
return NULL;
|
return NULL;
|
||||||
if (id->numid != 0)
|
if (id->numid != 0)
|
||||||
return snd_ctl_find_numid(card, id->numid);
|
return snd_ctl_find_numid(card, id->numid);
|
||||||
list_for_each_entry(kctl, &card->controls, list) {
|
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
|
||||||
if (kctl->id.iface != id->iface)
|
kctl = xa_load(&card->ctl_hash, get_ctl_id_hash(id));
|
||||||
continue;
|
if (kctl && elem_id_matches(kctl, id))
|
||||||
if (kctl->id.device != id->device)
|
|
||||||
continue;
|
|
||||||
if (kctl->id.subdevice != id->subdevice)
|
|
||||||
continue;
|
|
||||||
if (strncmp(kctl->id.name, id->name, sizeof(kctl->id.name)))
|
|
||||||
continue;
|
|
||||||
if (kctl->id.index > id->index)
|
|
||||||
continue;
|
|
||||||
if (kctl->id.index + kctl->count <= id->index)
|
|
||||||
continue;
|
|
||||||
return kctl;
|
return kctl;
|
||||||
}
|
if (!card->ctl_hash_collision)
|
||||||
|
return NULL; /* we can rely on only hash table */
|
||||||
|
#endif
|
||||||
|
/* no matching in hash table - try all as the last resort */
|
||||||
|
list_for_each_entry(kctl, &card->controls, list)
|
||||||
|
if (elem_id_matches(kctl, id))
|
||||||
|
return kctl;
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(snd_ctl_find_id);
|
EXPORT_SYMBOL(snd_ctl_find_id);
|
||||||
@ -2195,8 +2306,13 @@ static int snd_ctl_dev_free(struct snd_device *device)
|
|||||||
down_write(&card->controls_rwsem);
|
down_write(&card->controls_rwsem);
|
||||||
while (!list_empty(&card->controls)) {
|
while (!list_empty(&card->controls)) {
|
||||||
control = snd_kcontrol(card->controls.next);
|
control = snd_kcontrol(card->controls.next);
|
||||||
snd_ctl_remove(card, control);
|
__snd_ctl_remove(card, control, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
|
||||||
|
xa_destroy(&card->ctl_numids);
|
||||||
|
xa_destroy(&card->ctl_hash);
|
||||||
|
#endif
|
||||||
up_write(&card->controls_rwsem);
|
up_write(&card->controls_rwsem);
|
||||||
put_device(&card->ctl_dev);
|
put_device(&card->ctl_dev);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -310,6 +310,10 @@ static int snd_card_init(struct snd_card *card, struct device *parent,
|
|||||||
rwlock_init(&card->ctl_files_rwlock);
|
rwlock_init(&card->ctl_files_rwlock);
|
||||||
INIT_LIST_HEAD(&card->controls);
|
INIT_LIST_HEAD(&card->controls);
|
||||||
INIT_LIST_HEAD(&card->ctl_files);
|
INIT_LIST_HEAD(&card->ctl_files);
|
||||||
|
#ifdef CONFIG_SND_CTL_FAST_LOOKUP
|
||||||
|
xa_init(&card->ctl_numids);
|
||||||
|
xa_init(&card->ctl_hash);
|
||||||
|
#endif
|
||||||
spin_lock_init(&card->files_lock);
|
spin_lock_init(&card->files_lock);
|
||||||
INIT_LIST_HEAD(&card->files_list);
|
INIT_LIST_HEAD(&card->files_list);
|
||||||
mutex_init(&card->memory_mutex);
|
mutex_init(&card->memory_mutex);
|
||||||
|
Loading…
Reference in New Issue
Block a user