-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMainWindow.xaml.cs
More file actions
248 lines (204 loc) · 10.2 KB
/
MainWindow.xaml.cs
File metadata and controls
248 lines (204 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
using Accessibility;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows;
namespace GarbageCollectionExample_WPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
// Manual memory allocation
private void btnManualAlloc_Click(object sender, RoutedEventArgs e)
{
long before = GC.GetTotalMemory(true);
for (int i = 0; i < 10000; i++)
{
// allocating a new array in memory
byte[] myArray = new byte[1024];
// performing some work with the array
myArray[0] = (byte)i;
// array cleanup is left to be handled by garbage collection
}
long after = GC.GetTotalMemory(false);
lblManualAllocResults.Content = $"Before: {before} bytes\nAfter: {after} bytes";
}
// Using Array Pool
// - WARNING: Care should be taken to avoid data leakage between iterations reusing shared portions of the array pool
// if you keep track of how many elements you modified, you can clear the relevant portion of the array before
// you return it to be on the safe side. -- Array.Clear(myArray, 0, bytesUsed) (note: bytesUsed isn't the same as myArray.Length)
private void btnArrayPool_Click(object sender, RoutedEventArgs e)
{
long before = GC.GetTotalMemory(true);
var pool = ArrayPool<byte>.Shared;
for (int i = 0; i < 10000; i++)
{
// renting an array of length 1024 from the pool
byte[] myArray = pool.Rent(1024);
// performing some work with the array
myArray[0] = (byte)i;
// cleaning up the used portion of the array before returning it to the pool
Array.Clear(myArray, 0, 1);
// returning the array to the pool when you're done with it
pool.Return(myArray);
}
long after = GC.GetTotalMemory(false);
lblArrayPoolResults.Content = $"Before: {before} bytes\nAfter: {after} bytes";
}
// Using Boxing to create an object pointer on the stack that points to an object containing your int value on the Heap
// Garbage collection cares about the Heap, so this is expensive and puts pressure on the Garbage Collector -
// especially in loops or hot paths
private void btnWithBoxing_Click(object sender, RoutedEventArgs e)
{
long before = GC.GetTotalMemory(true);
object sum = 0;
for (int i = 0; i < 10000; i++)
{
sum = (int)sum + i;
}
long after = GC.GetTotalMemory(false);
lblWithBoxingResults.Content = $"Before: {before} bytes\nAfter: {after} bytes";
}
// Unboxed int values created purely on the stack - No Heap allocation - no Garbage Collection involved.
private void btnWithoutBoxing_Click(object sender, RoutedEventArgs e)
{
long before = GC.GetTotalMemory(true);
int sum = 0;
for (int i = 0; i < 10000; i++)
{
sum += i;
}
long after = GC.GetTotalMemory(false);
lblWithoutBoxingResults.Content = $"Before: {before} bytes\nAfter: {after} bytes";
}
// Large memory allocations (greater than 85,000 bytes) get placed on the Large Object Heap.
// this is far more expensive to Garbage Collection and should be avoided if possible
private void btnLargeAllocations_Click(object sender, RoutedEventArgs e)
{
long before = GC.GetTotalMemory(true);
for (int i = 0; i < 100; i++)
{
byte[] large = new byte[100_000];
large[0] = (byte)i;
}
long after = GC.GetTotalMemory(false);
lblLargeAllocResults.Content = $"Before: {before} bytes\nAfter: {after} bytes";
}
// reusing a Shared Array
// - prevents growth in the Large Object Heap
// - prevents unnecessary Gen 2 collections
// - WARNING: Care should be taken to avoid data leakage between iterations using the same array
// - Also, static variables in a multi-threaded service would use the same variable across
// all threads. So, this may not be an option in your scenario.
private static byte[] sharedArray = new byte[100_000];
private void btnReuseLargeBuffer_Click(object sender, RoutedEventArgs e)
{
long before = GC.GetTotalMemory(true);
for (int i = 0; i < 100; i++)
{
sharedArray[i] = (byte)i;
}
long after = GC.GetTotalMemory(false);
lblReuseLargeBufferResults.Content = $"Before: {before} bytes\nAfter: {after} bytes";
}
// Dispose examples:
// Note: "Without Dispose" and "With Dispose" examples likely won't show a noticeable difference in memory usage
// However, in "Without Dispose" the finalizers are run by Garbage Collection when we call GC.Collect()
// This would happen automatically in the context of long-running services as the system determines it needs to free up space.
// Finalizers delay cleanup and cause the object to live longer, often surviving Gen 1 or Gen 2 cleanups.
// View FinalizableResource.cs for additional comments.
private void btnWithoutDispose_Click(object sender, RoutedEventArgs e)
{
Trace.WriteLine("Note: the objects below have their Finalizers run by Garbage Collection:");
for (int i = 0; i < 10; i++)
{
var res = new FinalizableResource();
// No Dispose Called
}
// This is just for example purposes, don't call the below methods in your services unless you know for a fact you need to
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
lblWithoutDisposeResults.Content = $"Check Output Window\nFinalizers called by GC.";
}
// Note: Dispose containing GC.SuppressFinalize(this) tells the Garbage Collector it doesn't need to do anything
// This helps avoid unnecessary Garbage Collector work, and can help reduce latency spikes from Gen 2 collections.
private void btnWithDispose_Click(object sender, RoutedEventArgs e)
{
Trace.WriteLine("Note: the objects below are disposed as soon as we're done using them:");
for (int i = 0; i < 10; i++)
{
using var res = new FinalizableResource();
// Dispose is called automatically
// Earlier versions of .Net may require syntax in curly braces like below:
// using (var res = new FinalizableResource())
// {
// // do some work
// }
}
// This is just for example purposes, don't call the below methods in your services unless you know for a fact you need to
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
lblWithDisposeResults.Content = "Check Output Window\nDispose called - not GC.";
}
// String Concatenation
// On each iteration of the loop below, we create a new string result on the heap because strings are immutable
// the thrown away result variable is abandoned to be cleaned up by Garbage Collection.
// if we do this in a loop like below, you've created 1000+ temporary strings and only kept the last one.
// this can be very taxing on the Garbage Collector.
private void btnStringConcat_Click(object sender, RoutedEventArgs e)
{
long before = GC.GetTotalMemory(true);
string result = "";
for (int i = 0; i < 10000; i++)
{
result += i.ToString();
}
long after = GC.GetTotalMemory(false);
lblStringConcatResults.Content = $"Before: {before} bytes\nAfter: {after} bytes";
}
// StringBuilder
// StringBuilder maintains a mutable internal char array to build the string
// it resizes the array efficiently as needed.
// only one final string is created when you call .ToString()
// Helps redue Garbage Collection Frequency and pressure in long-running applications.
private void btnStringBuilder_Click(object sender, RoutedEventArgs e)
{
long before = GC.GetTotalMemory(true);
var sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.Append(i);
}
string result = sb.ToString();
long after = GC.GetTotalMemory(false);
lblStringBuilderResults.Content = $"Before: {before} bytes\nAfter: {after} bytes";
}
// What about String interpolation?
// There's no button for this on the main app, but this is worth bringing up
private void StringInterpolationExample()
{
string result = "";
int someValue = 15;
// this is usually fine and treated the same as String.Format or optimized concatenation.
// also, generally efficient for small, infrequent operations
result = $"The value of someValue is {someValue}";
// However, the below loop is just as bad as the String Concatenation example
// we still create a new result string on every iteration, so this is still a problem for Garbage Collection.
for (int i = 0; i < 1000; i++)
{
result = $"{result}The value of someValue is {someValue}\n";
}
// basically, String Interpolation is fine for one-off scenarios setting a string variable or logging,
// but if you continually overwrite a string variable with a new value, you'll add strain on the Garbage Collector
}
}
}