mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Yaxiong Tian <tianyaxiong@kylinos.cn>
To: Steven Rostedt <rostedt@goodmis.org>
Cc: mhiramat@kernel.org, mark.rutland@arm.com,
	mathieu.desnoyers@efficios.com, linux-kernel@vger.kernel.org,
	linux-trace-kernel@vger.kernel.org
Subject: Re: [RFC PATCH] ftrace: speed up module enable by binary searching module symbols
Date: Thu, 5 Nov 2026 10:08:51 +0800	[thread overview]
Message-ID: <514ce95a-747c-45b0-91b6-3a5c03ff4e6d@kylinos.cn> (raw)
In-Reply-To: <20261104134310.5cec1ebe@gandalf.local.home>


On 2026/11/5 02:43, Steven Rostedt wrote:
> On Wed, 16 Sep 2026 16:27:15 +0800
> Yaxiong Tian <tianyaxiong@kylinos.cn> wrote:
>
> +static int ftrace_mod_syms_cmp(const void *a, const void *b)
> +{
> +	unsigned long va = *(const unsigned long *)a;
> +	unsigned long vb = *(const unsigned long *)b;
> +
> +	return (va > vb) - (va < vb);
> +}
> Interesting compare technique. I usually do:
>
> static int ftrace_mod_syms_cmp(const void *a, const void *b)
> {
> 	unsigned long va = *(const unsigned long *)a;
> 	unsigned long vb = *(const unsigned long *)b;
>
> 	if (va < vb)
> 		return -1;
>
> 	return va > vb;
> }
>
> But I wanted to compare them, so I wrote this:
>
> int cmp1(const void *a, const void *b)
> {
>         int A = *(const int *)a;
>         int B = *(const int *)b;
>
>         return (A > B) - (A < B);
> }
>
> int cmp2(const void *a, const void *b)
> {
>         int A = *(const int *)a;
>         int B = *(const int *)b;
>
>         if (A < B)
>                 return -1;
>         return A > B;
> }
>
> And complied with gcc with -O2 and the result was this:
>
> 0000000000000000 <cmp1>:
>    0:   8b 06                   mov    (%rsi),%eax
>    2:   39 07                   cmp    %eax,(%rdi)
>    4:   0f 9f c0                setg   %al
>    7:   0f 9c c2                setl   %dl
>    a:   0f b6 d2                movzbl %dl,%edx
>    d:   0f b6 c0                movzbl %al,%eax
>   10:   29 d0                   sub    %edx,%eax
>   12:   c3                      ret
>   13:   66 90                   xchg   %ax,%ax
>   15:   66 66 2e 0f 1f 84 00    data16 cs nopw 0x0(%rax,%rax,1)
>   1c:   00 00 00 00 
>
> 0000000000000020 <cmp2>:
>   20:   8b 06                   mov    (%rsi),%eax
>   22:   39 07                   cmp    %eax,(%rdi)
>   24:   ba ff ff ff ff          mov    $0xffffffff,%edx
>   29:   0f 9f c0                setg   %al
>   2c:   0f b6 c0                movzbl %al,%eax
>   2f:   0f 4c c2                cmovl  %edx,%eax
>   32:   c3                      ret
>
> It looks like my version is a bit better with gcc.
>
> I complied with clang and it did this:
>
> 0000000000000000 <cmp1>:
>    0:   8b 07                   mov    (%rdi),%eax
>    2:   3b 06                   cmp    (%rsi),%eax
>    4:   0f 9c c0                setl   %al
>    7:   0f 9f c1                setg   %cl
>    a:   28 c1                   sub    %al,%cl
>    c:   0f be c1                movsbl %cl,%eax
>    f:   c3                      ret
>
> 0000000000000010 <cmp2>:
>   10:   8b 07                   mov    (%rdi),%eax
>   12:   3b 06                   cmp    (%rsi),%eax
>   14:   0f 9c c0                setl   %al
>   17:   0f 9f c1                setg   %cl
>   1a:   28 c1                   sub    %al,%cl
>   1c:   0f be c1                movsbl %cl,%eax
>   1f:   c3                      ret
>
> Which made the two identical!
>
> Probably just a nit, but food for thought if you want to keep your method
> or switch to mine ;-) I won't be picky about it.

I didn't think about the assembly side of this at the time ;-), and your
version is valuable for this kind of optimization.

I tried both with the type we actually use (unsigned long, -O2; the
counts include ret):

