Skip to content

perf(index): reduce resident anon RSS of incremental tasks - #291

Merged
deepin-bot[bot] merged 1 commit into
linuxdeepin:develop/snipefrom
Johnson-zs:develop/snipe
Oct 26, 2026
Merged

deepin-bot[bot] merged 1 commit into
linuxdeepin:develop/snipefrom
Johnson-zs:develop/snipe

Conversation

@Johnson-zs

@Johnson-zs Johnson-zs commented Oct 26, 2026 •

Copy link
Copy Markdown

Incremental index tasks allocate and free large transients in bursts
(whole-file wchar copies, Lucene analyzer/posting buffers). Under glibc
defaults those freed pages stay resident: the dynamic mmap threshold
rises after the first big munmap, per-task worker QThreads each hold
their own arena at high-water, so idle RSS lingers near the last
burst's peak (~200MB observed, reclaimable via malloc_trim).

Two measures:

  • pin allocator params at startup: M_ARENA_MAX=2, fixed
    M_MMAP_THRESHOLD=2MB (disables dynamic raise), M_TRIM_THRESHOLD=256KB
  • TaskManager: debounced malloc_trim(0) 30s after the last task ends,
    deferred while another task is running

Summary by Sourcery

Reduce idle RSS from incremental indexing by tuning glibc allocation and conditionally trimming the heap at task boundaries.

Bug Fixes:

  • Reduce resident memory after incremental indexing tasks by returning reclaimable heap pages to the operating system when sufficient free heap accumulates.

Enhancements:

  • Configure glibc allocation behavior at startup to limit arenas, keep large allocations readily reclaimable, and lower the heap trimming threshold.
  • Add conditional post-task heap trimming with task-state checks, a minimum interval, heap/RSS diagnostics, and compatibility for supported glibc versions.

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Johnson-zs

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai

sourcery-ai Bot commented Oct 26, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR reduces idle resident memory by configuring glibc before worker threads start and by scheduling a debounced malloc_trim(0) after 30 seconds of task inactivity, while postponing reclamation during active work.

Sequence diagram for debounced task idle heap trimming

sequenceDiagram
    participant Task as IndexTask
    participant Manager as TaskManager
    participant Timer as m_idleTrimTimer
    participant Allocator as glibc allocator
    participant OS as Operating system

    Task->>Manager: onTaskFinished()
    Manager->>Manager: scheduleIdleTrim()
    Manager->>Timer: start(30000)
    opt another task finishes before timeout
        Task->>Manager: onTaskPaused() or onTaskFinished()
        Manager->>Manager: scheduleIdleTrim()
        Manager->>Timer: start(30000)
    end
    Timer-->>Manager: timeout()
    alt hasRunningTask()
        Manager->>Timer: start(30000)
    else no task running
        Manager->>Allocator: malloc_trim(0)
        Allocator->>OS: return free heap pages
    end
Loading

File-Level Changes

Change Details Files
Pins glibc allocation behavior at process startup to reduce per-thread arena retention and make large allocations promptly reclaimable.
  • Limits the process to two malloc arenas.
  • Fixes the mmap threshold at 2 MiB, preventing glibc’s dynamic threshold from rising.
  • Sets a 256 KiB trim threshold for heap reclamation.
src/index/main.cpp
Adds debounced, task-aware heap trimming after incremental indexing activity becomes idle.
  • Creates a single-shot 30-second idle timer per TaskManager.
  • Reschedules trimming when tasks are paused or finished, so bursts are coalesced.
  • Defers and retries malloc_trim(0) while any task is still running.
src/index/task/taskmanager.cpp
src/index/task/taskmanager.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/index/main.cpp" line_range="93" />
<code_context>
+    // burst's peak. Fixed values (setting M_MMAP_THRESHOLD explicitly also
+    // disables the dynamic adjustment) keep big blocks on mmap where free
+    // returns pages to the OS immediately.
+    mallopt(M_ARENA_MAX, 2);
+    mallopt(M_MMAP_THRESHOLD, 2 * 1024 * 1024);
+    mallopt(M_TRIM_THRESHOLD, 256 * 1024);
</code_context>
<issue_to_address>
**Concurrent indexing becomes serialized**

When multiple profiles run allocation-heavy indexing tasks concurrently, `M_ARENA_MAX=2` caps the process to two glibc arenas, so allocation-heavy workers from concurrently active profiles must share arenas and contend on their allocator locks. Indexing throughput drops during concurrent profile workloads.

Benchmark the arena cap under concurrent profile workloads and set it high enough to avoid excessive allocator-lock contention while still meeting the RSS target.
</issue_to_address>

### Comment 2
<location path="src/index/task/taskmanager.h" line_range="154" />
<code_context>
     QTimer *m_burstRecheckTimer { nullptr };
+
+    // 任务静默后的堆回收定时器(singleShot,见构造函数注释)
+    QTimer *m_idleTrimTimer { nullptr };
 };

</code_context>
<issue_to_address>
**Private timer member misses required prefix**

When the header declares the new private timer member, `m_idleTrimTimer` uses the `m_` prefix instead of the required `_` prefix, and its mixed-case name violates the snake_case naming rule.

Rename the member and its references to `_idle_trim_timer`.
</issue_to_address>

### Comment 3
<location path="src/index/task/taskmanager.cpp" line_range="49" />
<code_context>
+// 任务静默期:任务结束后延迟多久把 glibc 堆里残留的空闲页还给 OS。
+// 取值需覆盖"事件批连发"的典型间隔(collector 窗口 1~3s),避免与
+// 下一批任务的分配持续互相抵消;正在跑任务时定时器会自行顺延。
+constexpr int kIdleTrimDelayMs = 30000;
+
 TaskQueueItem createCompensationTaskItem(const QStringList &paths)
