Skip to content

Commit b435da0

Browse files
mattnchrisbra
authored andcommitted
patch 9.2.0240: syn_name2id() is slow due to linear search
Problem: Looking up highlight group names uses a linear scan of the highlight table, which is slow at startup when many groups are defined. Solution: Use a hashtable for O(1) highlight group name lookup. (Yasuhiro Matsumoto). Benchmark (523 highlight groups, :highlight + highlight link + syntax keyword + hlID() + redraw, 20 iterations): master: 0.057 sec (~295,000 ops/sec) this branch: 0.036 sec (~463,000 ops/sec) speedup: ~1.57x closes: #19788 Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent 955c02d commit b435da0

2 files changed

Lines changed: 86 additions & 38 deletions

File tree

src/highlight.c

Lines changed: 84 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,21 @@ static int hl_flags[HLF_COUNT] = HL_FLAGS;
207207
static garray_T highlight_ga;
208208
#define HL_TABLE() ((hl_group_T *)((highlight_ga.ga_data)))
209209

210+
// Wrapper struct for hashtable entries: stores the highlight group ID alongside
211+
// the uppercase name used as a hash key. Uses the same offsetof pattern as
212+
// signgroup_T / HI2SG.
213+
typedef struct {
214+
int hn_id; // highlight group ID (1-based)
215+
char_u hn_key[1]; // uppercase name (stretchable array)
216+
} hlname_T;
217+
218+
#define HLNAME_KEY_OFF offsetof(hlname_T, hn_key)
219+
#define HI2HLNAME(hi) ((hlname_T *)((hi)->hi_key - HLNAME_KEY_OFF))
220+
221+
// hashtable for quick highlight group name lookup
222+
static hashtab_T highlight_ht;
223+
static bool highlight_ht_inited = false;
224+
210225
/*
211226
* An attribute number is the index in attr_table plus ATTR_OFF.
212227
*/
@@ -217,6 +232,7 @@ static void set_hl_attr(int idx);
217232
static void highlight_list_one(int id);
218233
static int highlight_list_arg(int id, int didh, int type, int iarg, char_u *sarg, char *name);
219234
static int syn_add_group(char_u *name);
235+
static int syn_name2id_len(char_u *name, int len);
220236
static int hl_has_settings(int idx, int check_link);
221237
static void highlight_clear(int idx);
222238

@@ -2034,9 +2050,11 @@ free_highlight(void)
20342050
{
20352051
highlight_clear(i);
20362052
vim_free(HL_TABLE()[i].sg_name);
2037-
vim_free(HL_TABLE()[i].sg_name_u);
2053+
vim_free(HL_TABLE()[i].sg_name_u - HLNAME_KEY_OFF);
20382054
}
20392055
ga_clear(&highlight_ga);
2056+
hash_clear(&highlight_ht);
2057+
highlight_ht_inited = false;
20402058
}
20412059
#endif
20422060

@@ -3810,25 +3828,37 @@ syn_override(int id)
38103828
}
38113829

38123830
/*
3813-
* Lookup a highlight group name and return its ID.
3831+
* Lookup a highlight group name by pointer and length.
38143832
* If it is not found, 0 is returned.
38153833
*/
3816-
int
3817-
syn_name2id(char_u *name)
3834+
static int
3835+
syn_name2id_len(char_u *name, int len)
38183836
{
3819-
int i;
38203837
char_u name_u[MAX_SYN_NAME + 1];
3838+
hashitem_T *hi;
38213839

3822-
// Avoid using stricmp() too much, it's slow on some systems
3823-
// Avoid alloc()/free(), these are slow too. ID names over 200 chars
3824-
// don't deserve to be found!
3825-
vim_strncpy(name_u, name, MAX_SYN_NAME);
3840+
if (len > MAX_SYN_NAME)
3841+
len = MAX_SYN_NAME;
3842+
mch_memmove(name_u, name, len);
3843+
name_u[len] = NUL;
38263844
vim_strup(name_u);
3827-
for (i = highlight_ga.ga_len; --i >= 0; )
3828-
if (HL_TABLE()[i].sg_name_u != NULL
3829-
&& STRCMP(name_u, HL_TABLE()[i].sg_name_u) == 0)
3830-
break;
3831-
return i + 1;
3845+
if (!highlight_ht_inited)
3846+
return 0;
3847+
hi = hash_find(&highlight_ht, name_u);
3848+
if (HASHITEM_EMPTY(hi))
3849+
return 0;
3850+
return HI2HLNAME(hi)->hn_id;
3851+
}
3852+
3853+
/*
3854+
* Lookup a highlight group name and return its ID.
3855+
* If it is not found, 0 is returned.
3856+
*/
3857+
int
3858+
syn_name2id(char_u *name)
3859+
{
3860+
// Avoid using stricmp() too much, it's slow on some systems.
3861+
return syn_name2id_len(name, (int)STRLEN(name));
38323862
}
38333863

