perf(utils): optimize linkPath with safeStat consolidation and batched parallel directory traversal#40
Open
perf(utils): optimize linkPath with safeStat consolidation and batched parallel directory traversal#40
Conversation
Agent-Logs-Url: https://github.com/Shougo/dpp.vim/sessions/aa2fcc21-0873-4fd5-b750-904c7631f4b3 Co-authored-by: Shougo <41495+Shougo@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Improve I/O performance of linkPath function
perf(utils): optimize linkPath with safeStat consolidation and batched parallel directory traversal
Apr 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
linkPathmade redundantstat/lstatcalls per entry and processed directory contents sequentially, making it slow for large trees.Changes
denops/dpp/utils.tsLINK_BATCH_SIZE = 32– controls concurrent I/O per directory levellinkPathInternal(private) – replaces the recursive body oflinkPath:safeStat(src)determines existence and type (eliminates the separateisDirectory(src)call which did its ownDeno.stat)Deno.readDirthen processed in parallel batches withPromise.allSettledDeno.linkon Windows,Deno.symlinkelsewhere)linkPathpublic signature unchanged — delegates tolinkPathInternaldenops/dpp/tests/linkpath_test.ts(new)Tests covering (non-Windows): file symlinking, skip-existing-dest, recursive directory, nested subdirectories, no-op on missing src.
Original prompt
目的
変更内容(詳細)
コミット方針
テスト手順(PR レビュー用)
PR 本文(レビュー向け要約)
要求
The following is the prior conversation context from the user's chat exploration (may be truncated):
User: パッチをまず確認しましょう
Assistant: 以下は Loader のモジュールロードを並列化するためのパッチ案です。要点は「重い I/O(Deno.stat / importPlugin)をロック外で実行し、共有状態への反映(this.#exts / this.#checkPaths)を短時間のクリティカルセクションで行う」ことです。並列で import を試みても最終的な登録処理はロック内で再チェックして一意化するため、二重登録の競合は回避します。
パッチ(denops/dpp/loader.ts の差分)
--- a/denops/dpp/loader.ts
+++ b/denops/dpp/loader.ts
@@ (省略して前後コンテキストを含めた抜粋、変更点に注目してください)
@@ async registerPath(type: DppExtType, path: string): Promise<void> { - await this.#registerLock.lock(async () => { - try { - await this.#register(type, path); - } catch (e) { - if (isDenoCacheIssueError(e)) { - console.warn("*".repeat(80)); - console.warn(`Deno module cache issue is detected.`); - console.warn( - `Execute '!deno cache --reload "${path}"' and restart Vim/Neovim.`, - ); - console.warn("*".repeat(80)); - } - - console.error(`Failed to load file '${path}': ${e}`); - throw e; - } - }); + // Fast-path: already registered. + if (path in this.#checkPaths) { + return; + } + + // Perform file stat and module import outside of the register lock so that + // multiple registerPath calls can do I/O in parallel. + let fileInfo: Deno.FileInfo; + try { + // NOTE: We intentionally use Deno.stat here (keep same behavior). + fileInfo = await Deno.stat(path); + } catch (e) { + // Preserve original error handling semantics. + if (isDenoCacheIssueError(e)) { + console.warn("*".repeat(80)); + console.warn(`Deno module cache issue is detected.`); + console.warn( + `Execute '!deno cache --reload "${path}"' and restart Vim/Neovim.`, + ); + console.warn("*".repeat(80)); + } + console.error(`Failed to load file '${path}': ${e}`); + throw e; + } + + // Import the module (heavy I/O) outside the lock so it can proceed in + // parallel with other registerPath calls. + const mod: Mod = { mod: undefined, path }; + try { + if (fileInfo.isDirectory) { + const entryPoint = join(path, EXT_ENTRY_POINT_FILE); + mod.mod = await importPlugin(entryPoint); + } else { + mod.mod = await importPlugin(path); + } + } catch (e) { + if (isDenoCacheIssueError(e)) { + console.warn("*".repeat(80)); + console.warn(`Deno module cache issue is detected.`); + console.warn( + `Execute '!deno cache --reload "${path}"' and restart Vim/Neovim.`, + ); + console.warn("*".repeat(80)); + } + console.error(`Failed to load file '${path}': ${e}`); + throw e; + } + + // Short critical section: update shared state under lock, but re-check to + // avoid races (another parallel caller may have registered the same path). + await this.#registerLock.lock(async () => { + if (path in this.#checkPaths) { + return; + } + + const typeExt = this.#exts[type]; + let add; + switch (type) { + case "ext": + add = (name: string) => { + const ext = new mod.mod.Ext(); + ext.name = name; + ext.path = path; + typeExt[name] = ext; + }; + break; + case "protocol": + add = (name: string) => { + const e... </details> <!-- START COPILOT CODING AGENT SUFFIX --> *This pull request was created from Copilot chat.* >