Skip to content

Commit 4c090b9

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 12ebe0a commit 4c090b9

File tree

5 files changed

+512
-23
lines changed

5 files changed

+512
-23
lines changed

tools/swift-inspect/Package.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
// swift-tools-version:5.2
1+
// swift-tools-version:5.3
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription
55

66
let package = Package(
77
name: "swift-inspect",
8+
products: [
9+
.library(name: "SwiftInspectClient", type: .dynamic, targets: ["SwiftInspectClient"]),
10+
],
811
dependencies: [
912
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"),
1013
],
@@ -16,12 +19,18 @@ let package = Package(
1619
dependencies: [
1720
"SymbolicationShims",
1821
.product(name: "ArgumentParser", package: "swift-argument-parser"),
22+
.target(name: "SwiftInspectClient", condition: .when(platforms: [.windows])),
23+
.target(name: "SwiftInspectClientShims", condition: .when(platforms: [.windows])),
1924
],
2025
swiftSettings: [
2126
.unsafeFlags([
2227
"-parse-as-library",
2328
]),
2429
]),
30+
.target(
31+
name: "SwiftInspectClient"),
32+
.systemLibrary(
33+
name: "SwiftInspectClientShims"),
2534
.testTarget(
2635
name: "swiftInspectTests",
2736
dependencies: ["swift-inspect"]),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if defined(_WIN32)
14+
15+
#pragma comment(lib, "swiftCore.lib")
16+
17+
#include "../SwiftInspectClientShims/SwiftInspectClientShims.h"
18+
#include <memory>
19+
#include <stdio.h>
20+
#include <strsafe.h>
21+
#include <vector>
22+
#include <windows.h>
23+
24+
namespace {
25+
struct HeapEntry {
26+
uintptr_t Address;
27+
uintptr_t Size;
28+
};
29+
30+
struct ScopedHandle {
31+
HANDLE Handle;
32+
explicit ScopedHandle(HANDLE Handle) noexcept : Handle(Handle) {}
33+
~ScopedHandle() noexcept {
34+
if (Handle != NULL) {
35+
CloseHandle(Handle);
36+
}
37+
}
38+
HANDLE get() const { return Handle; }
39+
};
40+
41+
struct ScopedViewOfFile {
42+
void *View;
43+
explicit ScopedViewOfFile(void *View) noexcept : View(View) {}
44+
~ScopedViewOfFile() noexcept {
45+
if (View != NULL) {
46+
UnmapViewOfFile(View);
47+
}
48+
}
49+
void *get() const { return View; }
50+
template <typename T> T *as() const { return reinterpret_cast<T *>(View); }
51+
};
52+
53+
struct ScopedHeapLock {
54+
HANDLE Heap;
55+
bool Failure = false;
56+
explicit ScopedHeapLock(HANDLE Heap) noexcept : Heap(Heap) {
57+
if (!HeapLock(Heap)) {
58+
OutputDebugStringA("Failed to lock heap\n");
59+
Failure = true;
60+
}
61+
}
62+
~ScopedHeapLock() noexcept {
63+
if (Heap != NULL && !Failure) {
64+
if (!HeapUnlock(Heap)) {
65+
OutputDebugStringA("Failed to lock heap\n");
66+
}
67+
}
68+
}
69+
};
70+
71+
} // anonymous namespace
72+
73+
#define BUF_NUM_ENTRIES (BUF_SIZE / sizeof(HeapEntry))
74+
75+
static int heapWalk() {
76+
// Format the shared mem and event object names
77+
DWORD Pid = GetCurrentProcessId();
78+
char SharedMemName[128];
79+
char ReadEventName[128];
80+
char WriteEventName[128];
81+
if (StringCbPrintfA(SharedMemName, sizeof(SharedMemName), "%hS-%lu",
82+
SHARED_MEM_NAME_PREFIX, Pid) != S_OK) {
83+
OutputDebugStringA("StringCbPrintfA for SharedMemName failed\n");
84+
return 1;
85+
}
86+
if (StringCbPrintfA(ReadEventName, sizeof(ReadEventName), "%hS-%lu",
87+
READ_EVENT_NAME_PREFIX, Pid) != S_OK) {
88+
OutputDebugStringA("StringCbPrintfA for ReadEventName failed\n");
89+
return 1;
90+
}
91+
if (StringCbPrintfA(WriteEventName, sizeof(WriteEventName), "%hS-%lu",
92+
WRITE_EVENT_NAME_PREFIX, Pid) != S_OK) {
93+
OutputDebugStringA("StringCbPrintfA for WriteEventName failed\n");
94+
return 1;
95+
}
96+
97+
ScopedHandle MapFile(
98+
OpenFileMappingA(FILE_MAP_ALL_ACCESS, false, SharedMemName));
99+
if (MapFile.get() == NULL) {
100+
OutputDebugStringA("OpenFileMapping failed\n");
101+
return 1;
102+
}
103+
ScopedViewOfFile Buf(
104+
MapViewOfFile(MapFile.Handle, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE));
105+
if (Buf.get() == NULL) {
106+
OutputDebugStringA("MapViewOfFile failed\n");
107+
return 1;
108+
}
109+
std::memset(Buf.get(), 0, BUF_SIZE);
110+
ScopedHandle WriteEvent(OpenEventA(EVENT_ALL_ACCESS, false, WriteEventName));
111+
if (WriteEvent.get() == NULL) {
112+
OutputDebugStringA("OpenEventA failed\n");
113+
return 1;
114+
}
115+
ScopedHandle ReadEvent(OpenEventA(EVENT_ALL_ACCESS, false, ReadEventName));
116+
if (ReadEvent.get() == NULL) {
117+
OutputDebugStringA("OpenEventA failed\n");
118+
return 1;
119+
}
120+
121+
// Collect heaps. This is a loop because GetProcessHeaps requires
122+
// specifying the max number of heaps to get upfront.
123+
std::vector<HANDLE> Heaps;
124+
while (TRUE) {
125+
DWORD ActualHeapCount = GetProcessHeaps(Heaps.size(), Heaps.data());
126+
if (ActualHeapCount <= Heaps.size()) {
127+
Heaps.resize(ActualHeapCount);
128+
break;
129+
}
130+
Heaps.resize(ActualHeapCount);
131+
}
132+
133+
// Iterate heaps and heap entries
134+
size_t Count = 0;
135+
for (HANDLE Heap : Heaps) {
136+
PROCESS_HEAP_ENTRY Entry;
137+
138+
ScopedHeapLock HeapLock(Heap);
139+
if (HeapLock.Failure) {
140+
continue;
141+
}
142+
143+
Entry.lpData = NULL;
144+
while (HeapWalk(Heap, &Entry)) {
145+
if ((!(Entry.wFlags & PROCESS_HEAP_REGION)) &&
146+
(!(Entry.wFlags & PROCESS_HEAP_UNCOMMITTED_RANGE)) &&
147+
(Entry.wFlags & PROCESS_HEAP_ENTRY_BUSY)) {
148+
if (Count < BUF_NUM_ENTRIES) {
149+
Buf.as<HeapEntry>()[Count].Address =
150+
reinterpret_cast<uintptr_t>(Entry.lpData);
151+
Buf.as<HeapEntry>()[Count].Size = Entry.cbData + Entry.cbOverhead;
152+
++Count;
153+
} else {
154+
if (!SetEvent(ReadEvent.get())) {
155+
OutputDebugStringA("SetEvent on ReadEvent failed\n");
156+
return 1;
157+
}
158+
DWORD Wait = WaitForSingleObject(WriteEvent.get(), WAIT_TIMEOUT_MS);
159+
if (Wait != WAIT_OBJECT_0) {
160+
char Msg[128];
161+
if (StringCbPrintfA(Msg, sizeof(Msg),
162+
"WaitForSingleObject failed %lu\n",
163+
Wait) == S_OK) {
164+
OutputDebugStringA(Msg);
165+
}
166+
return 1;
167+
}
168+
std::memset(Buf.get(), 0, BUF_SIZE);
169+
Count = 0;
170+
}
171+
}
172+
}
173+
174+
// Write the remaining entries.
175+
if (!SetEvent(ReadEvent.get())) {
176+
OutputDebugStringA("SetEvent on ReadEvent failed\n");
177+
return 1;
178+
}
179+
if (Count > 0) {
180+
DWORD Wait = WaitForSingleObject(WriteEvent.get(), WAIT_TIMEOUT_MS);
181+
if (Wait != WAIT_OBJECT_0) {
182+
char Msg[128];
183+
if (StringCbPrintfA(Msg, sizeof(Msg),
184+
"WaitForSingleObject failed %lu\n", Wait) == S_OK) {
185+
OutputDebugStringA(Msg);
186+
}
187+
return 1;
188+
}
189+
std::memset(Buf.get(), 0, BUF_SIZE);
190+
Count = 0;
191+
}
192+
}
193+
194+
// Indicate the end of iteration with one last write.
195+
std::memset(Buf.get(), 0, BUF_SIZE);
196+
Buf.as<HeapEntry>()[0].Address = -1;
197+
if (!SetEvent(ReadEvent.get())) {
198+
OutputDebugStringA("SetEvent at the end of heap iteration failed\n");
199+
return 1;
200+
}
201+
DWORD Wait = WaitForSingleObject(WriteEvent.get(), WAIT_TIMEOUT_MS);
202+
if (Wait != WAIT_OBJECT_0) {
203+
char Msg[128];
204+
if (StringCbPrintfA(Msg, sizeof(Msg), "WaitForSingleObject failed %lu\n",
205+
Wait) == S_OK) {
206+
OutputDebugStringA(Msg);
207+
}
208+
return 1;
209+
}
210+
211+
return 0;
212+
}
213+
214+
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call,
215+
LPVOID lpReserved) {
216+
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
217+
heapWalk();
218+
}
219+
return TRUE;
220+
}
221+
222+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if defined(_WIN32)
14+
15+
#define BUF_SIZE 512
16+
#define SHARED_MEM_NAME_PREFIX "Local\\SwiftInspectFileMapping"
17+
#define READ_EVENT_NAME_PREFIX "Local\\SwiftInspectReadEvent"
18+
#define WRITE_EVENT_NAME_PREFIX "Local\\SwiftInspectWriteEvent"
19+
#define WAIT_TIMEOUT_MS 30000
20+
21+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module SwiftInspectClientShims {
2+
header "SwiftInspectClientShims.h"
3+
export *
4+
}

0 commit comments

Comments
 (0)