Skip to content

Refacto/33 parallel parsing#34

Open
c5ln wants to merge 4 commits into
mainfrom
refacto/33-parallel-parsing
Open

Refacto/33 parallel parsing#34
c5ln wants to merge 4 commits into
mainfrom
refacto/33-parallel-parsing

Conversation

@c5ln

@c5ln c5ln commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

PythonParser::parseDirectory의 파일 단위 파싱을 멀티스레드로 병렬화하여 스캔 속도를 개선한다. 사전 벤치마크로 파싱이 전체 스캔 시간의 71%를 차지하는 지배 병목임을 확인했고(src/parser/BENCHMARK.md), 병렬화 결과 파싱 5.9배 / 전체 스캔 2.5배 개선을 달성했다. 병렬화 전후 스캔 결과는 DB 덤프 기준 완전히 동일하다.

Changes

  • 벤치마크 인프라 구축: scan_main.cpp에 단계별 타이머 추가, -O2 측정용 scripts/bench_parser.sh 작성, baseline 및 결과를 src/parser/BENCHMARK.md에 기록
  • PythonParser stateless화: 스레드 간 공유가 불가능한 TSParser 멤버(m_parser)를 제거하고, std::unique_ptr<TSParser, TsParserDeleter> + makeTsParser() 팩토리로 호출마다 로컬 생성 (생성 비용 실측 ~0.0003ms/회로 파싱 시간의 0.01% — 재사용 최적화 불필요). IParser 인터페이스 및 호출부 변경 없음
  • parseDirectory 병렬화: "파일 목록 선수집 → 워커 풀 병렬 파싱 → join 배리어 → 순차 cross-file resolve" 구조로 변경. 스레드 수는 hardware_concurrency(파일 수 초과 시 축소), std::atomic 카운터로 작업 분배, 사전 할당된 results[i]에 인덱스 기록으로 락 없이 결과 순서 결정적 유지, 워커 본문 try/catch로 예외 격리
  • 빌드 설정: CMakeLists.txt에 find_package(Threads REQUIRED) 추가, TelescodeScanner/TelescodeUpdater에 Threads::Threads 링크

성능 비교 (Python 3.11 stdlib, 680개 파일, 12코어, -O2, 3회 측정)

파싱

  • 병렬화 전 => ~2,220 ms
  • 병렬화 후 => ~377 ms
  • 5.9배

재현: scripts/bench_parser.sh

정확성 검증

  • 병렬화 전/후 stdlib 스캔 DB 전체 테이블 정렬 덤프 109,034줄 완전 일치
  • ctest 3개 스위트 통과, 소형 저장소(sherlock) 및 빈 디렉토리 엣지 케이스 정상 동작

Related Issue

Closes #33

Checklist

  • Code compiles without errors
  • Self-reviewed the diff
  • No unintended files included

@c5ln c5ln self-assigned this Jul 12, 2026
@c5ln c5ln linked an issue Jul 12, 2026 that may be closed by this pull request
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Python directory parsing now runs concurrently with per-file result indexing and post-parse cross-file resolution. Parser ownership uses local RAII wrappers, scanner timing is reported, benchmark tooling and documentation are added, and CMake links thread support.

Changes

Parallel Python Parsing

Layer / File(s) Summary
Stateless parser and AST analysis
src/parser/PythonParser.h, src/parser/PythonParser.cpp, src/algo/Scoring.h
PythonParser removes persistent parser state, creates local RAII-managed tree-sitter parsers, and retains AST metrics, relationship collection, and call resolution.
Parallel directory execution
src/parser/PythonParser.cpp
parseDirectory collects Python files, processes them through worker threads using an atomic index, stores results by deterministic index, and isolates per-file exceptions.
Timing and benchmark reproduction
src/scan_main.cpp, scripts/bench_parser.sh, src/parser/BENCHMARK.md
Scanner timings cover parsing and database insertion; the benchmark script builds and runs an optimized scanner, and documentation records measurements and reproduction steps.
Thread-enabled build targets
CMakeLists.txt
CMake discovers Threads and links Threads::Threads to the scanner and updater targets.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant scan_main
  participant PythonParser
  participant WorkerThreads
  participant DbInserter
  scan_main->>PythonParser: parseDirectory(target)
  PythonParser->>WorkerThreads: parse files concurrently
  WorkerThreads-->>PythonParser: indexed ParseResults
  PythonParser-->>scan_main: resolve cross-file calls and return results
  scan_main->>DbInserter: insertAll(results)
  DbInserter-->>scan_main: return insertion status
Loading

Possibly related PRs

  • c5ln/Telescode#10: Refactors and parallelizes the existing Python parser and parser lifetime.
  • c5ln/Telescode#13: Introduces the scan pipeline components modified for timing and thread linking.
  • c5ln/Telescode#30: Modifies the parser’s AST metric logic touched by this parallelization.