</code_context>
<issue_to_address>
**Allocator constant breaks naming rule**

`kIdleTrimDelayMs` uses mixed case instead of the required UPPER_CASE, so the new constant breaks the repository naming rule.

Rename the constant to UPPER_CASE.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread src/index/main.cpp
// burst's peak. Fixed values (setting M_MMAP_THRESHOLD explicitly also
// disables the dynamic adjustment) keep big blocks on mmap where free
// returns pages to the OS immediately.
mallopt(M_ARENA_MAX, 2);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Concurrent indexing becomes serialized

When multiple profiles run allocation-heavy indexing tasks concurrently, M_ARENA_MAX=2 caps the process to two glibc arenas, so allocation-heavy workers from concurrently active profiles must share arenas and contend on their allocator locks. Indexing throughput drops during concurrent profile workloads.

Benchmark the arena cap under concurrent profile workloads and set it high enough to avoid excessive allocator-lock contention while still meeting the RSS target.

Prompt for AI agents
In `src/index/main.cpp` at line 93:

**Concurrent indexing becomes serialized**

When multiple profiles run allocation-heavy indexing tasks concurrently, `M_ARENA_MAX=2` caps the process to two glibc arenas, so allocation-heavy workers from concurrently active profiles must share arenas and contend on their allocator locks. Indexing throughput drops during concurrent profile workloads.

Benchmark the arena cap under concurrent profile workloads and set it high enough to avoid excessive allocator-lock contention while still meeting the RSS target.

Comment thread src/index/task/taskmanager.h Resolved
Comment thread src/index/task/taskmanager.cpp Outdated
// 任务静默期:任务结束后延迟多久把 glibc 堆里残留的空闲页还给 OS。
// 取值需覆盖"事件批连发"的典型间隔(collector 窗口 1~3s),避免与
// 下一批任务的分配持续互相抵消;正在跑任务时定时器会自行顺延。
constexpr int kIdleTrimDelayMs = 30000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Low · Allocator constant breaks naming rule

kIdleTrimDelayMs uses mixed case instead of the required UPPER_CASE, so the new constant breaks the repository naming rule.

Rename the constant to UPPER_CASE.

Prompt for AI agents
In `src/index/task/taskmanager.cpp` at line 49:

**Allocator constant breaks naming rule**

`kIdleTrimDelayMs` uses mixed case instead of the required UPPER_CASE, so the new constant breaks the repository naming rule.

Rename the constant to UPPER_CASE.

From AGENTS.md L60:

- Constants: `UPPER_CASE` (e.g., `MAX_INPUT_MINOR`, `INFO_LEVELS`)

…undary trim

Incremental index tasks allocate and free large transients in bursts
(whole-file wchar copies, Lucene analyzer/posting buffers, per-batch
reader/writer). Under glibc defaults those freed pages stay resident,
so idle RSS sits at the last burst's high-water (~200-360MB observed,
dropping only under a manual malloc_trim).

Two measures:
- pin allocator params at startup: M_ARENA_MAX=2, fixed
  M_MMAP_THRESHOLD=2MB (an explicit value also disables glibc's dynamic
  threshold raise), M_TRIM_THRESHOLD=256KB
- return the arena heap at task boundaries: when no task is running and
  mallinfo2().fordblks >= 32MB, call malloc_trim(0), at most once per
  10s. An idle-window debounce was tried first and never fires on hosts
  with steady incremental churn (events arrive every few seconds, and
  long commit/merge phases keep the task marker set), so the trigger is
  based on reclaimable heap size instead.

Log the per-task heap state (free/in-use/RSS) and the trim result at
debug level, so RSS attribution stays verifiable in the field.

Verified on a 5.3GB fulltext index: a 30-file batch leaves 85MB free /
28MB in use at 148MB RSS and trim brings RSS to 67MB; repeated batches
128->70MB and 100->71MB. Steady state 214MB -> 71MB.
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

总体评分: 98 分 (通过阈值: 70分)

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过
评分详情 本次提交为内存优化(降低增量索引任务的常驻 RSS),通过 mallopt 固定分配器参数 并在任务边界处条件式 malloc_trim 归还空闲堆页。代码逻辑正确、注释详尽、性能设计 合理(含频率限制与阈值门控)、无安全漏洞。仅有一处轻微的代码风格问题 (maybeTrimHeap 声明位于 Q_SLOTS 区域),不影响功能。

🔍 详细分析

1. 语法逻辑 ✅

评价: 语法正确,逻辑清晰 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ['无需修改,逻辑清晰正确。']


2. 代码质量 ✅

评价: 代码结构清晰,注释完整 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ['将 maybeTrimHeap() 声明从 Q_SLOTS 区域移至 private: 区域,保持声明语义与实际用途一致。']


3. 代码性能 ✅

评价: 性能良好,资源使用合理 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ['无需修改,性能设计合理。']


4. 代码安全 🔒

评价: 存在0个安全漏洞 ✅ 通过

🔐 发现 0 个安全漏洞

安全漏洞详情:
✅ 未发现安全漏洞

建议: ['无需修改,安全合规。']


💡 改进建议代码示例

// taskmanager.h 修复:将 maybeTrimHeap() 移至 private 区域
private:
    qint64 queuedIncrementalCount() const;
    void updateBacklogState();
    void recordIngestedFiles(qint64 count);

    // 任务边界处的条件式堆回收(见实现处注释)
    void maybeTrimHeap();

本报告由 AI 代码审查工具自动生成

@wangrong1069 wangrong1069 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@Johnson-zs

Copy link
Copy Markdown
Author

/forcemerge

@deepin-bot
deepin-bot Bot merged commit 1f55b83 into linuxdeepin:develop/snipe Oct 26, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants