All of lore.kernel.org
 help / color / mirror / Atom feed
From: Namhyung Kim <namhyung@kernel.org>
To: haghdoost@uber.com
Cc: Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>,
	Arnaldo Carvalho de Melo <acme@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	Alexander Shishkin <alexander.shishkin@linux.intel.com>,
	Jiri Olsa <jolsa@kernel.org>, Ian Rogers <irogers@google.com>,
	Adrian Hunter <adrian.hunter@intel.com>,
	James Clark <james.clark@linaro.org>,
	Alexei Starovoitov <ast@kernel.org>,
	Andrii Nakryiko <andriin@fb.com>,
	linux-perf-users@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v2 3/4] perf script: Add --lazy-load-symbols for lazy symbol loading
Date: Sun, 20 Sep 2026 17:19:39 -0700	[thread overview]
Message-ID: <arB4G744iWiSPAnV@z2> (raw)
In-Reply-To: <20260919-perf-symbol-memory-send-v2-3-495b8f00ad7c@uber.com>

On Sat, Sep 19, 2026 at 07:33:58PM -0700, Alireza Haghdoost via B4 Relay wrote:
> From: Alireza Haghdoost <haghdoost@uber.com>
> 
> perf script eagerly materializes eligible symbols from every DSO
> encountered in samples. On a production fixture, it loaded about 765k
> symbols to resolve about 45k distinct (DSO, symbol) frames, exceeding the
> memory available in a memory-constrained cgroup.
> 
> This patch adds --lazy-load-symbols for userspace ELF DSOs. It builds a
> compact sorted index, resolves sampled addresses by binary search, reads
> symbol names through a private data-source DSO, and caches resolved symbols
> in the existing rb-tree. The private DSO uses the normal DSO data cache, so
> the exact split-debuginfo source can be reopened after descriptor eviction.
> If the source cannot be read during preflight, perf discards the index and
> eagerly loads that DSO instead.
> 
> On the same fixture, peak RssAnon drops from 265 MiB to 39 MiB and wall
> time from 3.1 seconds to 1.85 seconds. Memory optimizations usually cost
> time; this one does not because lazy loading skips many unnecessary
> calloc() calls and demangling operations.
> 
> Lazy loading is most effective when samples reference only a small
> fraction of the available symbols, such as profiles spanning many large
> DSOs. It still builds an index proportional to the total symbol count.
> Eager loading remains available for dense symbol coverage or cases
> requiring its broader ELF and architecture support.
> 
> This does not claim full parity with the eager loader. Lazy loading
> supports the common userspace ELF symtab/dynsym case; .gnu_debugdata and
> PPC64 .opd continue through the eager loader.
> 
> Materialization is serialized with the DSO lock. Name lookups materialize
> the remaining index before constructing the name-sorted array. Lazy loading
> shares eager duplicate and IFUNC selection, and clips ranges that cross
> .plt before synthesizing PLT symbols.
> 
> Signed-off-by: Alireza Haghdoost <haghdoost@uber.com>
> ---
[SNIP]
> +static int dso__build_ondemand_index(struct dso *dso, struct symsrc *syms_ss,
> +				     struct symsrc *runtime_ss,
> +				     int dynsym)
> +{
> +	struct dso_ondemand *od;
> +	Elf *elf = syms_ss->elf;
> +	GElf_Ehdr ehdr = syms_ss->ehdr;
> +	GElf_Shdr shdr;
> +	GElf_Shdr strshdr;
> +	Elf_Scn *strscn, *sec_strndx;
> +	Elf_Data *syms;
> +	GElf_Sym sym;
> +	Elf_Data *secstrs = NULL;
> +	size_t i, index_bytes, reservation_peak;
> +	u32 count = 0, j;
> +	u32 *name_offsets;
> +	u64 nr_entries, strtab_offset;
> +	u64 probe_off;
> +	u8 probe;
> +
> +	/*
> +	 * GNU debugdata is backed by a temporary decompressed fd rather than a
> +	 * reopenable source path. Keep using the eager loader for that case.
> +	 */
> +	if (syms_ss->type == DSO_BINARY_TYPE__GNU_DEBUGDATA)
> +		return 0;
> +
> +	if (dynsym)
> +		shdr = syms_ss->dynshdr;
> +	else
> +		shdr = syms_ss->symshdr;
> +
> +	syms = elf_getdata(dynsym ? syms_ss->dynsym : syms_ss->symtab, NULL);
> +	if (!syms)
> +		return -1;
> +
> +	if (!shdr.sh_entsize)
> +		return 0;
> +
> +	nr_entries = shdr.sh_size / shdr.sh_entsize;
> +	if (nr_entries > UINT32_MAX)
> +		return -EOVERFLOW;
> +
> +	strscn = elf_getscn(elf, shdr.sh_link);
> +	if (!strscn || !gelf_getshdr(strscn, &strshdr))
> +		return -1;
> +	strtab_offset = strshdr.sh_offset;
> +
> +	/*
> +	 * Section name string table, used to match the eager path's
> +	 * elf_sec__filter() (text/data section check for STT_NOTYPE labels).
> +	 */
> +	sec_strndx = elf_getscn(elf, ehdr.e_shstrndx);
> +	if (sec_strndx)
> +		secstrs = elf_getdata(sec_strndx, NULL);
> +
> +	for (i = 0; i < nr_entries; i++) {
> +		if (!gelf_getsym(syms, i, &sym))
> +			continue;
> +		if (ondemand_sym_ok(elf, secstrs, &sym, shdr.sh_link,
> +				    ehdr.e_machine))
> +			count++;
> +	}
> +
> +	if (!count)
> +		return 0;
> +	if (check_mul_overflow((size_t)count, sizeof(*od->sorted),
> +			       &index_bytes))
> +		return -EOVERFLOW;
> +
> +	/*
> +	 * Account the index against the symbol memory budget: at 24
> +	 * bytes/symbol it is the dominant on-demand cost and must count
> +	 * toward --max-symbol-bytes just like struct symbol allocations do.
> +	 */
> +	if (!symbol__try_account_bytes(index_bytes)) {
> +		symbol_budget_warning();
> +		return 0; /* fall back to the eager loader's per-symbol budget */
> +	}
> +	reservation_peak = symbol__bytes_used();
> +
> +	od = zalloc(sizeof(*od));
> +	if (!od) {
> +		symbol__unaccount_bytes(index_bytes);
> +		return -1;
> +	}
> +
> +	od->sorted = zalloc(index_bytes);
> +	if (!od->sorted) {
> +		symbol__unaccount_bytes(index_bytes);
> +		free(od);
> +		return -1;
> +	}
> +	od->nr_alloc = count;	/* allocated; the deduped count may shrink */
> +	name_offsets = malloc(count * sizeof(*name_offsets));
> +	if (!name_offsets) {
> +		symbol__unaccount_bytes(index_bytes);
> +		free(od->sorted);
> +		free(od);
> +		return -1;
> +	}
> +
> +	j = 0;
> +	for (i = 0; i < nr_entries; i++) {
> +		u64 adjusted;
> +		GElf_Phdr phdr;
> +
> +		if (!gelf_getsym(syms, i, &sym))
> +			continue;
> +		if (!ondemand_sym_ok(elf, secstrs, &sym, shdr.sh_link,
> +				     ehdr.e_machine))
> +			continue;
> +
> +		adjusted = sym.st_value;
> +
> +		if ((ehdr.e_machine == EM_ARM) &&
> +		    (GELF_ST_TYPE(sym.st_info) == STT_FUNC) &&
> +		    (adjusted & 1))
> +			--adjusted;
> +
> +		/*
> +		 * Program header adjustment, identical to the eager loop:
> +		 * read the PT_LOAD containing the symbol from the runtime
> +		 * ELF (the debug-info file may have zeroed p_offset), and
> +		 * fall back to the section-header bias when no program
> +		 * header matches -- exactly what the eager path does when
> +		 * elf_read_program_header fails.
> +		 */
> +		if (elf_read_program_header(runtime_ss->elf, adjusted,
> +					    &phdr) == 0) {
> +			adjusted -= phdr.p_vaddr - phdr.p_offset;
> +		} else {
> +			Elf_Scn *sym_sec = elf_getscn(elf, sym.st_shndx);
> +			GElf_Shdr sym_shdr;
> +
> +			if (sym_sec && gelf_getshdr(sym_sec, &sym_shdr))
> +				adjusted -= sym_shdr.sh_addr - sym_shdr.sh_offset;
> +		}
> +
> +		od->sorted[j].start = adjusted;
> +		od->sorted[j].end = sym.st_size; /* st_size for now, converted later */
> +		/*
> +		 * Sort equal-start aliases in original symtab order to match
> +		 * rb-tree insertion order. Restore st_name after sorting.
> +		 */
> +		name_offsets[j] = sym.st_name;
> +		od->sorted[j].name_off = j;
> +		od->sorted[j].binding = GELF_ST_BIND(sym.st_info);
> +		od->sorted[j].type = GELF_ST_TYPE(sym.st_info);
> +		j++;
> +	}
> +	count = j;
> +	if (!count) {
> +		symbol__unaccount_bytes(index_bytes);
> +		free(name_offsets);
> +		free(od->sorted);
> +		free(od);
> +		return 0;
> +	}
> +
> +	qsort(od->sorted, count, sizeof(*od->sorted), cmp_sym_idx);
> +	for (i = 0; i < count; i++)
> +		od->sorted[i].name_off = name_offsets[od->sorted[i].name_off];
> +	free(name_offsets);
> +
> +	/*
> +	 * Match the eager loader's ordering: fill zero-sized ranges before
> +	 * choosing among equal-start aliases, so the size preference sees
> +	 * the same synthesized lengths as symbols__fixup_duplicate().
> +	 */
> +	for (i = 0; i < count; i++) {
> +		u64 size = od->sorted[i].end; /* was st_size */
> +
> +		if (size > 0)
> +			od->sorted[i].end = od->sorted[i].start + size;
> +		else if (i + 1 < count)
> +			od->sorted[i].end = od->sorted[i + 1].start;
> +		else
> +			od->sorted[i].end = roundup(od->sorted[i].start, 4096) + 4096;
> +	}
> +
> +	if (!symbol_conf.allow_aliases) {
> +		u32 out = 0;

I think we agreed to factor out this block of code.

> +
> +		for (i = 0; i < count; i++) {
> +			u32 best = i;
> +			const char *na = NULL, *nb;
> +			char *da = NULL, *db;
> +			bool has_ifunc = od->sorted[i].type == STT_GNU_IFUNC;
> +
> +			na = elf_strptr(elf, shdr.sh_link,
> +					od->sorted[best].name_off);
> +			if (na) {
> +				da = dso__demangle_sym(dso, 0, na);
> +				if (da)
> +					na = da;
> +			}
> +
> +			for (j = i + 1; j < count &&
> +			     od->sorted[j].start == od->sorted[i].start; j++) {
> +				int choice;
> +
> +				has_ifunc |= od->sorted[j].type == STT_GNU_IFUNC;
> +				nb = elf_strptr(elf, shdr.sh_link,
> +						od->sorted[j].name_off);
> +				if (!na || !nb)
> +					continue;
> +
> +				db = dso__demangle_sym(dso, 0, nb);
> +				if (db)
> +					nb = db;
> +
> +				choice = symbol__choose_best(
> +						od->sorted[best].end -
> +						od->sorted[best].start,
> +						od->sorted[best].type,
> +						od->sorted[best].binding, na,
> +						od->sorted[j].end -
> +						od->sorted[j].start,
> +						od->sorted[j].type,
> +						od->sorted[j].binding, nb);
> +				if (choice == SYMBOL_B) {
> +					best = j;
> +					free(da);
> +					da = db;
> +					na = nb;
> +				} else {
> +					free(db);
> +				}
> +			}
> +
> +			free(da);
> +			od->sorted[out++] = od->sorted[best];
> +			if (has_ifunc && od->sorted[out - 1].type != STT_GNU_IFUNC)
> +				od->sorted[out - 1].flags |= SYM_IDX_FLAG_IFUNC_ALIAS;
> +			i = j - 1; /* skip past all aliases of this start */
> +		}
> +
> +		if (out < count) {
> +			struct sym_idx *shrunk;
> +
> +			shrunk = realloc(od->sorted, out * sizeof(*od->sorted));
> +			if (shrunk) {
> +				od->sorted = shrunk;
> +				symbol__unaccount_bytes((od->nr_alloc - out) *
> +						       sizeof(*od->sorted));
> +				od->nr_alloc = out;
> +			}
> +		}
> +		count = out;
> +	}
> +
> +	/*
> +	 * The interval binary search requires non-overlapping ranges.  In
> +	 * the default deduplicated mode, prefer the symbol with the nearest
> +	 * preceding start when an ELF st_size overlaps the next symbol.
> +	 */
> +	if (!symbol_conf.allow_aliases) {
> +		for (i = 0; i + 1 < count; i++) {
> +			if (od->sorted[i].end > od->sorted[i + 1].start)
> +				od->sorted[i].end = od->sorted[i + 1].start;
> +		}
> +	}
> +
> +	/*
> +	 * Keep an exact-path data DSO for the symbol source.  This may differ
> +	 * from the runtime image (for example, split debuginfo), so using the
> +	 * primary DSO's data cache could read an unrelated string-table offset.
> +	 * The standard DSO data cache manages descriptor eviction and reopening.
> +	 */

Yes, it's a known problem and I hope to address it soon.  Can you please
make the path handling a separate commit?  I think it's an independent
fix for split debuginfo.


> +	od->data_dso = dso__new(syms_ss->name);
> +	if (!od->data_dso ||
> +	    dso__data_set_path(od->data_dso, syms_ss->name) < 0)
> +		goto out_decline_source;
> +	dso__set_binary_type(od->data_dso, DSO_BINARY_TYPE__SYSTEM_PATH_DSO);
> +	dso__set_nsinfo(od->data_dso, nsinfo__get(dso__nsinfo(dso)));
> +
> +	/*
> +	 * Open the managed source while eager fallback is still possible.
> +	 * A later failure would otherwise turn materialization into a miss.
> +	 */
> +	if (od->sorted[0].name_off >= strshdr.sh_size)
> +		goto out_decline_source;
> +	if (check_add_overflow(strtab_offset,
> +			       (u64)od->sorted[0].name_off, &probe_off))
> +		goto out_decline_source;
> +	if (dso__data_read_offset(od->data_dso, NULL, probe_off, &probe, 1) != 1)
> +		goto out_decline_source;
> +
> +	od->strtab_offset = strtab_offset;
> +	od->strtab_size = strshdr.sh_size;
> +	od->nr_sorted = count;
> +
> +	dso__set_ondemand(dso, od);
> +
> +	pr_debug("%s: on-demand index: %u symbols (%zu bytes, %zu bytes total) budget=%zu\n",
> +		 dso__long_name(dso), count,
> +		 od->nr_alloc * sizeof(*od->sorted), symbol__bytes_used(),
> +		 reservation_peak);
> +
> +	return 1;
> +
> +out_decline_source:
> +	if (od->data_dso) {
> +		dso__data_close(od->data_dso);
> +		dso__put(od->data_dso);
> +	}
> +	symbol__unaccount_bytes(od->nr_alloc * sizeof(*od->sorted));
> +	free(od->sorted);
> +	free(od);
> +	return 0;
> +}
[SNIP]
> diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
> index 32eef666f748..3ca9655c36fd 100644
> --- a/tools/perf/util/symbol.c
> +++ b/tools/perf/util/symbol.c
> @@ -143,72 +143,80 @@ int __weak arch__compare_symbol_names_n(const char *namea, const char *nameb,
>  	return strncmp(namea, nameb, n);
>  }
>  
> -int __weak arch__choose_best_symbol(struct symbol *syma,
> -				    struct symbol *symb __maybe_unused)
> +int __weak arch__choose_best_symbol(const char *syma_name)
>  {
>  	/* Avoid "SyS" kernel syscall aliases */
> -	if (strlen(syma->name) >= 3 && !strncmp(syma->name, "SyS", 3))
> +	if (strlen(syma_name) >= 3 && !strncmp(syma_name, "SyS", 3))
>  		return SYMBOL_B;
> -	if (strlen(syma->name) >= 10 && !strncmp(syma->name, "compat_SyS", 10))
> +	if (strlen(syma_name) >= 10 && !strncmp(syma_name, "compat_SyS", 10))
>  		return SYMBOL_B;
>  
>  	return SYMBOL_A;
>  }
>  
> -static int choose_best_symbol(struct symbol *syma, struct symbol *symb)
> +int symbol__choose_best(u64 a_size, u8 a_type, u8 a_binding,
> +			const char *a_name,
> +			u64 b_size, u8 b_type, u8 b_binding,
> +			const char *b_name)
>  {
>  	s64 a;
>  	s64 b;
>  	size_t na, nb;
>  
>  	/* Prefer a symbol with non zero length */
> -	a = syma->end - syma->start;
> -	b = symb->end - symb->start;
> -	if ((b == 0) && (a > 0))
> +	if ((b_size == 0) && (a_size > 0))
>  		return SYMBOL_A;
> -	else if ((a == 0) && (b > 0))
> +	else if ((a_size == 0) && (b_size > 0))
>  		return SYMBOL_B;
>  
> -	if (symbol__type(syma) != symbol__type(symb)) {
> -		if (symbol__type(syma) == STT_NOTYPE)
> +	if (a_type != b_type) {
> +		if (a_type == STT_NOTYPE)
>  			return SYMBOL_B;
> -		if (symbol__type(symb) == STT_NOTYPE)
> +		if (b_type == STT_NOTYPE)
>  			return SYMBOL_A;
>  	}
>  
>  	/* Prefer a non weak symbol over a weak one */
> -	a = symbol__binding(syma) == STB_WEAK;
> -	b = symbol__binding(symb) == STB_WEAK;
> +	a = a_binding == STB_WEAK;
> +	b = b_binding == STB_WEAK;
>  	if (b && !a)
>  		return SYMBOL_A;
>  	if (a && !b)
>  		return SYMBOL_B;
>  
>  	/* Prefer a global symbol over a non global one */
> -	a = symbol__binding(syma) == STB_GLOBAL;
> -	b = symbol__binding(symb) == STB_GLOBAL;
> +	a = a_binding == STB_GLOBAL;
> +	b = b_binding == STB_GLOBAL;
>  	if (a && !b)
>  		return SYMBOL_A;
>  	if (b && !a)
>  		return SYMBOL_B;
>  
>  	/* Prefer a symbol with less underscores */
> -	a = prefix_underscores_count(syma->name);
> -	b = prefix_underscores_count(symb->name);
> +	a = prefix_underscores_count(a_name);
> +	b = prefix_underscores_count(b_name);
>  	if (b > a)
>  		return SYMBOL_A;
>  	else if (a > b)
>  		return SYMBOL_B;
>  
>  	/* Choose the symbol with the longest name */
> -	na = strlen(syma->name);
> -	nb = strlen(symb->name);
> +	na = strlen(a_name);
> +	nb = strlen(b_name);
>  	if (na > nb)
>  		return SYMBOL_A;
>  	else if (na < nb)
>  		return SYMBOL_B;
>  
> -	return arch__choose_best_symbol(syma, symb);
> +	return arch__choose_best_symbol(a_name);
> +}
> +
> +static int choose_best_symbol(struct symbol *syma, struct symbol *symb)
> +{
> +	return symbol__choose_best(syma->end - syma->start,
> +				   symbol__type(syma), symbol__binding(syma), syma->name,
> +				   symb->end - symb->start,
> +				   symbol__type(symb), symbol__binding(symb), symb->name);
>  }

To reduce the size of the patch, I think it's better to split this as a
separate commit.  Please consider minimize the patch size in general to
help reviewers. :)

