Skip to content
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

Allow stack size to be configured for native AOT #110455

Open
wants to merge 3 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.

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.

4 participants