Re: [PATCH 2/2] alloc_tag: keep codetag iterator cross read() calls

David Wang
2025-05-09T17:45:13Z

At 2025-05-10 01:34:52, "Suren Baghdasaryan" <surenb@google.com> wrote:
>On Thu, May 8, 2025 at 10:53 PM David Wang <00107082@163.com> wrote:
>>
>> When reading /proc/allocinfo, for each read syscall, seq_file would
>> invoke start/stop callbacks. In start callback, a memory is alloced
>> to store iterator and the iterator would start from beginning to
>> walk to current read position.
>>
>> seq_file read() takes at most 4096 bytes, even if read with a larger
>> user space buffer, meaning read out all of /proc/allocinfo, tens of read
>> syscalls are needed. For example, a 306036 bytes allocinfo files need
>> 76 reads:
>>
>> $ sudo cat /proc/allocinfo | wc
>> 3964 16678 306036
>> $ sudo strace -T -e read cat /proc/allocinfo
>> ...
>> read(3, " 4096 1 arch/x86/k"..., 131072) = 4063 <0.000062>
>> ...
>> read(3, " 0 0 sound/core"..., 131072) = 4021 <0.000150>
>> ...
>> For those n=3964 lines, each read takes about m=3964/76=52 lines,
>> since iterator restart from beginning for each read(),
>> it would move forward
>> m steps on 1st read
>> 2*m steps on 2nd read
>> 3*m steps on 3rd read
>> ...
>> n steps on last read
>> As read() along, more iterator steps make read() calls slower and
>> slower. Adding those up, codetag iterator moves about O(n*n/m) steps,
>> making data structure traversal take significant part of the whole reading.
>> Profiling when stress reading /proc/allocinfo confirms it:
>>
>> vfs_read(99.959% 1677299/1677995)
>> proc_reg_read_iter(99.856% 1674881/1677299)
>> seq_read_iter(99.959% 1674191/1674881)
>> allocinfo_start(75.664% 1266755/1674191)
>> codetag_next_ct(79.217% 1003487/1266755) <---
>> srso_return_thunk(1.264% 16011/1266755)
>> __kmalloc_cache_noprof(0.102% 1296/1266755)
>> ...
>> allocinfo_show(21.287% 356378/1674191)
>> allocinfo_next(1.530% 25621/1674191)
>> allocinfo_start() takes about 75% of seq_read().
>>
>> A private data alloced at open() time can be used to carry iterator
>> alive across read() calls, and avoid the memory allocation and
>> iterator reset for each read(). This way, only O(1) memory allocation
>> and O(n) steps iterating, and `time` shows performance improvement
>> from ~7ms to ~4ms.
>> Profiling with the change:
>>
>> vfs_read(99.865% 1581073/1583214)
>> proc_reg_read_iter(99.485% 1572934/1581073)
>> seq_read_iter(99.846% 1570519/1572934)
>> allocinfo_show(87.428% 1373074/1570519)
>> seq_buf_printf(83.695% 1149196/1373074)
>> seq_buf_putc(1.917% 26321/1373074)
>> _find_next_bit(1.531% 21023/1373074)
>> ...
>> codetag_to_text(0.490% 6727/1373074)
>> ...
>> allocinfo_next(6.275% 98543/1570519)
>> ...
>> allocinfo_start(0.369% 5790/1570519)
>> ...
>> allocinfo_start taks less than 1%.
>>
>> Signed-off-by: David Wang <00107082@163.com>
>
>I think you will be posting another version to address comments in the
>first patch, but for this patch feel free to add:
>
>Acked-by: Suren Baghdasaryan <surenb@google.com>
>
>Thanks!
>

Oops.... I just made some changes to commit message.
(Found some typo, and made some re-wordings....)
>> ---
>> lib/alloc_tag.c | 29 ++++++++++-------------------
>> 1 file changed, 10 insertions(+), 19 deletions(-)
>>
>> diff --git a/lib/alloc_tag.c b/lib/alloc_tag.c
>> index 25ecc1334b67..fdd5887769a6 100644
>> --- a/lib/alloc_tag.c
>> +++ b/lib/alloc_tag.c
>> @@ -45,21 +45,16 @@ struct allocinfo_private {
>> static void *allocinfo_start(struct seq_file *m, loff_t *pos)
>> {
>> struct allocinfo_private *priv;
>> - struct codetag *ct;
>> loff_t node = *pos;
>>
>> - priv = kzalloc(sizeof(*priv), GFP_KERNEL);
>> - m->private = priv;
>> - if (!priv)
>> - return NULL;
>> -
>> - priv->print_header = (node == 0);
>> + priv = (struct allocinfo_private *)m->private;
>> codetag_lock_module_list(alloc_tag_cttype, true);
>> - priv->iter = codetag_get_ct_iter(alloc_tag_cttype);
>> - while ((ct = codetag_next_ct(&priv->iter)) != NULL && node)
>> - node--;
>> -
>> - return ct ? priv : NULL;
>> + if (node == 0) {
>> + priv->print_header = true;
>> + priv->iter = codetag_get_ct_iter(alloc_tag_cttype);
>> + codetag_next_ct(&priv->iter);
>> + }
>> + return priv->iter.ct ? priv : NULL;
>> }
>>
>> static void *allocinfo_next(struct seq_file *m, void *arg, loff_t *pos)
>> @@ -76,12 +71,7 @@ static void *allocinfo_next(struct seq_file *m, void *arg, loff_t *pos)
>>
>> static void allocinfo_stop(struct seq_file *m, void *arg)
>> {
>> - struct allocinfo_private *priv = (struct allocinfo_private *)m->private;
>> -
>> - if (priv) {
>> - codetag_lock_module_list(alloc_tag_cttype, false);
>> - kfree(priv);
>> - }
>> + codetag_lock_module_list(alloc_tag_cttype, false);
>> }
>>
>> static void print_allocinfo_header(struct seq_buf *buf)
>> @@ -249,7 +239,8 @@ static void __init procfs_init(void)
>> if (!mem_profiling_support)
>> return;
>>
>> - if (!proc_create_seq(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op)) {
>> + if (!proc_create_seq_private(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op,
>> + sizeof(struct allocinfo_private), NULL)) {
>> pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME);
>> shutdown_mem_profiling(false);
>> }
>> --
>> 2.39.2
>>