Suggested labels: feature

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning src/algo/Scoring.h adds an unrelated comment about future std::thread parallelization, which is outside the parsing and benchmark scope. Remove the unrelated Scoring.h comment or move the note into documentation tied to this PR's parser changes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly reflects the main change: parallel parsing.
Description check ✅ Passed The PR description follows the template with Summary, Changes, Related Issue, and Checklist sections filled in.
Linked Issues check ✅ Passed The changes match #33: parser state was removed, parseDirectory was parallelized, cross-file resolution stayed sequential, and benchmarks were added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refacto/33-parallel-parsing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/bench_parser.sh (1)

35-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer -pthread over -lpthread for the compiler flag.

-pthread is the recommended way to enable threading support with g++ — it sets both the correct preprocessor defines and linker flags. -lpthread alone works on Linux/glibc but may not on all platforms. Since this is a benchmark script, correctness of the binary matters for valid measurements.

♻️ Suggested change
 g++ -O2 -std=c++17 -I"$ROOT/src" -I"$DEPS/tree-sitter-src/lib/include" -I"$ROOT/third_party/sqlite" \
     "$ROOT/src/scan_main.cpp" "$ROOT/src/db/DbInserter.cpp" "$ROOT/src/db/db.cpp" \
     "$ROOT/src/parser/PythonParser.cpp" "$ROOT/src/parser/ParserRegistry.cpp" \
     "$OUT"/ts.o "$OUT"/tsp_parser.o "$OUT"/tsp_scanner.o "$OUT"/sqlite3.o \
-    -lpthread -ldl -o "$OUT/scanner_bench"
+    -pthread -ldl -o "$OUT/scanner_bench"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/bench_parser.sh` around lines 35 - 39, Update the g++ link command in
the benchmark build to use the compiler’s -pthread option instead of the
standalone -lpthread flag, while preserving the existing source files,
libraries, and output target.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/parser/PythonParser.cpp`:
- Around line 973-978: Update the catch-all block in the parseFile loop to log
the failed file path to std::cerr before continuing, using files[i] to identify
the file while preserving the existing behavior of leaving results[i] empty and
processing remaining files.

---

Nitpick comments:
In `@scripts/bench_parser.sh`:
- Around line 35-39: Update the g++ link command in the benchmark build to use
the compiler’s -pthread option instead of the standalone -lpthread flag, while
preserving the existing source files, libraries, and output target.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 843a35b5-dfd7-49b9-bfe4-ef79af5fae3e

📥 Commits

Reviewing files that changed from the base of the PR and between 3f545fc and f4d875f.

⛔ Files ignored due to path filters (6)
  • output/sherlock/base_class.csv is excluded by !**/*.csv
  • output/sherlock/class.csv is excluded by !**/*.csv
  • output/sherlock/file.csv is excluded by !**/*.csv
  • output/sherlock/function.csv is excluded by !**/*.csv
  • output/sherlock/link.csv is excluded by !**/*.csv
  • output/sherlock/param.csv is excluded by !**/*.csv
📒 Files selected for processing (7)
  • CMakeLists.txt
  • scripts/bench_parser.sh
  • src/algo/Scoring.h
  • src/parser/BENCHMARK.md
  • src/parser/PythonParser.cpp
  • src/parser/PythonParser.h
  • src/scan_main.cpp
💤 Files with no reviewable changes (1)
  • src/parser/PythonParser.h

Comment on lines +973 to +978
try {
results[i] = parseFile(files[i], rootStr);
} catch (...) {
// 해당 파일만 빈 결과로 남기고 계속 진행.
// (std::thread에서 예외가 새면 프로세스가 종료됨)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Log failed file paths for debuggability.

The catch (...) block silently swallows parse exceptions. When a file fails to parse, there is no indication which file caused the failure, making it difficult to diagnose issues in large repositories. A simple std::cerr with the file path would help.

💡 Suggested improvement
             try {
                 results[i] = parseFile(files[i], rootStr);
             } catch (...) {
-                // 해당 파일만 빈 결과로 남기고 계속 진행.
-                // (std::thread에서 예외가 새면 프로세스가 종료됨)
+                // 해당 파일만 빈 결과로 남기고 계속 진행.
+                // (std::thread에서 예외가 나면 프로세스가 종료됨)
+                std::cerr << "[PythonParser] Parse failed: " << files[i] << "\n";
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
results[i] = parseFile(files[i], rootStr);
} catch (...) {
// 해당 파일만 빈 결과로 남기고 계속 진행.
// (std::thread에서 예외가 새면 프로세스가 종료됨)
}
try {
results[i] = parseFile(files[i], rootStr);
} catch (...) {
// 해당 파일만 빈 결과로 남기고 계속 진행.
// (std::thread에서 예외가 나면 프로세스가 종료됨)
std::cerr << "[PythonParser] Parse failed: " << files[i] << "\n";
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/parser/PythonParser.cpp` around lines 973 - 978, Update the catch-all
block in the parseFile loop to log the failed file path to std::cerr before
continuing, using files[i] to identify the file while preserving the existing
behavior of leaving results[i] empty and processing remaining files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[refactoring] Parallel Parsing

1 participant