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

Conversation

@benaadams
Copy link
Member

@benaadams benaadams commented Dec 2, 2018

Accessing Thread.CurrentThread via a [ThreadStatic] is 26% faster than via the InternalCall GetCurrentThreadNative; so cache GetCurrentThreadNative in a [ThreadStatic]

BenchmarkDotNet=v0.11.3, OS=Windows 10.0.18290
Intel Core i7-4720HQ CPU 2.60GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.0.100-preview-009812
  [Host]     : .NET Core 3.0.0-preview-27122-01 (CoreCLR 4.6.27121.03, CoreFX 4.7.18.57103), 64bit RyuJIT
  DefaultJob : .NET Core 3.0.0-preview-27122-01 (CoreCLR 4.6.27121.03, CoreFX 4.7.18.57103), 64bit RyuJIT


                     Method |     Mean |     Error |    StdDev | Ratio | RatioSD |
--------------------------- |---------:|----------:|----------:|------:|--------:|
       Thread_CurrentThread | 5.244 ns | 0.0509 ns | 0.0476 ns |  1.00 |    0.00 |
 ThreadStatic_CurrentThread | 4.168 ns | 0.0832 ns | 0.0778 ns |  0.79 |    0.02 |
public class Program
{
    static void Main(string[] args) => BenchmarkRunner.Run<Program>();

    [Benchmark(Baseline = true)]
    public static Thread Thread_CurrentThread() => Thread.CurrentThread;

    [Benchmark]
    public static Thread ThreadStatic_CurrentThread() => CurrentThread;

    [ThreadStatic]
    private static Thread s_currentThread;

    private static Thread CurrentThread => s_currentThread ?? InitaliseCurrentThread();

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static Thread InitaliseCurrentThread() => (s_currentThread = Thread.CurrentThread);
}

As its used very commonly (ExecutionContext.Capture() and ExecutionContext.Run(...) being the main examples for my usage)

@benaadams
Copy link
Member Author

The thread is presumably the root of the ThreadStatics so might be a different approach that would be faster? (e.g. is it something else in the native call; as I assume ThreadStatic access is also a native call?)

@jkotas
Copy link
Member

jkotas commented Dec 2, 2018

The thread is presumably the root of the ThreadStatics

The System.Threading.Thread object is not the root of the ThreadStatics.

might be a different approach that would be faster

It is probably possible because of the access to thread statics is not as optimized as it can be in JITed code. However, I would rather be spending energy on optimizing thread statics in general than on trying to tweak the GetCurrentThread FCall to be faster.

internal static CultureInfo m_CurrentCulture;
[ThreadStatic]
internal static CultureInfo m_CurrentUICulture;
[ThreadStatic]
Copy link
Member

Choose a reason for hiding this comment

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

Nit: The prefix for thread statics should t_.

System.Threading.Thread in CoreLib is left-over from full framework. It may be nice to do this in RuntimeThread.cs and get rid of Thread.CurrentThread in this file. (The FCall can still stay in this file.)

Copy link
Member Author

Choose a reason for hiding this comment

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

It may be nice to do this in RuntimeThread.cs and get rid of Thread.CurrentThread in this file.

'Thread' is less accessible than 'RuntimeThread' but if it returned 'RuntimeThread' instead then that returns other errors so more properties would need to be moved up e.g.

'RuntimeThread' does not contain a definition for 'ExecutionContext'

Copy link
Member

Choose a reason for hiding this comment

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

What file is that? Most places should be covered by using Thread = Internal.Runtime.Augments.RuntimeThread; already.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ahh, I can add it then

Copy link
Member Author

Choose a reason for hiding this comment

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

Issue is with ThreadPool.cs and ExecutionContext.cs which are in the System.Threading namespace.

Moving using Thread = Internal.Runtime.Augments.RuntimeThread; inside the namespace declaration gives Namespace 'System.Threading' contains a definition conflicting with alias 'Thread'

Copy link
Member Author

Choose a reason for hiding this comment

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

They are also places that use properties that are on Thread but not on RuntimeThread

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed to t_ anyway 😄

Copy link
Member

Choose a reason for hiding this comment

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

ok. It see that it is complicated.

@benaadams
Copy link
Member Author

I tried to increase the iterations and use volatile to stop hoisting to get a better differential

public class Program
{
    const int Iters = 1000;

    public static bool Thread_CurrentThread()
    {
        var ret = false;
        for (var i = 0; i < Iters; i++)
        {
            ret ^= (Thread.CurrentThread == s_currentThread);
        }

        return ret;
    }

    public static bool ThreadStatic_CurrentThread()
    {
        var ret = false;
        for (var i = 0; i < Iters; i++)
        {
            ret ^= (CurrentThread == s_currentThread);
        }

        return ret;
    }

    [ThreadStatic]
    private static Thread t_currentThread;

    private volatile static Thread s_currentThread;

    private static Thread CurrentThread => t_currentThread ?? InitaliseCurrentThread();

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static Thread InitaliseCurrentThread() => (t_currentThread = Thread.CurrentThread);
}

But that gets so fast for the ThreadStatic I'm assuming its still loop hoisting it 😄

                     Method |      Mean |     Error |    StdDev | Ratio |
--------------------------- |----------:|----------:|----------:|------:|
       Thread_CurrentThread | 6.3077 ns | 0.0139 ns | 0.0123 ns |  1.00 |
 ThreadStatic_CurrentThread | 0.8681 ns | 0.0026 ns | 0.0024 ns |  0.14 |

@benaadams
Copy link
Member Author

Not for this PR, but could also do something similar with IsThreadPoolThread?

private bool? _isThreadPoolThread;

public bool IsThreadPoolThread => _isThreadPoolThread ?? InitializeIsThreadPoolThread();

[MethodImpl(MethodImplOptions.NoInlining)]
private bool InitializeIsThreadPoolThread()
{
    return (_isThreadPoolThread = IsThreadPoolThreadNative());
}

However the InternalCall is IsThreadPoolThread so would need renaming in the C++

@jkotas
Copy link
Member

jkotas commented Dec 3, 2018

could also do something similar with IsThreadPoolThread?

The thread flags should be cached eagerly when the thread is created, so that the flag can be checked without worrying whether it has been initialized. CoreRT has good prior art for this:
https://github.com/dotnet/corert/blob/5fa3a9f19898199cd5ed7cf83ca3690b3c3d607f/src/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeThread.cs#L220

@jkotas jkotas merged commit 5a7f3c6 into dotnet:master Dec 3, 2018
@benaadams benaadams deleted the CurrentThread branch December 3, 2018 06:55
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants