【问题标题】:should I use thread affinity for "latency-critical" threads?我应该对“延迟关键”线程使用线程亲和性吗?
【发布时间】:2013-03-08 13:48:45
【问题描述】:

在我的 HFT 交易应用程序中,我有几个地方可以从网络接收数据。在大多数情况下,这只是一个只接收和处理数据的线程。以下是此类处理的一部分:

    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,但是我认为我的问题更多是关于硬件而不是语言或操作系统。

【问题讨论】:

  • 上下文切换是指线程间的切换。这不受线程亲和性的影响。操作系统仍然需要调度这个线程和其他线程。
  • 无论您使用什么,延迟都无法得到保证,因为许多线程在不同的内核上并行执行,只要您希望安排其他线程,就会有一些琐事要执行操作系统方面,它不是一个固定时间的任务。
  • 程序不应该完全依赖于固定延迟。这样的程序很容易崩溃。
  • 我不需要保证延迟。我需要尽量减少延迟。这是交易。更低的延迟 - 我可以赚更多的钱。
  • 这样说吧。有几篇关于亲和力的博文以及如何尝试改善延迟/性能。 AFAIK,nobdy 尚未发布任何后续消息,称他们的应用程序性能已经有所提高。

标签: c# multithreading low-latency


【解决方案1】:

我认为具有尽可能低延迟的算法是将您的线程固定到一个核心并将它们设置为实时优先级(或任何最高优先级)。

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

希望当您的线程被安排在那里时,CPU 缓存仍将包含有用的数据。出于这个原因,我喜欢固定到核心的想法。

您可能应该将整个过程设置为高优先级,并尽量减少您盒子上的其他活动。还要关闭未使用的硬件,因为它可能会产生中断。将您的 NIC 中断修复到不同的 CPU 内核(一些更好的 NIC 可以做到这一点)。

【讨论】:

  • 在 Windows 上以实时优先级运行某些东西不一定是个好主意,如果进程消耗所有 CPU,它将无法控制机器。只是说。
  • @TonyTheLion:电源循环饲料。
  • 争取纳秒的延迟似乎很奇怪,因为 GC 线程可以随时中断执行:)
  • @ZdeslavVojkovic - 另外,“驱逐”在另一个核心上运行的线程是混乱/冗长的 - 意味着另一个核心的硬件中断。
  • 我当然不相信如果没有大的缺点,其他设计更改不会产生更大的影响。池化缓冲区而不是 new(),使用 IOCP 来避免数据复制和过多的内核转换,可能还有更多的东西。那个,然后用 C 或 C++ 重写,所以没有 GC。
【解决方案2】:

因为我想避免上下文切换,我最好在同一个处理器内核上运行同一个线程,对吧?

没有。将关联设置到一个 CPU 不一定可以避免上下文切换。您无法控制上下文切换,它们掌握在操作系统线程调度程序的手中。它们发生在线程量子(时间片)已经过去或更高优先级的线程中断您的线程时。

您所说的延迟,我假设是网络或内存延迟,根本无法通过设置线程亲和性来避免。内存延迟可以通过使您的代码缓存友好来避免(例如,它可以都在 L1 - L2 缓存中)。网络延迟实际上只是任何网络的一部分,我怀疑您对此无能为力。

【讨论】:

  • 是的,所以 +1。我认为 s.Receive() 块,所以任何亲和力都没有意义。
  • 为什么不对我的代码设置线程亲和性?为什么我的示例中的线程需要在内核之间传输?在同一个内核上运行不是更好吗?
  • @javapowered 为什么你如此迫切地想把它放在一个核心上?它对你有什么影响?除非你有强烈的理由这样做,否则我认为没有必要在系统上强制执行。
  • @javapowered 上下文切换和亲和力是不同的东西。无论如何,不​​,不是更好,因为您确实没有对该内核/cpu 的独占控制权。如果你将你的线程锁定到那个核心,它可能会在很久以后执行,因为 OS 也决定将它用于其他目的...
  • @javapowered 到目前为止它是 C#,托管线程甚至可能与物理线程不匹配。此外,不仅软件而且硬件(中断)也可以使用 your 内核。我认为我们阅读了相同的答案 1000、1000000 次。您需要实时(或非常严格的调度)吗?不要使用通用操作系统。没有“如果”,也没有“我可以”。这总是一种妥协,如果你正在开发一个你不能接受的关键任务系统,它可能会失败。
【解决方案3】:

Tony The Lion 已经回答了你的问题,我想谈谈你的评论:

“为什么不对我的代码设置线程亲和性?为什么我的示例中的线程需要在内核之间传输?”

您的线程不会传播到任何地方。

当操作系统线程调度器决定给你的线程一段执行时间时,上下文切换就会发生。然后为您的线程准备环境,例如CPU寄存器 设置为正确的值等。这称为上下文切换。

因此,无论线程亲和性如何,都必须完成相同的 CPU 设置工作,无论是线程运行时在前一个切片中使用的 CPU/内核还是另一个。此时,您的计算机拥有比编译时更多的信息来正确执行此操作。

您似乎相信线程以某种方式驻留在 CPU 上,但事实并非如此。您使用的是逻辑线程,可能有数百甚至数千个。普通 CPU OTOH 通常每个内核有 1 或 2 个硬件线程,并且您的逻辑线程在每次调度时都会映射到其中之一,即使操作系统总是选择相同的硬件线程。

编辑:看来你已经选择了你想听到的答案,我不喜欢关于答案的冗长讨论线程,所以我会把它放在这里。

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

【讨论】:

  • 我的线程使用“忙等待”。但是没有 CPU 内核加载到 100%。只有当我为此线程设置“线程亲和力”时,我的核心才加载到 100%。所以线程正在内核之间传输。当然,这对延迟非常不利,因为这消除了缓存的想法。每个处理器都有自己的缓存,甚至每个内核都有一些本地缓存。
  • 没有转移。当你的线程被中断时,CPU 直到下次调度线程时才知道它的任何信息。您的线程不会以某种方式留在旧 CPU 上。您看到的是,操作系统将您的线程 4 次调度到同一个核心,而不是 1 次调度到 4 个不同的核心,但每次调度它,它都会做相同数量的工作。 CPU负载的增加和其他核心负载的减少差不多,时间也差不多(暂且不说缓存问题)
  • 那么线程亲和力到底是什么?你是说这绝对是无用的选择吗?
  • 不同意,网络延迟是恒定的,比如 300 微秒。因此,如果一个人的总延迟为 350 微秒,而另一个人的总延迟为 355 微秒,那么第一个人将赚到所有钱,而第二个人将一无所获。
  • 网络延迟在交易所的托管数据中心几乎是恒定的,人们为此服务支付了很多钱。
猜你喜欢
  • 2017-12-06
  • 2021-05-02
  • 2011-01-15
  • 2012-11-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多