38343864
/*
@@ -3876,16 +3906,7 @@ syn_id2name(int id)
38763906
int
38773907
syn_namen2id(char_u *linep, int len)
38783908
{
3879-
char_u *name;
3880-
int id = 0;
3881-
3882-
name = vim_strnsave(linep, len);
3883-
if (name == NULL)
3884-
return 0;
3885-
3886-
id = syn_name2id(name);
3887-
vim_free(name);
3888-
return id;
3909+
return syn_name2id_len(linep, len);
38893910
}
38903911

38913912
/*
@@ -3905,15 +3926,14 @@ syn_check_group(char_u *pp, int len)
39053926
emsg(_(e_highlight_group_name_too_long));
39063927
return 0;
39073928
}
3908-
name = vim_strnsave(pp, len);
3909-
if (name == NULL)
3910-
return 0;
3911-
3912-
id = syn_name2id(name);
3929+
id = syn_name2id_len(pp, len);
39133930
if (id == 0) // doesn't exist yet
3931+
{
3932+
name = vim_strnsave(pp, len);
3933+
if (name == NULL)
3934+
return 0;
39143935
id = syn_add_group(name);
3915-
else
3916-
vim_free(name);
3936+
}
39173937
return id;
39183938
}
39193939

@@ -3952,6 +3972,8 @@ syn_add_group(char_u *name)
39523972
{
39533973
highlight_ga.ga_itemsize = sizeof(hl_group_T);
39543974
highlight_ga.ga_growsize = 10;
3975+
hash_init(&highlight_ht);
3976+
highlight_ht_inited = true;
39553977
}
39563978

39573979
if (highlight_ga.ga_len >= MAX_HL_ID)
@@ -3968,11 +3990,20 @@ syn_add_group(char_u *name)
39683990
return 0;
39693991
}
39703992

3971-
name_up = vim_strsave_up(name);
3972-
if (name_up == NULL)
39733993
{
3974-
vim_free(name);
3975-
return 0;
3994+
hlname_T *hn;
3995+
int len = (int)STRLEN(name);
3996+
3997+
hn = alloc(offsetof(hlname_T, hn_key) + len + 1);
3998+
if (hn == NULL)
3999+
{
4000+
vim_free(name);
4001+
return 0;
4002+
}
4003+
vim_strncpy(hn->hn_key, name, len);
4004+
vim_strup(hn->hn_key);
4005+
hn->hn_id = highlight_ga.ga_len + 1; // ID is index plus one
4006+
name_up = hn->hn_key;
39764007
}
39774008

39784009
CLEAR_POINTER(&(HL_TABLE()[highlight_ga.ga_len]));
@@ -3983,6 +4014,7 @@ syn_add_group(char_u *name)
39834014
HL_TABLE()[highlight_ga.ga_len].sg_gui_fg = INVALCOLOR;
39844015
HL_TABLE()[highlight_ga.ga_len].sg_gui_sp = INVALCOLOR;
39854016
#endif
4017+
hash_add(&highlight_ht, name_up, "highlight");
39864018
++highlight_ga.ga_len;
39874019

39884020
return highlight_ga.ga_len; // ID is index plus one
@@ -3995,9 +4027,14 @@ syn_add_group(char_u *name)
39954027
static void
39964028
syn_unadd_group(void)
39974029
{
4030+
hashitem_T *hi;
4031+
39984032
--highlight_ga.ga_len;
4033+
hi = hash_find(&highlight_ht, HL_TABLE()[highlight_ga.ga_len].sg_name_u);
4034+
if (!HASHITEM_EMPTY(hi))
4035+
hash_remove(&highlight_ht, hi, "highlight");
39994036
vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name);
4000-
vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name_u);
4037+
vim_free(HL_TABLE()[highlight_ga.ga_len].sg_name_u - HLNAME_KEY_OFF);
40014038
}
40024039

40034040
/*
@@ -4246,6 +4283,7 @@ highlight_changed(void)
42464283
int hlf;
42474284
int i;
42484285
char_u *p;
4286+
char_u *default_hl;
42494287
int attr;
42504288
char_u *end;
42514289
int id;
@@ -4278,14 +4316,22 @@ highlight_changed(void)
42784316
highlight_ids[hlf] = 0;
42794317
}
42804318

4319+
default_hl = get_highlight_default();
4320+
42814321
// First set all attributes to their default value.
4282-
// Then use the attributes from the 'highlight' option.
4322+
// Then use the attributes from the 'highlight' option, unless it already
4323+
// matches the default and would repeat the same work.
42834324
for (i = 0; i < 2; ++i)
42844325
{
42854326
if (i)
4327+
{
4328+
if (default_hl != NULL && p_hl != NULL
4329+
&& STRCMP(default_hl, p_hl) == 0)
4330+
continue;
42864331
p = p_hl;
4332+
}
42874333
else
4288-
p = get_highlight_default();
4334+
p = default_hl;
42894335
if (p == NULL) // just in case
42904336
continue;
42914337

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,8 @@ static char *(features[]) =
734734

735735
static int included_patches[] =
736736
{ /* Add new patch number below this line */
737+
/**/
738+
240,
737739
/**/
738740
239,
739741
/**/

0 commit comments

Comments
 (0)