-
Notifications
You must be signed in to change notification settings - Fork 0
perf: use a single snapshot for all functions #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThreaded a single ToolHelp snapshot HANDLE through process-inspection call chains in Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai uuuuuuuuuuughhhhhhhhh WHAT IS HAPPENINGGGGGGG 😡😡😡😤😤🤬🤬 |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain
🏁 Script executed: Length of output: 1424 🏁 Script executed: Length of output: 220 🏁 Script executed: Length of output: 0
Line 111: void EnsureCurrentParentExe(hSnapshot) { // ❌ Missing type!Should be: void EnsureCurrentParentExe(HANDLE hSnapshot) { // ✅Line 1728: void PIDinspect(const std::vector<DWORD>& pids, const std::vector<std::string>& names, hshot) {Should be: void PIDinspect(const std::vector<DWORD>& pids, const std::vector<std::string>& names, HANDLE hshot) {Also, line 143 has Let me check for more functions with the same issue: 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 128 🏁 Script executed: Length of output: 0 🏁 Script executed: Length of output: 275 Found all of them! You're missing Quick Fix List:Line 111: void EnsureCurrentParentExe(HANDLE hSnapshot) {Line 313: void PrintErrorHints(int errorCode, HANDLE hshot) {Line 1492: void PrintAncestry(DWORD pid, HANDLE hSnapshot) {Line 1728: void PIDinspect(const std::vector<DWORD>& pids, const std::vector<std::string>& names, HANDLE hshot) {Line 1990: ProcInfos findMyProc(const char *procname, HANDLE hSnapshot) {🏁 Script executed: Length of output: 166 🏁 Script executed: Length of output: 755 Perfect! Here's the complete fix for your refactoring: 🔧 Part 1: Add
|
|
@coderabbitai oh damn you're right i'm so stupid i forgot to declare the types |
|
Tip For best results, initiate chat on the files or code changes.
Once you add those 🧠 Learnings used |
|
@coderabbitai DANG IT I REALLY THOGUHT THIS WOULD WORK! it compiles... but the average speed is still 18ms... and really nested trees still take 300 ms.🤬🤬😡😡🤬🤬😤😤😤😤😤😤😤😤😤😤 |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 145 🏁 Script executed: Length of output: 5600 🏁 Script executed: Length of output: 1569 🏁 Script executed: Length of output: 7760 🏁 Script executed: Length of output: 7402
🐛 The Critical Issues:Lines 2139 and 2160 in main(): HANDLE hshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // ❌ Still here!HANDLE hshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // ❌ And here too!Line 1609 in PrintAncestry: CloseHandle(hSnapshot); // ❌ You're closing it INSIDE the function!Line 2144 in main: CloseHandle(hshot); // ❌ Closing too early💥 Why Performance Got WORSE (300ms instead of 248ms):
✅ The Complete Fix:Here's what you need to do:
int main(int argc, char* argv[]) {
SetConsoleOutputCP(CP_UTF8);
// 🎯 CREATE ONE SNAPSHOT HERE FOR THE ENTIRE PROGRAM
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to create process snapshot" << std::endl;
return 1;
}
// ... rest of main
CloseHandle(hSnapshot);
return 0;
}🚀 Bonus Optimization for PrintAncestry:After the above fixes, you can get even better performance by building a PID→process map once at the start of PrintAncestry instead of calling Process32First three times. This would make it truly O(n): // At the start of PrintAncestry, build a map once:
std::unordered_map<DWORD, PROCESSENTRY32> pidMap;
PROCESSENTRY32 pe32{};
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe32)) {
do {
pidMap[pe32.th32ProcessID] = pe32;
} while (Process32Next(hSnapshot, &pe32));
}
// Now use pidMap for O(1) lookups instead of walking the snapshot 3 times!This should bring you back to ~248ms or even better! 🎯 🏁 Script executed: Length of output: 179 🏁 Script executed: Length of output: 13 Perfect! Now I can give you the exact line-by-line fixes: 🔧 Step-by-Step Fix:1. Add ONE snapshot at line 2043 (right after
|
|
@coderabbitai oooohhh i didn't know process32next created a new snapshot every time |
|
Tip For best results, initiate chat on the files or code changes.
Here's what these APIs actually do:
All three functions work with the same snapshot handle you created once with 🐛 The Real Performance Problem Was:
The fix is:
That's how you'll get back to ~248ms! The snapshot itself is static once created - it's just how you were using it that caused the O(n²) slowdown. 🎯 🧠 Learnings used |
|
OOOMMMMMMMMGGGGGGGGGG @coderabbitai you won't believe this |
|
R.i.p @coderabbitai |
|
Tip For best results, initiate chat on the files or code changes.
29.2871ms for a 100-level deep tree!?!?That's ~8.5x FASTER than your previous best of 248ms, and ~34x FASTER than the original 1000ms! 😱 The Journey:
You absolutely CRUSHED IT! The combination of:
...turned this into an absolute speed demon! 💪 This is the kind of optimization that makes other developers jealous. The stress test is now basically instantaneous! 🎯 Not dead, just in awe! 🤯 🧠 Learnings usedIf you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
|
I need.... to shave... Off .... more... milliseconds... 🥵🥵🥵🥵🥵🥵🥵 What can I do!!! Also can you start a review idk why it's not working auto |
🧩 Analysis chain
I'll also kick off the review for you! 🏁 Script executed: Length of output: 7708 🏁 Script executed: Length of output: 1712 🏁 Script executed: Length of output: 3160 🏁 Script executed: Length of output: 3
🐛 Critical Bug First (Line 1609):CloseHandle(hSnapshot); // ❌ DELETE THIS LINE!You're still closing the snapshot inside PrintAncestry! This is wrong since you're now passing a shared snapshot. Remove this line completely. 🚀 Performance Optimizations (Ranked by Impact):1. Cache
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
main.cpp (2)
1606-1606:⚠️ Potential issue | 🔴 CriticalBug:
PrintAncestrycloses the caller's shared snapshot handle — causes double-close and use-after-close.This is the shared snapshot handle passed from
main→PIDinspect→PrintAncestry. Closing it here invalidates the handle for any subsequent use and causes a double-close whenmaincallsCloseHandle(hshot)at lines 2142/2166. The PR objective is specifically to share a single snapshot, so this function must not close it.🐛 Proposed fix
-CloseHandle(hSnapshot); // we're only closing the handle until we finish messing with the snapshot
2160-2173:⚠️ Potential issue | 🔴 CriticalBug:
return {};invalid forint main(), and snapshot handle leaked when process is not found.Same
return {};issue as the--pidpath. Additionally, whenr.pidsis empty (line 2167), execution falls into theelsebranch which prints an error but never closeshshot, leaking the handle.🐛 Proposed fix
- if (INVALID_HANDLE_VALUE == hshot) {return {};} + if (INVALID_HANDLE_VALUE == hshot) {return 1;} ProcInfos r = findMyProc(procName.c_str(), hshot); if (!r.pids.empty()) { std::vector<DWORD> dwPids(r.pids.begin(), r.pids.end()); PIDinspect(dwPids, r.names, hshot); CloseHandle(hshot); } else { + CloseHandle(hshot); if (IsVirtualTerminalModeEnabled()) { std::cerr << "\033[1;31mError:\033[0m Could not find process with name " << procName << "." << std::endl; } else { std::cerr << "Error: Could not find process with name " << procName << "." << std::endl; } }
🤖 Fix all issues with AI agents
In `@main.cpp`:
- Around line 2139-2142: Replace the invalid `return {};` in main's
CreateToolhelp32Snapshot error path with `return 1;` to signal failure, and
eliminate the double-close by making the snapshot handle lifetime consistent:
remove the CloseHandle call inside PrintAncestry (so PIDinspect/PrintAncestry no
longer closes hshot) and keep the single CloseHandle(hshot) in main after
PIDinspect(pids, trash, hshot); ensuring CreateToolhelp32Snapshot, hshot,
PIDinspect, and PrintAncestry are updated accordingly.
🧹 Nitpick comments (3)
main.cpp (3)
1577-1598:emplaceatbegin()onstd::vectoris O(n) per insertion — consider appending then reversing.The TODO comment on line 1582-1583 already acknowledges this. You're already reversing all vectors at lines 1611-1614, so you could just
emplace_backhere and let the reverse handle ordering. This avoids O(c²) element shifts wherecis the number of children.♻️ Suggested change
- exeNames.emplace(exeNames.begin(), exeName); + exeNames.emplace_back(exeName); // ... - pidNames.emplace(pidNames.begin(), entry.th32ProcessID); + pidNames.emplace_back(entry.th32ProcessID); // ... - exeTimes.emplace(exeTimes.begin(), childTime); + exeTimes.emplace_back(childTime); - parentPids.emplace(parentPids.begin(), entry.th32ProcessID); + parentPids.emplace_back(entry.th32ProcessID);
1548-1556:GetProcessCreationTimeopens/closes a process handle per ancestor level — consider caching.Each call to
GetProcessCreationTime(lines 1548 and 1556) doesOpenProcess+GetProcessTimes+CloseHandle. For a deep tree this adds up. Since you already have thepidMap, you could build a creation-time cache during the same loop or lazily populate one to avoid redundant kernel calls.
433-450:GetProcessNameFromPidstill walks the snapshot linearly — could use the pidMap pattern.This function is called from
PIDinspect(line 1727) beforePrintAncestrybuilds its map. If you built the map earlier (e.g., inPIDinspectormain) and passed it to all consumers, you'd avoid redundant O(n) walks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
main.cpp (1)
2192-2205:⚠️ Potential issue | 🟠 MajorBug: snapshot handle leaked when process name is not found.
CloseHandle(hshot)is only called inside theif (!r.pids.empty())branch (line 2198). When the process isn't found, the else branch prints an error but never closeshshot.🐛 Proposed fix — close handle unconditionally
if (!r.pids.empty()) { std::vector<DWORD> dwPids(r.pids.begin(), r.pids.end()); PIDinspect(dwPids, r.names, hshot); - CloseHandle(hshot); } else { if (virtualTerminalEnabled) { std::cerr << "\033[1;31mError:\033[0m Could not find process with name " << procName << "." << std::endl; } else { std::cerr << "Error: Could not find process with name " << procName << "." << std::endl; } } + CloseHandle(hshot);
🧹 Nitpick comments (4)
main.cpp (4)
279-282:pidMapoverload only filters—it doesn't cache creation times.The PR discussion suggested caching process creation times during the initial map build to avoid repeated
OpenProcess/GetProcessTimescalls. This overload still calls the originalGetProcessCreationTime(pid)which opens a new handle every time. InPrintAncestry, this is called once per ancestor (line 1570, 1585), so deep trees still incur O(depth) handle opens.To actually cache, you could build a
std::unordered_map<DWORD, ULONGLONG>of creation times alongside the pidMap, or extend the map's value type to include the cached time.
319-338: DRY:GetReadableFileTimebody is copy-pasted.The only difference from the original (lines 291–317) is which
GetProcessCreationTimeoverload is called. Extract the formatting into a shared helper:♻️ Suggested refactor
+std::string FormatCreationTime(ULONGLONG creationTime) { + if (creationTime == 0) return "N/A"; + time_t unixTime = windows_time_to_unix_epoch(creationTime); + time_t now = std::time(nullptr); + double diffSeconds = std::difftime(now, unixTime); + + std::string ago; + if (diffSeconds < 60) ago = std::to_string((int)diffSeconds) + " seconds ago"; + else if (diffSeconds < 3600) ago = std::to_string((int)diffSeconds / 60) + " minutes ago"; + else if (diffSeconds < 86400) ago = std::to_string((int)diffSeconds / 3600) + " hours ago"; + else ago = std::to_string((int)diffSeconds / 86400) + " days ago"; + + std::tm bt{}; + localtime_s(&bt, &unixTime); + std::ostringstream oss; + oss << ago << " (" << std::put_time(&bt, "%a %Y-%m-%d %H:%M:%S %z") << ")"; + return oss.str(); +} + std::string GetReadableFileTime(DWORD pid) { - ULONGLONG creationTime = GetProcessCreationTime(pid); - if (creationTime == 0) return "N/A"; - // ... duplicated formatting ... + return FormatCreationTime(GetProcessCreationTime(pid)); } std::string GetReadableFileTime(DWORD pid, const std::unordered_map<DWORD, PROCESSENTRY32>& pidMap) { - ULONGLONG creationTime = GetProcessCreationTime(pid, pidMap); - if (creationTime == 0) return "N/A"; - // ... duplicated formatting ... + return FormatCreationTime(GetProcessCreationTime(pid, pidMap)); }
1748-1758:PIDinspectbuildspidMapthen immediately does a linear scan viaGetProcessNameFromPid.Line 1758 calls
GetProcessNameFromPid(pid, hshot)which walks the snapshot fromProcess32First. But you just builtpidMap(lines 1750–1757) with O(1) lookup. Use the map instead:♻️ Suggested fix
- std::string procName = GetProcessNameFromPid(pid, hshot); + std::string procName; + auto it = pidMap.find(pid); + if (it != pidMap.end()) { + procName = WideToString(it->second.szExeFile); + }
1572-1598: Nice: ancestry walk is now O(depth) with O(1) map lookups.This is a major improvement over the previous O(n²) repeated snapshot walks. The
pidMap.find()approach at lines 1574–1576 is clean.One subtle note on correctness:
creationTime(line 1570) is fixed to the target process's creation time and never updated per ancestor level. This means PID-reuse detection is coarser than comparing each child→parent pair's times. Consider updatingcreationTimeas you walk up:} else { pid = parentPid; + creationTime = parentTime; }This makes the stale-parent check tighter for mid-chain PID reuse.
Summary by CodeRabbit
Refactor
Bug Fixes