gcc 12.3.0:
0000000000000000 <cmp1>:
   0:    48 8b 0f                 mov    (%rdi),%rcx
   3:    48 8b 16                 mov    (%rsi),%rdx
   6:    31 c0                    xor    %eax,%eax
   8:    48 39 ca                 cmp    %rcx,%rdx
   b:    0f 92 c0                 setb   %al
   e:    48 39 d1                 cmp    %rdx,%rcx
  11:    83 d8 00                 sbb    $0x0,%eax
  14:    c3                       ret
                              (8 insns, branchless)

0000000000000020 <cmp2>:
  20:    48 8b 17                 mov    (%rdi),%rdx
  23:    48 8b 0e                 mov    (%rsi),%rcx
  26:    b8 ff ff ff ff           mov    $0xffffffff,%eax
  2b:    48 39 ca                 cmp    %rcx,%rdx
  2e:    72 08                    jb     38 <cmp2+0x18>
  30:    31 c0                    xor    %eax,%eax
  32:    48 39 d1                 cmp    %rdx,%rcx
  35:    0f 92 c0                 setb   %al
  38:    c3                       ret
                              (9 insns, with a data-dependent branch)

clang 17.0.6:
0000000000000000 <cmp1>:
   0:    48 8b 0f                 mov    (%rdi),%rcx
   3:    31 c0                    xor    %eax,%eax
   5:    48 3b 0e                 cmp    (%rsi),%rcx
   8:    0f 97 c0                 seta   %al
   b:    83 d8 00                 sbb    $0x0,%eax
   e:    c3                       ret
                              (6 insns, branchless)

0000000000000010 <cmp2>:
  10:    48 8b 07                 mov    (%rdi),%rax
  13:    31 c9                    xor    %ecx,%ecx
  15:    48 3b 06                 cmp    (%rsi),%rax
  18:    0f 97 c1                 seta   %cl
  1b:    b8 ff ff ff ff           mov    $0xffffffff,%eax
  20:    0f 43 c1                 cmovae %ecx,%eax
  23: c3                       ret
                              (7 insns, branchless)

Your version is one instruction shorter for int, but for 64-bit gcc needs a
second compare after the branch to tell > from ==, and picks a branch
instead
of cmov, which is worse for a sort comparator.  So I kept the subtract form.

>
>> +
>> +static struct ftrace_mod_syms *ftrace_mod_syms_alloc(struct module *mod)
>> +{
>> +	struct mod_kallsyms *kallsyms;
>> +	struct ftrace_mod_syms *syms;
>> +	unsigned int i, nr = 0;
>> +
>> +	/* mod->kallsyms is not replaced until do_init_module() returns. */
>> +	rcu_read_lock();
>> +	kallsyms = rcu_dereference(mod->kallsyms);
>> +	rcu_read_unlock();
> The above was already discussed.
>
>> +
>> +	if (!kallsyms || kallsyms->num_symtab < 2)
>> +		return NULL;
>> +
>> +	syms = kvmalloc(struct_size(syms, addr, kallsyms->num_symtab),
>> +			GFP_KERNEL);
>> +	if (!syms)
>> +		return NULL;
>> +
>> +	/* The symbols find_kallsyms_symbol() would consider. */
>> +	for (i = 1; i < kallsyms->num_symtab; i++) {
>> +		const Elf_Sym *sym = &kallsyms->symtab[i];
>> +		const char *name = kallsyms->strtab + sym->st_name;
>> +
>> +		if (sym->st_shndx == SHN_UNDEF || !*name ||
>> +		    is_mapping_symbol(name))
>> +			continue;
>> +
>> +		syms->addr[nr++] = kallsyms_symbol_value(sym);
>> +	}
>> +	syms->nr = nr;
>> +
>> +	if (nr > 1)
>> +		sort(syms->addr, nr, sizeof(syms->addr[0]), ftrace_mod_syms_cmp,
>> +		     NULL);
>> +
>> +	return syms;
>> +}
>> +
>> +/* The verdict test_for_valid_rec() would give, without walking the table. */
>> +static bool ftrace_mod_rec_is_valid(struct module *mod,
>> +				    const struct ftrace_mod_syms *syms,
>> +				    struct dyn_ftrace *rec)
>> +{
>> +	unsigned long base, best;
>> +	unsigned int lo, hi;
>> +
>> +	if (!syms)
>> +		return test_for_valid_rec(rec) != 0;
>> +
>> +	/* A record is in executable memory; anything else kallsyms answers. */
>> +	if (within_module_mem_type(rec->ip, mod, MOD_TEXT))
>> +		base = (unsigned long)mod->mem[MOD_TEXT].base;
>> +	else if (within_module_mem_type(rec->ip, mod, MOD_INIT_TEXT))
>> +		base = (unsigned long)mod->mem[MOD_INIT_TEXT].base;
>> +	else
>> +		return test_for_valid_rec(rec) != 0;
>> +
>> +	/* The closest address at or below @rec->ip, if there is one. */
>> +	best = 0;
>> +	for (lo = 0, hi = syms->nr; lo < hi; ) {
>> +		unsigned int mid = lo + (hi - lo) / 2;
>> +
>> +		if (syms->addr[mid] <= rec->ip)
>> +			lo = mid + 1;
>> +		else
>> +			hi = mid;
>> +	}
>> +	if (lo)
>> +		best = syms->addr[lo - 1];
>> +
>> +	/*
>> +	 * No symbol, or only ones below @base: let kallsyms_lookup() answer,
>> +	 * as it also looks in the BPF and ftrace trampoline tables.
>> +	 */
>> +	if (best < base)
>> +		return test_for_valid_rec(rec) != 0;
>> +
>> +	return rec->ip - best <= FTRACE_MCOUNT_MAX_OFFSET;
>> +}
>> +#else
>> +struct ftrace_mod_syms;
>> +
>> +static inline struct ftrace_mod_syms *ftrace_mod_syms_alloc(struct module *mod)
>> +{
>> +	return NULL;
>> +}
>> +
>> +static inline bool ftrace_mod_rec_is_valid(struct module *mod,
>> +					   const struct ftrace_mod_syms *syms,
>> +					   struct dyn_ftrace *rec)
>> +{
>> +	return test_for_valid_rec(rec) != 0;
>> +}
>> +#endif /* FTRACE_MCOUNT_MAX_OFFSET */
>> +
>>  void ftrace_module_enable(struct module *mod)
>>  {
>> +	struct ftrace_mod_syms *syms;
> Hmm, remove this...
>
>
>>  	struct dyn_ftrace *rec;
>>  	struct ftrace_page *pg;
>>  
>> +	/*
>> +	 * Allocates, so build it before taking ftrace_lock.  It is skipped when
>> +	 * ftrace never came up, where the loop below would not run anyway; that
>> +	 * also makes reading ftrace_disabled without the lock safe.
>> +	 */
>> +	syms = ftrace_disabled ? NULL : ftrace_mod_syms_alloc(mod);
> and make the above:
>
> 	struct ftrace_mod_syms *syms __free(kvfree) =
> 		ftrace_disabled ? NULL : ftrace_mod_syms_alloc(mod);
>
>
>> +
>>  	mutex_lock(&ftrace_lock);
>>  
>>  	if (ftrace_disabled)
>> @@ -8050,7 +8177,7 @@ void ftrace_module_enable(struct module *mod)
>>  		cond_resched();
>>  
>>  		/* Weak functions should still be ignored */
>> -		if (!test_for_valid_rec(rec)) {
>> +		if (!ftrace_mod_rec_is_valid(mod, syms, rec)) {
>>  			/* Clear all other flags. Should not be enabled anyway */
>>  			rec->flags = FTRACE_FL_DISABLED;
>>  			continue;
>> @@ -8087,6 +8214,8 @@ void ftrace_module_enable(struct module *mod)
>>   out_unlock:
>>  	mutex_unlock(&ftrace_lock);
>>  
>> +	kvfree(syms);
> Then we can also remove that.

Done as suggested; the explicit kvfree() is gone.  The v2 also carries the
rcu_dereference_raw() change discussed earlier.

I will send the v2 shortly, after I finish testing it today.

Thanks,
Yaxiong


>
> Thanks,
>
> -- Steve
>
>> +
>>  	process_cached_mods(mod->name);
>>  }
>>

      reply	other threads:[~2026-11-05  2:08 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-16  8:27 Yaxiong Tian
2026-10-16 18:07 ` Steven Rostedt
2026-10-19  1:06   ` Yaxiong Tian
2026-10-19 15:25 ` Masami Hiramatsu
2026-10-20  2:08   ` Yaxiong Tian
2026-11-04 18:43 ` Steven Rostedt
2026-11-05  2:08   ` Yaxiong Tian [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=514ce95a-747c-45b0-91b6-3a5c03ff4e6d@kylinos.cn \
    --to=tianyaxiong@kylinos.cn \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-trace-kernel@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=mathieu.desnoyers@efficios.com \
    --cc=mhiramat@kernel.org \
    --cc=rostedt@goodmis.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®