Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 36ae610

Browse files
gfoidlstephentoub
authored andcommitted
Stack<T> optimization of (Try)Peek, (Try)Pop and Push (#26086)
* Stack.Pop RCE With value types the effect is not so big, because there is still one (manual) check for bounds. For reference types one bounds-check can be saved, so there is a win. * Applied same optimizations as for Pop on Peek, TryPeek, TryPop, Push * Revert change for Push I can't see a real win, sometimes it's faster and sometimes slower. On Linux the tendency is to be slower. Therefore the state as is will be remained. * Stack.Push with hot-/cold-path (PushWithResize) * Addressed PR feedback Cf. #26086 (comment) * Array-copy in Peek is not necessary, JIT can do the same As of PR-feedback #26086 (comment) * Reverted b0bfd83 Cf. #26086 (comment) * Addressed PR feedback Cf. #26086 (comment)
1 parent fc7cd1b commit 36ae610

File tree

1 file changed

+49
-15
lines changed
  • src/System.Collections/src/System/Collections/Generic

1 file changed

+49
-15
lines changed

src/System.Collections/src/System/Collections/Generic/Stack.cs

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -202,69 +202,103 @@ public void TrimExcess()
202202
// is empty, Peek throws an InvalidOperationException.
203203
public T Peek()
204204
{
205-
if (_size == 0)
205+
int size = _size - 1;
206+
T[] array = _array;
207+
208+
if ((uint)size >= (uint)array.Length)
206209
{
207210
ThrowForEmptyStack();
208211
}
209212

210-
return _array[_size - 1];
213+
return array[size];
211214
}
212215

213216
public bool TryPeek(out T result)
214217
{
215-
if (_size == 0)
218+
int size = _size - 1;
219+
T[] array = _array;
220+
221+
if ((uint)size >= (uint)array.Length)
216222
{
217-
result = default(T);
223+
result = default;
218224
return false;
219225
}
220-
result = _array[_size - 1];
226+
result = array[size];
221227
return true;
222228
}
223229

224230
// Pops an item from the top of the stack. If the stack is empty, Pop
225231
// throws an InvalidOperationException.
226232
public T Pop()
227233
{
228-
if (_size == 0)
234+
int size = _size - 1;
235+
T[] array = _array;
236+
237+
// if (_size == 0) is equivalent to if (size == -1), and this case
238+
// is covered with (uint)size, thus allowing bounds check elimination
239+
// https://github.com/dotnet/coreclr/pull/9773
240+
if ((uint)size >= (uint)array.Length)
229241
{
230242
ThrowForEmptyStack();
231243
}
232244

233245
_version++;
234-
T item = _array[--_size];
246+
_size = size;
247+
T item = array[size];
235248
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
236249
{
237-
_array[_size] = default(T); // Free memory quicker.
250+
array[size] = default; // Free memory quicker.
238251
}
239252
return item;
240253
}
241254

242255
public bool TryPop(out T result)
243256
{
244-
if (_size == 0)
257+
int size = _size - 1;
258+
T[] array = _array;
259+
260+
if ((uint)size >= (uint)array.Length)
245261
{
246-
result = default(T);
262+
result = default;
247263
return false;
248264
}
249265

250266
_version++;
251-
result = _array[--_size];
267+
_size = size;
268+
result = array[size];
252269
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
253270
{
254-
_array[_size] = default(T); // Free memory quicker.
271+
array[size] = default; // Free memory quicker.
255272
}
256273
return true;
257274
}
258275

259276
// Pushes an item to the top of the stack.
260277
public void Push(T item)
261278
{
262-
if (_size == _array.Length)
279+
int size = _size;
280+
T[] array = _array;
281+
282+
if ((uint)size < (uint)array.Length)
263283
{
264-
Array.Resize(ref _array, (_array.Length == 0) ? DefaultCapacity : 2 * _array.Length);
284+
array[size] = item;
285+
_version++;
286+
_size = size + 1;
265287
}
266-
_array[_size++] = item;
288+
else
289+
{
290+
PushWithResize(item);
291+
}
292+
}
293+
294+
// Non-inline from Stack.Push to improve its code quality as uncommon path
295+
[MethodImpl(MethodImplOptions.NoInlining)]
296+
private void PushWithResize(T item)
297+
{
298+
Array.Resize(ref _array, (_array.Length == 0) ? DefaultCapacity : 2 * _array.Length);
299+
_array[_size] = item;
267300
_version++;
301+
_size++;
268302
}
269303

270304
// Copies the Stack to an array, in the same order Pop would return the items.

0 commit comments

Comments
 (0)