“低延迟关键”线程是否应使用线程亲和力?

4
在我的高频交易应用程序中,我有几个地方需要从网络接收数据。大多数情况下,这只是一个仅接收和处理数据的线程。以下是此类处理的一部分:
    public Reciver(IPAddress mcastGroup, int mcastPort, IPAddress ipSource)
    {

        thread = new Thread(ReceiveData);

        s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        s.ReceiveBufferSize = ReceiveBufferSize;

        var ipPort = new IPEndPoint(LISTEN_INTERFACE/* IPAddress.Any*/, mcastPort);
        s.Bind(ipPort);

        option = new byte[12];
        Buffer.BlockCopy(mcastGroup.GetAddressBytes(), 0, option, 0, 4);
        Buffer.BlockCopy(ipSource.GetAddressBytes(), 0, option, 4, 4);
        Buffer.BlockCopy(/*IPAddress.Any.GetAddressBytes()*/LISTEN_INTERFACE.GetAddressBytes(), 0, option, 8, 4);
    }

    public void ReceiveData()
    {
        byte[] byteIn = new byte[4096];
        while (needReceive)
        {
            if (IsConnected)
            {
                int count = 0;
                try
                {
                    count = s.Receive(byteIn);
                }
                catch (Exception e6)
                {
                    Console.WriteLine(e6.Message);
                    Log.Push(LogItemType.Error, e6.Message);
                    return;
                }
                if (count > 0)
                {
                    OnNewMessage(new NewMessageEventArgs(byteIn, count));
                }
            }
        }
    }

创建后,此线程将永久工作。我想知道是否应该配置此线程以在某个核心上运行?由于需要最低的延迟,我希望避免上下文切换。为了避免上下文切换,我最好在相同的处理器核心上运行相同的线程,对吗?

考虑到我需要最低延迟,那么:

  • 为大部分“长时间运行”的线程设置“线程亲和性”是否更好?
  • 为上述示例中的线程设置“线程亲和性”是否更好?

我现在正在将上述代码重写为C++,以后再移植到Linux,不过我认为我的问题更多与硬件有关,而不是语言或操作系统。


1
上下文切换是指在线程之间进行切换。这不受线程亲和性的影响。操作系统仍然需要调度这些和其他线程。 - Zdeslav Vojkovic
无论你使用什么,延迟都无法得到保证,因为许多线程在不同的核心上并行执行,当你想要调度其他线程时,操作系统需要执行一些任务,这些任务不是固定时间完成的。 - Narendra Pathai
程序不应该完全依赖于固定的延迟时间,这样的程序容易出现故障。 - Narendra Pathai
我不需要保证的延迟,我需要尽量减少延迟。这是交易。延迟越低 - 我能赚更多的钱。 - Oleg Vazhnev
这么说吧,已经有几篇关于亲和力修补的帖子,都是为了尝试提高延迟/性能。据我所知,至今还没有人发表任何跟进内容说明他们的应用程序性能有任何改善。 - Martin James
不要这样做。在测试中可能会更好,但它会出问题。在这里找到原因:http://blogs.microsoft.co.il/blogs/sasha/archive/2008/04/20/parallelism-and-cpu-affinity.aspx - Adriano Repetti
3个回答

2

我认为最小化延迟的算法是将线程固定到一个核心,并将它们设置为实时优先级(或者任何最高优先级)。

这将导致操作系统驱逐使用该核心的任何其他线程。

希望当您的线程被调度到该核心时,CPU缓存仍然包含有用数据。因此,我喜欢将线程固定到一个核心的想法。

您应该将整个进程设置为高优先级类,并最小化计算机上的其他活动。还要关闭未使用的硬件,因为它可能会生成中断。将您NIC的中断修复到不同的CPU核心(一些更好的NIC可以做到这一点)。


在Windows上以实时优先级运行某些东西并不一定是一个好主意,如果进程消耗了所有的CPU资源,那么它将使得控制机器变得不可能。只是说一下。 - Tony The Lion
1
@TonyTheLion:电源循环饲料。 - Martin James
为纳秒级的延迟而战似乎有些奇怪,因为GC线程可以随时中断执行 :) - Zdeslav Vojkovic
@ZdeslavVojkovic - 此外,“驱逐”正在另一个核心上运行的线程是混乱/冗长的 - 这意味着硬件中断另一个核心。 - Martin James
我并不确定其他设计更改不会带来更大的影响,而没有太大的副作用。例如,使用缓冲池而不是new(),使用IOCP避免数据复制和过多的内核转换,可能还有更多的东西。此外,重写为C或C++,以避免垃圾回收。 - Martin James
@MartinJames 我同意你的所有观点。尽管如此,优化调度仍然是一个好主意,因为像这种情况一样,“把调度交给操作系统”的老话在实际中根本不切实际。在实践中,调度远非最优解。将线程钉在核心上是一种经过充分验证的技术,适用于非常低延迟或高吞吐量的特殊应用程序。 - usr

1

我希望避免上下文切换,所以最好在同一处理器核心上运行相同的线程,对吗?

