Skip to content

Commit f776ae2

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 f776ae2

File tree

6 files changed

+624
-60
lines changed

6 files changed

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

0 commit comments

Comments
 (0)