【问题标题】:should i try to avoid "new" keyword in ultra-low-latency software?我应该尽量避免在超低延迟软件中使用“new”关键字吗?
【发布时间】:2013-01-09 17:36:38
【问题描述】:

我正在编写高频交易软件。我确实关心每一微秒。现在它是用 C# 编写的,但我很快就会迁移到 C++。

让我们考虑这样的代码

// Original
class Foo {
....

    // method is called from one thread only so no need to be thread-safe
    public void FrequentlyCalledMethod() {
        var actions = new List<Action>();
        for (int i = 0; i < 10; i++) {
            actions.Add(new Action(....));
        }
        // use actions, synchronous
        executor.Execute(actions);
        // now actions can be deleted
    }

我觉得超低延迟的软件不应该过多的使用“new”关键字,所以我把actions移成了一个字段:

// Version 1
class Foo {
....

    private List<Action> actions = new List<Action>();

    // method is called from one thread only so no need to be thread-safe
    public void FrequentlyCalledMethod() {
        actions.Clear()
        for (int i = 0; i < 10; i++) {
            actions.Add(new Action { type = ActionType.AddOrder; price = 100 + i; });
        }
        // use actions, synchronous
        executor.Execute(actions);
        // now actions can be deleted
    }

也许我应该尽量避免使用“new”关键字?我可以使用一些预分配对象的“池”:

// Version 2
class Foo {
....

    private List<Action> actions = new List<Action>();
    private Action[] actionPool = new Action[10];

    // method is called from one thread only so no need to be thread-safe
    public void FrequentlyCalledMethod() {
        actions.Clear()
        for (int i = 0; i < 10; i++) {
            var action = actionsPool[i];
            action.type = ActionType.AddOrder;
            action.price = 100 + i;
            actions.Add(action);
        }
        // use actions, synchronous
        executor.Execute(actions);
        // now actions can be deleted
    }
  • 我应该走多远?
  • 避免new 有多重要?
  • 在使用我只需要配置的预分配对象时,我会赢得什么吗? (在上面的示例中设置类型和价格)

请注意,这是超低延迟,因此我们假设性能优先于可读性、可维护性等。

【问题讨论】:

  • 如果这很重要,你会信任谁:互联网上的人,还是科学基准?
  • @delnan 我确实信任 stackoverflow 上的人 :)
  • 我会尝试两者并进行测量。
  • 请不要相信 SO 这种事情哈哈,但在我们的金融应用程序中,我们基本上预先分配了大块内存,编写了一个自定义的“内存管理器”,并“分配了”我们在这个空间中的新对象。
  • 请记住,在 C# 中分配新对象的成本非常。 GC 在内存中有一个指向堆的空闲部分的指针,它将指针向上移动您分配的大小,然后运行构造函数。垃圾收集和碎片整理使堆对象比使用堆栈内存更昂贵,但是分配对象时并没有花费时间,而是稍后花费。如果这个确切的时刻对时间非常敏感,但将来某个时候会有空闲时间(运行集合),那么您可能没有问题。在 C++ 中,情况正好相反。

标签: c# performance low-latency hft


【解决方案1】:

在 C++ 中,您不需要 new 来创建范围有限的对象。

void FrequentlyCalledMethod() 
{
    std::vector<Action> actions;
    actions.reserve( 10 );
    for (int i = 0; i < 10; i++) 
    {
        actions.push_back( Action(....) );
    }
    // use actions, synchronous
    executor.Execute(actions);
    // now actions can be deleted
}

如果Action 是基类,而您拥有的实际类型是派生类,则此处需要指针或智能指针和new。但是如果 Action 是一个具体类型并且所有元素都属于这个类型,并且这个类型是默认可构造、可复制和可分配的,则不需要。

不过,一般来说,您的性能优势不太可能来自不使用 new。在 C++ 中,当它是对象的范围时,使用局部函数范围是一种很好的做法。这是因为在 C++ 中,您必须更多地关注资源管理,这是通过一种称为“RAII”的技术完成的——这本质上意味着关注如何删除资源(通过对象的析构函数)分配点。

高性能更有可能通过以下方式实现:

  • 正确使用算法
  • 适当的并行处理和同步技术
  • 有效的缓存和惰性求值。

【讨论】:

  • “有效缓存”是什么意思?你在谈论处理器缓存吗?我几乎不使用并行处理和同步。我几乎使用单线程来完成 80% 的工作,因为我希望在不到 100 微秒内完成工作,而且可能远远少于 100 微秒。使用“具体类型”还是使用“只需要配置的预分配对象”会更快吗?
  • 这样说,如果结果是使用new或者不使用(在hot code上),对你的性能影响不大,那么你的程序太慢了。
  • 既是您自己的缓存,又是确保某些操作确保正确的处理器缓存。我们之前有过关于不同集合大小在速度上形成鲜明对比的话题。
【解决方案2】:

尽管我非常讨厌 HFT,但我将告诉您如何在给定的铁块上充分利用每个线程的性能。

Here's an explanation 一个示例,其中最初编写的程序速度提高了 730 倍。

您分阶段进行。在每个阶段,您都会发现需要花费大量时间的东西,然后将其修复。 关键字是find,而不是guess。 太多的人只是盯着代码,然后修复他们认为有帮助的东西,而且通常但并不总是有帮助,有些人。 那是猜测。 要获得真正的加速,您需要找到所有问题,而不仅仅是您能猜到的少数问题。

如果您的程序正在执行new,那么在某些时候可能会出现您需要修复的问题。 但这不是唯一的事情。

这是theory behind it

【讨论】:

  • 我同意,但这不是我问题的答案。谢谢你的链接,这很有趣。
  • @javapowered:它说可能会是一个问题,但不是唯一的问题。我做的其中一件事是池回收对象。
【解决方案3】:

对于优秀 HFT 商店的高性能交易引擎,避免在 C++ 代码中使用 new/malloc 是基本原则。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-05
    • 1970-01-01
    • 2013-01-27
    • 2018-06-16
    • 1970-01-01
    • 1970-01-01
    • 2011-03-26
    相关资源
    最近更新 更多