What are the dangers when creating a thread with a stack size of 50x the default?
Upon comparing test code with Sam, I determined that we are both right!
However, about different things:
- Accessing memory (reading and writing) is just as fast wherever it is - stack, global or heap.
- Allocating it, however, is fastest on stack and slowest on heap.
It goes like this: stack
< global
< heap
. (allocation time)
Technically, stack allocation isn't really an allocation, the runtime just makes sure a part of the stack (frame?) is reserved for the array.
I strongly advise being careful with this, though.
I recommend the following:
- When you need to create arrays frequently which never leave the function (e.g. by passing its reference), using the stack will be an enormous improvement.
- If you can recycle an array, do so whenever you can! The heap is the best place for long-term object storage. (polluting global memory isn't nice; stack frames can disappear)
(Note: 1. only applies to value types; reference types will be allocated on the heap and the benefit will be reduced to 0)
To answer the question itself: I have not encountered any problem at all with any large-stack test.
I believe the only possible problems are a stack overflow, if you are not careful with your function calls and running out of memory when creating your thread(s) if the system is running low.
The section below is my initial answer. It is wrong-ish and the tests aren't correct. It is kept only for reference.
My test indicates the stack-allocated memory and global memory is at least 15% slower than (takes 120% the time of) heap-allocated memory for usage in arrays!
This is my test code, and this is a sample output:
Stack-allocated array time: 00:00:00.2224429
Globally-allocated array time: 00:00:00.2206767
Heap-allocated array time: 00:00:00.1842670
------------------------------------------
Fastest: Heap.
| S | G | H |
--+---------+---------+---------+
S | - | 100.80 %| 120.72 %|
--+---------+---------+---------+
G | 99.21 %| - | 119.76 %|
--+---------+---------+---------+
H | 82.84 %| 83.50 %| - |
--+---------+---------+---------+
Rates are calculated by dividing the row's value to the column's.
I tested on Windows 8.1 Pro (with Update 1), using an i7 4700 MQ, under .NET 4.5.1
I tested both with x86 and x64 and the results are identical.
Edit: I increased the stack size of all threads 201 MB, the sample size to 50 million and decreased iterations to 5.
The results are the same as above:
Stack-allocated array time: 00:00:00.4504903
Globally-allocated array time: 00:00:00.4020328
Heap-allocated array time: 00:00:00.3439016
------------------------------------------
Fastest: Heap.
| S | G | H |
--+---------+---------+---------+
S | - | 112.05 %| 130.99 %|
--+---------+---------+---------+
G | 89.24 %| - | 116.90 %|
--+---------+---------+---------+
H | 76.34 %| 85.54 %| - |
--+---------+---------+---------+
Rates are calculated by dividing the row's value to the column's.
Though, it seems the stack is actually getting slower.
I would have a reservation there that I simply wouldn't know how to predict it - permissions, GC (which needs to scan the stack), etc - all could be impacted. I would be very tempted to use unmanaged memory instead:
var ptr = Marshal.AllocHGlobal(sizeBytes);
try
{
float* x = (float*)ptr;
DoWork(x);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
I've discovered a 530% increase in processing speed!
That's by far the biggest danger I'd say. There's something seriously wrong with your benchmark, code that behaves this unpredictably usually has a nasty bug hidden somewhere.
It is very, very difficult to consume a lot of stack space in a .NET program, other than by excessive recursion. The size of the stack frame of managed methods are set in stone. Simply the sum of the arguments of the method and the local variables in a method. Minus the ones that can be stored in a CPU register, you can ignore that since there are so few of them.
Increasing the stack size doesn't accomplish anything, you'll just reserve a bunch of address space that will never be used. There is no mechanism that can explain a perf increase from not using memory of course.
This is unlike a native program, particularly one written in C, it can also reserve space for arrays on the stack frame. The basic malware attack vector behind stack buffer overflows. Possible in C# as well, you'd have to use the stackalloc
keyword. If you are doing that then the obvious danger is having to write unsafe code that is subject to such attacks, as well as random stack frame corruption. Very hard to diagnose bugs. There is a counter-measure against this in later jitters, I think starting at .NET 4.0, where the jitter generates code to put a "cookie" on the stack frame and checks if it is still intact when the method returns. Instant crash to the desktop without any way to intercept or report the mishap if that happens. That's ... dangerous to the user's mental state.
The main thread of your program, the one started by the operating system, will have a 1 MB stack by default, 4 MB when you compile your program targeting x64. Increasing that requires running Editbin.exe with the /STACK option in a post build event. You can typically ask for up to 500 MB before your program will have trouble getting started when running in 32-bit mode. Threads can too, much easier of course, the danger zone typically hovers around 90 MB for a 32-bit program. Triggered when your program has been running for a long time and address space got fragmented from previous allocations. Total address space usage must already be high, over a gig, to get this failure mode.
Triple-check your code, there's something very wrong. You can't get a x5 speedup with a bigger stack unless you explicitly write your code to take advantage of it. Which always requires unsafe code. Using pointers in C# always has a knack for creating faster code, it isn't subjected to the array bounds checks.
One thing that can go wrong is that you might not get the permission to do so. Unless running in full-trust mode, the Framework will just ignore the request for a larger stack size (see MSDN on Thread Constructor (ParameterizedThreadStart, Int32)
)
Instead of increasing the system stack size to such huge numbers, I would suggest to rewrite your code so that it uses Iteration and a manual stack implementation on the heap.