Bug 40359 - Mono Threadpool adds threads 10x faster than .NET
Summary: Mono Threadpool adds threads 10x faster than .NET
Status: RESOLVED ANSWERED
Alias: None
Product: Runtime
Classification: Mono
Component: General ()
Version: 4.2.0 (C6)
Hardware: PC Linux
: --- normal
Target Milestone: ---
Assignee: Ludovic Henry
URL:
Depends on:
Blocks:
 
Reported: 2016-04-13 19:35 UTC by Matt Z
Modified: 2016-04-14 06:17 UTC (History)
5 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
Test program (1.04 KB, text/plain)
2016-04-13 19:35 UTC, Matt Z
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on GitHub or Developer Community with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED ANSWERED

Description Matt Z 2016-04-13 19:35:51 UTC
Created attachment 15706 [details]
Test program

After the threadpool minimum number of worker threads have been created, Mono 4.2.3 seems to only wait for a thread to become free for 0.1 seconds before creating a new one. MS .NET seems to wait up to 1.0 seconds (10x). This means that Mono creates 10x more threads for a given workload than .NET when task completion is greater than 1 second.

The attached program schedules 300 tasks, and then prints out the number of available threadpool threads every second until they are all exhausted. I hardcoded the threadpool min/max workers to 10/40. 

On .NET this takes roughly 30 seconds as expected:

Available: 30, Threads: 25 Private: 19.055 MB, Working: 22.363 MB
Available: 29, Threads: 26 Private: 19.457 MB, Working: 23.035 MB
Available: 28, Threads: 27 Private: 19.914 MB, Working: 23.434 MB
Available: 27, Threads: 28 Private: 20.367 MB, Working: 23.828 MB
Available: 26, Threads: 29 Private: 20.832 MB, Working: 24.234 MB
Available: 25, Threads: 30 Private: 21.262 MB, Working: 24.664 MB
...
Available: 4, Threads: 51 Private: 24.168 MB, Working: 26.852 MB
Available: 3, Threads: 52 Private: 24.242 MB, Working: 26.887 MB
Available: 2, Threads: 53 Private: 24.320 MB, Working: 26.922 MB
Available: 1, Threads: 54 Private: 24.398 MB, Working: 26.969 MB
Available: 0, Threads: 55 Private: 24.473 MB, Working: 27.008 MB

On Mono 4.2.3 this completes in only a 5 seconds:

Available: 38, Threads: 6 Private: 295.496 MB, Working: 15.973 MB
Available: 29, Threads: 15 Private: 889.672 MB, Working: 20.176 MB
Available: 19, Threads: 25 Private: 1,037.871 MB, Working: 20.363 MB
Available: 9, Threads: 35 Private: 1,058.070 MB, Working: 20.531 MB
Available: 0, Threads: 44 Private: 1,076.246 MB, Working: 22.664 MB

On Mono 4.4.0 TOT this better, but still adds threads 2x faster than .NET:

Available: 38, Threads: 6 Private: 295.621 MB, Working: 18.398 MB
Available: 36, Threads: 8 Private: 427.660 MB, Working: 18.434 MB
Available: 34, Threads: 10 Private: 559.703 MB, Working: 18.477 MB
Available: 32, Threads: 12 Private: 691.742 MB, Working: 18.520 MB
Available: 30, Threads: 14 Private: 823.781 MB, Working: 18.551 MB
Available: 28, Threads: 16 Private: 955.820 MB, Working: 20.590 MB
Available: 26, Threads: 18 Private: 1,023.844 MB, Working: 20.613 MB
Available: 25, Threads: 19 Private: 1,025.879 MB, Working: 20.625 MB
Available: 23, Threads: 21 Private: 1,029.918 MB, Working: 20.672 MB
Available: 21, Threads: 23 Private: 1,033.957 MB, Working: 20.699 MB
Available: 19, Threads: 25 Private: 1,037.996 MB, Working: 20.723 MB
Available: 17, Threads: 27 Private: 1,042.039 MB, Working: 20.754 MB
Available: 15, Threads: 29 Private: 1,046.078 MB, Working: 20.781 MB
Available: 13, Threads: 31 Private: 1,050.117 MB, Working: 20.801 MB
Available: 11, Threads: 33 Private: 1,054.156 MB, Working: 20.832 MB
Available: 9, Threads: 35 Private: 1,058.195 MB, Working: 20.855 MB
Available: 7, Threads: 37 Private: 1,062.234 MB, Working: 22.871 MB
Available: 5, Threads: 39 Private: 1,066.273 MB, Working: 22.898 MB
Available: 3, Threads: 41 Private: 1,070.313 MB, Working: 22.926 MB
Available: 1, Threads: 43 Private: 1,074.352 MB, Working: 22.953 MB
Available: 0, Threads: 44 Private: 1,076.371 MB, Working: 22.969 MB

This is causing our service to be killed for OOM, since each thread requires 2MB of stack space on Linux.
Comment 1 Rodrigo Kumpera 2016-04-14 06:17:41 UTC
If you are running out of memory then limit the number of threads or heap size.

The thread ramp up heuristics are different and you must not expect them to be identical. Even dotnet can change across releases and break you program.