Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Allow stack size to be configured for native AOT #110455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

keegan-caruso
Copy link
Contributor

If a native AOT compiled application can lower its allocated memory, more scenarios that are memory sensitive can use it.

The default stack size can be excessive for simple applications and can significantly contribute to the RSS.

For linux, there is the ulimit command, and this is respected, but it can be too broad.

With this trivial code:

int s = 0;

await Task.Run(() => Interlocked.Increment(ref s));

Console.ReadLine();

On linux with the default stack size of 8mb, RSS measured at 5916.

With setting the stack size to 1mb, the RSS measured at 3428.

Roughly a 40% decrease.

@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Dec 5, 2024
@jkotas
Copy link
Member

jkotas commented Dec 6, 2024

We have (undocumented) IlcDefaultStackSize build property to set the default stack size in Native AOT binaries. Would it be sufficient for what you are trying to do?

Comment on lines 500 to 501
const string stackSizeVar = "DefaultStackSize";
const string stackSizeEnvVar = "DOTNET_DefaultStackSize";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shortcut (making the AppContext switch name same as the environment variable name) makes things inconsistent with how we normally name AppContext switches. See e.g. documented GC switches where DOTNET_gcServer environment variable is a synonym for System.GC.Server host config option (i.e. these are not prefixes and AppContext switches adhere to a naming convention).

It is a bit more work to do this correctly. Sample how it's done on the managed side (also notice the naming convention):

int timeoutMs =
AppContextConfigHelper.GetInt32Config(
"System.Threading.ThreadPool.ThreadTimeoutMs",
"DOTNET_ThreadPool_ThreadTimeoutMs",
DefaultThreadPoolThreadTimeoutMs);

On the native side we do this (it's what the GC uses to read both env variable settings and host config options):

uint64_t uiValue;
if (g_pRhConfig->ReadConfigValue(privateKey, &uiValue))
{
*value = uiValue;
return true;
}
if (publicKey)
{
if (g_pRhConfig->ReadKnobUInt64Value(publicKey, &uiValue))
{
*value = uiValue;
return true;
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it would be nice to keep the configuration capabilities between NAOT and regular CoreCLR the same - make the configuration switch work the same way for both NAOT and CoreCLR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MichalStrehovsky @jkotas

I believe this feedback is addressed. Are there any other changes you would like to see?

This change would help one of the teams that I support.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this feedback is addressed. Are there any other changes you would like to see?

I don't see this comment addressed:

  1. The switch name should be something starting with System.Threading.Thread. Maybe System.Threading.Thread.DefaultStackSize? This is a naming convention respected across the entire framework.
  2. If I'm reading it right, PalStartBackgroundWork will look at the environment variable, but will not look at RuntimeHostConfigurationOption (the environment variable settings and AppContext/RuntimeHostConfiguration settings are from disjoint namespaces). Is that expected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the confusion, I pushed 2975db0 awhile ago, but Github wasn't showing the commit in this diff. I followed jkotas's advice, rebasing and force pushed fixed the state.

@keegan-caruso
Copy link
Contributor Author

We have (undocumented) IlcDefaultStackSize build property to set the default stack size in Native AOT binaries. Would it be sufficient for what you are trying to do?

Thanks. This works on Windows, it is not working for me on Linux.

On windows I checked against kernel32 GetCurrentThreadStackLimits. On linux I checked against strace.

@jkotas
Copy link
Member

jkotas commented Dec 8, 2024

Thanks. This works on Windows, it is not working for me on Linux.

It works for musl libc (musl libc is used e.g. by Alpine Linux). glibc that is used by the most popular Linux distros like Ubuntu does not respect value set by -z,stack-size linker switch for some reason. That's unfortunate.

@jkotas
Copy link
Member

jkotas commented Feb 10, 2025

Github status is stuck in "Checking for ability to merge automatically…".

Could you please rebase against main and force push? Hopefully, it is going to reset the status.


private static int StackSizeFromConfig
{
get => _stackSizeFromConfig ??= GetDefaultStackSize();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern with Nullable<int> is not thread safe. Nullable is a struct with two fields. This pattern has a race condition caused by tearing.

I do not think you need to cache the value on the managed side. The value is cached on the unmanaged side. You can call every time.

// the AppContext class.
// To have similar behavior as RhConfig, also read from environment variables, which have priority.

string? valueFromConfig = Environment.GetEnvironmentVariable(stackSizeEnvVar) ?? AppContext.GetData(stackSizeVar)?.ToString();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can use AppContextHelper.GetInt32ComPlusOrDotNetConfig so that we only have one copy of the policy around parsing these.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, this can call into the native runtime to reuse the value parsed there.

@MichalStrehovsky
Copy link
Member

I think we should look at this in the context of #113379:

Here's a couple questions I have:

  • Are we happy with the name of the runtimeconfig.json name of this and DOTNET_ environment variable? (They look good to me.)
  • Are we happy with the radix? (I assume we want to keep it as base-16.)

If so, I can add the CoreCLR version so that we can also resolve #113379 and we can doc it.

Cc @kouvel @AaronRobinsonMSFT for opinion

@am11
Copy link
Member

am11 commented Mar 13, 2025

The naming looks good as it's consistent with:

src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs:            AppContextConfigHelper.GetInt16ComPlusOrDotNetConfig("System.Threading.ThreadPool.MinThreads", "ThreadPool_ForceMinWorkerThreads", 0, false);
src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.cs:            AppContextConfigHelper.GetInt16ComPlusOrDotNetConfig("System.Threading.ThreadPool.MaxThreads", "ThreadPool_ForceMaxWorkerThreads", 0, false);
src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Unix.cs:            AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.EnableWorkerTracking", "DOTNET_ThreadPool_EnableWorkerTracking");
src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.Windows.cs:            AppContextConfigHelper.GetBooleanConfig("System.Threading.ThreadPool.UseWindowsThreadPool", "DOTNET_ThreadPool_UseWindowsThreadPool");

@AaronRobinsonMSFT
Copy link
Member

@MichalStrehovsky See #113344 also.

Are we happy with the name of the runtimeconfig.json name of this and DOTNET_ environment variable? (They look good to me.)

I didn't see the DOTNET_ environment variable. Can you point me to it?

Are we happy with the radix? (I assume we want to keep it as base-16.)

This was a source of confusion on a partner team who had been assuming it was radix 10 for about 6 years. I think having it as radix 16 is fine, but we need to document this properly at https://learn.microsoft.com/dotnet/core/runtime-config/threading.

@AaronRobinsonMSFT
Copy link
Member

DOTNET_DefaultStackSize

Seems reasonable to me.

@am11
Copy link
Member

am11 commented Mar 13, 2025

I didn't see the DOTNET_ environment variable. Can you point me to it?

It's DOTNET_Threading_DefaultStackSize. Prefix is added later. It's consistent with existing convention DOTNET_ThreadPool_UseWindowsThreadPool, DOTNET_ThreadPool_EnableWorkerTracking etc.

@kouvel
Copy link
Member

kouvel commented Mar 20, 2025

It's DOTNET_Threading_DefaultStackSize. Prefix is added later. It's consistent with existing convention DOTNET_ThreadPool_UseWindowsThreadPool, DOTNET_ThreadPool_EnableWorkerTracking etc.

CoreCLR already has a DOTNET_DefaultStackSize config setting. There are other thread-specific settings with DOTNET_Thread_*. Should we use the DOTNET_Thread_ prefix for consistency and add the new one to CoreCLR as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-NativeAOT-coreclr community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants