Skip to content

Commit de4c1ad

Browse files
committed
docs(buffering): document GC pressure tracking for unmanaged memory
- Fix incorrect "No GC Pauses" claim in Why Unmanaged Memory section - Add GC Pressure Tracking subsection explaining how NumSharp informs GC - Update transfer ownership example to show AddMemoryPressure best practice
1 parent 43ad43e commit de4c1ad

1 file changed

Lines changed: 18 additions & 6 deletions

File tree

docs/website-src/docs/buffering.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This page explains how to create arrays from existing buffers without copying, h
1212

1313
**Predictable Layout.** Managed arrays can be moved by the garbage collector at any time. Unmanaged memory stays put, which is essential when passing pointers to native libraries or GPU drivers.
1414

15-
**No GC Pauses.** Large managed arrays cause GC pressure. A 1GB NDArray in unmanaged memory doesn't affect GC at all.
15+
**Reduced GC Overhead.** Large managed arrays cause GC pressure and can trigger expensive collections. Unmanaged memory avoids this—though NumSharp still informs the GC about allocation sizes so it can schedule collections appropriately.
1616

1717
**Interop Efficiency.** When calling into native code (BLAS, CUDA, image processing libraries), unmanaged memory can be passed directly without marshaling.
1818

@@ -50,6 +50,14 @@ User Code
5050

5151
**Internal Infrastructure** handles the low-level details: pinning managed arrays so the GC won't move them, tracking ownership so memory gets freed at the right time, and managing the raw pointers. You don't need to interact with these directly—the external APIs handle it for you.
5252

53+
### GC Pressure Tracking
54+
55+
Although NumSharp uses unmanaged memory, the .NET garbage collector still needs to know about it. Otherwise, the GC sees only the small managed wrappers (~100 bytes each) and doesn't realize there's megabytes of unmanaged data attached. This can cause memory to grow unbounded before the GC kicks in.
56+
57+
NumSharp solves this by calling `GC.AddMemoryPressure()` when allocating native memory and `GC.RemoveMemoryPressure()` when freeing it. This applies to arrays created with `np.array()`, `np.zeros()`, `np.empty()`, and similar functions.
58+
59+
For external memory (via `np.frombuffer()` with a dispose callback), the caller is responsible for pressure tracking since NumSharp doesn't know how the memory was allocated.
60+
5361
---
5462

5563
## Creating Arrays from Buffers
@@ -163,17 +171,21 @@ This is appropriate when you're borrowing memory temporarily. You must ensure th
163171

164172
```csharp
165173
// We allocate native memory
166-
IntPtr ptr = Marshal.AllocHGlobal(1024 * sizeof(float));
174+
int bytes = 1024 * sizeof(float);
175+
IntPtr ptr = Marshal.AllocHGlobal(bytes);
176+
GC.AddMemoryPressure(bytes); // Tell GC about this allocation
167177
168178
// Transfer ownership to NumSharp
169-
var arr = np.frombuffer(ptr, 1024 * sizeof(float), typeof(float),
170-
dispose: () => Marshal.FreeHGlobal(ptr));
179+
var arr = np.frombuffer(ptr, bytes, typeof(float),
180+
dispose: () => {
181+
Marshal.FreeHGlobal(ptr);
182+
GC.RemoveMemoryPressure(bytes);
183+
});
171184

172185
// When arr is garbage collected, the dispose action runs
173-
// No manual free needed
174186
```
175187

176-
The `dispose` parameter takes an action that NumSharp calls when the array is no longer needed. This is cleaner for memory you've allocated, but be careful: if you free the memory yourself AND provide a dispose action, you'll double-free.
188+
The `dispose` parameter takes an action that NumSharp calls when the array is no longer needed. For large allocations, pair `GC.AddMemoryPressure()` with `GC.RemoveMemoryPressure()` so the GC knows about your memory. Be careful: if you free the memory yourself AND provide a dispose action, you'll double-free.
177189

178190
### From .NET Buffer Types
179191

0 commit comments

Comments
 (0)