不是这样的。将亲和性设置为一个CPU并不能保证避免上下文切换。您无法控制上下文切换,它们在操作系统线程调度程序的掌握下。当线程时间片(时间切片)已过或更高优先级的线程中断您的线程时,它们会发生。

您提到的延迟,我假设是网络或内存延迟,并不完全通过设置线程亲和力来避免。通过使您的代码缓存友好(例如,可以全部在L1-L2缓存中),可以避免内存延迟。网络延迟实际上只是任何网络的一部分,我认为您可能无法做太多关于它的事情。


1
是的,所以+1。我假设s.Receive()会阻塞,所以没有必要使用任何亲和性bodges。 - Martin James
据我所知,@Adriano,可以强制Linux(非实时,常规)仅使用一个核心来运行一个线程,但是配置可能不是很容易,我现在无法准确告诉您如何操作。 - Oleg Vazhnev
1
@javapowered 到目前为止,它是 C# 和托管线程,可能甚至不匹配物理线程。此外,不仅软件,而且硬件(中断)也可能使用 您的 核心。我认为我们已经读了1000次、1000000次相同的答案。你需要实时(或非常严格的调度)吗?不要使用一个 普通的 操作系统。没有 "如果",也没有 "可以吗"。这总是一个妥协,如果你正在开发一个关键任务的系统,你不能接受它可能会失败。 - Adriano Repetti
@Adriano,我不在NASA工作,而是从事交易。我需要最小的平均延迟,有时失败也是可以接受的。 - Oleg Vazhnev
@Adriano,我更需要延迟而不是性能。是的,我测量延迟,但我不知道如何准确地测量将线程从一个核心传输到另一个核心的延迟。我甚至不知道如何捕捉这样的情况。因此,我决定在专用核心上运行线程,并检查整体性能是否会更好。 - Oleg Vazhnev
显示剩余10条评论

1
由于Tony The Lion已经回答了你的问题,我想回应你的评论:
“为什么不将线程亲和性设置为我的代码?为什么需要从我的示例中的线程在核之间移动?”
你的线程并没有移动到任何地方。
当操作系统线程调度器决定给你的线程一个时间片来执行时,就会发生上下文切换。然后为你的线程准备环境,例如将CPU寄存器设置为正确的值等。这就是所谓的上下文切换。
因此,无论线程亲和性如何,都必须进行相同的CPU设置工作,无论是在之前的时间片中使用的相同CPU/核心还是另一个。在这些时刻,你的计算机有更多的信息来正确地完成它,而你在编译时所知道的信息比较少。
你似乎认为线程某种程度上驻留在CPU上,但事实并非如此。你使用的是逻辑线程,可以有数百甚至数千个。而常见的CPU通常每个核心只有1或2个硬件线程,每次调度时将你的逻辑线程映射到其中之一,即使操作系统总是选择相同的硬件线程。

编辑:看起来你已经选择了想听到的答案,我不喜欢在答案上进行长时间的讨论,所以我会在这里放置。

  • 你应该尝试并测量它。我相信你会感到失望
  • 在高优先级线程上运行一些线程可能会轻松搞乱其他进程
  • 你担心上下文切换延迟,但你没有GC线程会冻结你的线程的问题吗?顺便问一下,你的GC线程将在哪个核心上运行? :)
  • 如果你的最高优先级线程阻塞了GC线程怎么办?内存泄漏?你知道那个线程的优先级,所以你确定它可以工作吗?
  • 真的,如果微秒很重要,为什么不使用C或手动优化的汇编语言?
  • 正如有人建议的那样,如果您想控制执行的这个方面,您应该使用RTOS
  • 似乎不太可能你的数据通过数据中心只比在一台机器上设置线程上下文慢4-5倍,但谁知道...

我的一个线程正在使用“忙等待”。但是没有CPU核心被加载到100%。只有当我为这个线程设置了“线程亲和性”时,我的核心才会被加载到100%。因此,线程在不同的核心之间传输。当然,这对延迟非常不利,因为这消除了缓存的概念。每个处理器都有自己的缓存,甚至每个核心都有一些本地缓存,据我所知。 - Oleg Vazhnev
当您的线程被中断时,CPU 在下一次调度该线程之前并不知道任何事情。您的线程并没有留在旧的 CPU 上。您所看到的是操作系统将您的线程 4 次调度到同一个核心,而不是 1 次调度到 4 个不同的核心,但每次调度时都会完成相同数量的工作。 CPU 负载的增加或减少与其他核心负载的增加或减少大致相同,并且时间也大致相同(撇开缓存问题不谈)。 - Zdeslav Vojkovic
那么线程亲和性是什么意思?你是说这是绝对无用的选项吗? - Oleg Vazhnev
不同意,网络延迟是恒定的,例如300微秒。因此,如果一个人的总延迟为350微秒,而另一个人的总延迟为355微秒,则第一个人将赚取所有的钱,而第二个人将一分钱也没有。 - Oleg Vazhnev
我已将评论移至此处并添加到我的答案中,以避免长时间的讨论。 - Zdeslav Vojkovic
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接