Thanks,
Namhyung

>  
>  void symbols__fixup_duplicate(struct rb_root_cached *symbols)
> @@ -1989,11 +1997,19 @@ int dso__load(struct dso *dso, struct map *map)
>  		}
>  
>  #ifdef HAVE_LIBBFD_SUPPORT
> +#ifdef HAVE_LIBELF_SUPPORT
> +		if (is_reg && !symbol_conf.lazy_load_symbols)
> +#else
>  		if (is_reg)
> +#endif
>  			bfdrc = dso__load_bfd_symbols(dso, name);
>  #endif
>  		if (is_reg && bfdrc < 0)
>  			sirc = symsrc__init(ss, dso, name, symtab_type);
> +#if defined(HAVE_LIBBFD_SUPPORT) && defined(HAVE_LIBELF_SUPPORT)
> +		if (is_reg && symbol_conf.lazy_load_symbols && sirc < 0)
> +			bfdrc = dso__load_bfd_symbols(dso, name);
> +#endif
>  
>  		if (nsexit)
>  			nsinfo__mountns_enter(dso__nsinfo(dso), &nsc);
> diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h
> index f7331edf0b71..99563c9ff610 100644
> --- a/tools/perf/util/symbol.h
> +++ b/tools/perf/util/symbol.h
> @@ -233,6 +233,10 @@ size_t symbol__bytes_used(void);
>  void symbol__account_bytes(size_t bytes);
>  bool symbol__try_account_bytes(size_t bytes);
>  void symbol__unaccount_bytes(size_t bytes);
> +int symbol__choose_best(u64 a_size, u8 a_type, u8 a_binding,
> +			const char *a_name,
> +			u64 b_size, u8 b_type, u8 b_binding,
> +			const char *b_name);
>  size_t __symbol__fprintf_symname_offs(const struct symbol *sym,
>  				      const struct addr_location *al,
>  				      bool unknown_as_addr,
> @@ -308,7 +312,7 @@ const char *arch__normalize_symbol_name(const char *name);
>  int arch__compare_symbol_names(const char *namea, const char *nameb);
>  int arch__compare_symbol_names_n(const char *namea, const char *nameb,
>  				 unsigned int n);
> -int arch__choose_best_symbol(struct symbol *syma, struct symbol *symb);
> +int arch__choose_best_symbol(const char *syma_name);
>  
>  enum symbol_tag_include {
>  	SYMBOL_TAG_INCLUDE__NONE = 0,
> diff --git a/tools/perf/util/symbol_conf.h b/tools/perf/util/symbol_conf.h
> index 6a16c5badd5e..0f8d044eba20 100644
> --- a/tools/perf/util/symbol_conf.h
> +++ b/tools/perf/util/symbol_conf.h
> @@ -74,6 +74,7 @@ struct symbol_conf {
>  			no_buildid_mmap2,
>  			guest_code,
>  			lazy_load_kernel_maps,
> +			lazy_load_symbols,
>  			keep_exited_threads,
>  			annotate_data_member,
>  			annotate_data_sample,
> 
> -- 
> Git-157)
> 
> 

  parent reply	other threads:[~2026-09-21  0:19 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-20  2:33 [PATCH v2 0/4] perf script: Bounded and lazy symbol loading Alireza Haghdoost
2026-09-20  2:33 ` Alireza Haghdoost via B4 Relay
2026-09-20  2:33 ` [PATCH v2 1/4] perf symbols: Fix broken ELF_C_READ_MMAP fallback guard Alireza Haghdoost
2026-09-20  2:33   ` Alireza Haghdoost via B4 Relay
2026-09-20  2:42   ` sashiko-bot
2026-09-20 23:52   ` Namhyung Kim
2026-09-20  2:33 ` [PATCH v2 2/4] perf script: Add --max-symbol-bytes to bound ELF symbol memory Alireza Haghdoost
2026-09-20  2:33   ` Alireza Haghdoost via B4 Relay
2026-09-20  2:43   ` sashiko-bot
2026-09-21  0:03   ` Namhyung Kim
2026-09-21  4:27     ` Alireza Haghdoost
2026-09-20  2:33 ` [PATCH v2 3/4] perf script: Add --lazy-load-symbols for lazy symbol loading Alireza Haghdoost
2026-09-20  2:33   ` Alireza Haghdoost via B4 Relay
2026-09-20  2:45   ` sashiko-bot
2026-09-20 13:49     ` Alireza Haghdoost
2026-09-21  0:19   ` Namhyung Kim [this message]
2026-09-21  4:45     ` Alireza Haghdoost
2026-09-20  2:33 ` [PATCH v2 4/4] perf test: Test lazy symbol loading and symbol memory limits Alireza Haghdoost
2026-09-20  2:33   ` Alireza Haghdoost via B4 Relay
2026-09-20  2:39   ` sashiko-bot

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=arB4G744iWiSPAnV@z2 \
    --to=namhyung@kernel.org \
    --cc=acme@kernel.org \
    --cc=adrian.hunter@intel.com \
    --cc=alexander.shishkin@linux.intel.com \
    --cc=andriin@fb.com \
    --cc=ast@kernel.org \
    --cc=haghdoost@uber.com \
    --cc=irogers@google.com \
    --cc=james.clark@linaro.org \
    --cc=jolsa@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=mingo@redhat.com \
    --cc=peterz@infradead.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.