Skip to content

Commit c4ea12d

Browse files
committed
Improve the dump-arrays performance on Windows
Use the HeapWalk API for heap iteration instead of the Heap32First/Next API, which was known to be slow. Since HeapWalk only works locally, it requires using a remote thread and a DLL.
1 parent 9f0dcf1 commit c4ea12d

File tree

3 files changed

+441
-22
lines changed

3 files changed

+441
-22
lines changed

tools/swift-inspect/Package.swift

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import PackageDescription
55

66
let package = Package(
77
name: "swift-inspect",
8+
products: [
9+
.library(name: "SwiftInspectHeapWalk", type: .dynamic, targets: ["SwiftInspectHeapWalk"]),
10+
],
811
dependencies: [
912
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"),
1013
],
@@ -16,12 +19,15 @@ let package = Package(
1619
dependencies: [
1720
"SymbolicationShims",
1821
.product(name: "ArgumentParser", package: "swift-argument-parser"),
22+
"SwiftInspectHeapWalk",
1923
],
2024
swiftSettings: [
2125
.unsafeFlags([
2226
"-parse-as-library",
2327
]),
2428
]),
29+
.target(
30+
name: "SwiftInspectHeapWalk"),
2531
.testTarget(
2632
name: "swiftInspectTests",
2733
dependencies: ["swift-inspect"]),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#if defined(_WIN32)
2+
3+
#pragma comment(lib, "swiftCore.lib")
4+
5+
#include <windows.h>
6+
#include <stdio.h>
7+
8+
namespace {
9+
struct HeapEntry {
10+
uintptr_t address;
11+
uintptr_t size;
12+
};
13+
} // anonymous namespace
14+
15+
#define BUF_SIZE 512
16+
#define BUF_NUM_ENTRIES (BUF_SIZE / sizeof(HeapEntry))
17+
#define SHARED_MEM_NAME "Local\\SwiftInspectFileMapping"
18+
#define READ_EVENT_NAME "Local\\SwiftInspectReadEvent"
19+
#define WRITE_EVENT_NAME "Local\\SwiftInspectWriteEvent"
20+
#define WAIT_TIMEOUT_MS 30000
21+
22+
static int heapWalk() {
23+
HANDLE hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, false,
24+
SHARED_MEM_NAME);
25+
if (hMapFile == NULL) {
26+
printf("OpenFileMapping failed\n");
27+
return 1;
28+
}
29+
HeapEntry *buf = (HeapEntry *)
30+
MapViewOfFile(hMapFile,
31+
FILE_MAP_ALL_ACCESS,
32+
0,
33+
0,
34+
BUF_SIZE);
35+
if (buf == NULL) {
36+
printf("MapViewOfFile failed\n");
37+
CloseHandle(hMapFile);
38+
return 1;
39+
}
40+
memset(buf, 0, BUF_SIZE);
41+
HANDLE writeEvent = OpenEventA(EVENT_ALL_ACCESS,
42+
false,
43+
WRITE_EVENT_NAME);
44+
if (writeEvent == NULL) {
45+
printf("OpenEventA failed\n");
46+
CloseHandle(hMapFile);
47+
UnmapViewOfFile(buf);
48+
return 1;
49+
}
50+
HANDLE readEvent = OpenEventA(EVENT_ALL_ACCESS,
51+
false,
52+
READ_EVENT_NAME);
53+
if (readEvent == NULL) {
54+
printf("OpenEventA failed\n");
55+
CloseHandle(hMapFile);
56+
UnmapViewOfFile(buf);
57+
CloseHandle(writeEvent);
58+
return 1;
59+
}
60+
61+
auto cleanupIPC = [&] {
62+
CloseHandle(hMapFile);
63+
UnmapViewOfFile(buf);
64+
CloseHandle(writeEvent);
65+
CloseHandle(readEvent);
66+
};
67+
68+
// Collect heaps. This is a loop because GetProcessHeaps requires
69+
// specifying the max number of heaps to get upfront.
70+
DWORD heapCount = 0;
71+
PHANDLE heaps = NULL;
72+
while (TRUE) {
73+
DWORD actualHeapCount = GetProcessHeaps(heapCount, heaps);
74+
if (actualHeapCount <= heapCount) {
75+
break;
76+
}
77+
heapCount = actualHeapCount;
78+
free(heaps);
79+
heaps = (HANDLE*)malloc(heapCount * sizeof(HANDLE));
80+
if (heaps == NULL) {
81+
printf("malloc failed for the list of heaps\n");
82+
cleanupIPC();
83+
return 1;
84+
}
85+
}
86+
87+
auto cleanup = [&] {
88+
free(heaps);
89+
cleanupIPC();
90+
};
91+
92+
// Iterate heaps and heap entries
93+
size_t count = 0;
94+
for (DWORD heapIndex = 0; heapIndex < heapCount; heapIndex++) {
95+
PROCESS_HEAP_ENTRY entry;
96+
97+
if (!HeapLock(heaps[heapIndex])) {
98+
printf("Failed to lock heap\n");
99+
continue;
100+
}
101+
102+
uintptr_t i = 0;
103+
entry.lpData = NULL;
104+
while (HeapWalk(heaps[heapIndex], &entry)) {
105+
if ((!(entry.wFlags & PROCESS_HEAP_REGION)) &&
106+
(!(entry.wFlags & PROCESS_HEAP_UNCOMMITTED_RANGE)) &&
107+
(entry.wFlags & PROCESS_HEAP_ENTRY_BUSY)) {
108+
if (count < BUF_NUM_ENTRIES) {
109+
buf[count].address = (uintptr_t)entry.lpData;
110+
buf[count].size = entry.cbData + entry.cbOverhead;
111+
++count;
112+
} else {
113+
if (!SetEvent(readEvent)) {
114+
printf("SetEvent on readEvent failed\n");
115+
HeapUnlock(heaps[heapIndex]);
116+
cleanup();
117+
return 1;
118+
}
119+
DWORD wait = WaitForSingleObject(writeEvent, WAIT_TIMEOUT_MS);
120+
if (wait != WAIT_OBJECT_0) {
121+
printf("WaitForSingleObject failed %lu\n", wait);
122+
HeapUnlock(heaps[heapIndex]);
123+
cleanup();
124+
return 1;
125+
}
126+
memset(buf, 0, BUF_SIZE);
127+
count = 0;
128+
}
129+
}
130+
}
131+
132+
// Write the remaining entries.
133+
if (!SetEvent(readEvent)) {
134+
printf("SetEvent on readEvent failed\n");
135+
HeapUnlock(heaps[heapIndex]);
136+
cleanup();
137+
return 1;
138+
}
139+
if (count > 0) {
140+
DWORD wait = WaitForSingleObject(writeEvent, WAIT_TIMEOUT_MS);
141+
if (wait != WAIT_OBJECT_0) {
142+
printf("WaitForSingleObject failed %lu\n", wait);
143+
HeapUnlock(heaps[heapIndex]);
144+
cleanup();
145+
return 1;
146+
}
147+
memset(buf, 0, BUF_SIZE);
148+
count = 0;
149+
}
150+
151+
if (!HeapUnlock(heaps[heapIndex])) {
152+
printf("Failed to unlock heap\n");
153+
}
154+
}
155+
156+
// Indicate the end of iteration with one last write.
157+
memset(buf, 0, BUF_SIZE);
158+
buf[0].address = -1;
159+
if (!SetEvent(readEvent)) {
160+
printf("SetEvent at the end of heap iteration failed\n");
161+
cleanup();
162+
return 1;
163+
}
164+
DWORD wait = WaitForSingleObject(writeEvent, WAIT_TIMEOUT_MS);
165+
if (wait != WAIT_OBJECT_0) {
166+
printf("WaitForSingleObject failed %lu\n", wait);
167+
cleanup();
168+
return 1;
169+
}
170+
171+
cleanup();
172+
return 0;
173+
}
174+
175+
BOOL APIENTRY DllMain(HANDLE hModule,
176+
DWORD ul_reason_for_call,
177+
LPVOID lpReserved) {
178+
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
179+
heapWalk();
180+
}
181+
return TRUE;
182+
}
183+
184+
#endif

0 commit comments

Comments
 (0)