Skip to content

Commit a4547bb

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 a4547bb

File tree

6 files changed

+546
-23
lines changed

6 files changed

+546
-23
lines changed

tools/swift-inspect/Package.swift

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

0 commit comments

Comments
 (0)