代码之家  ›  专栏  ›  技术社区  ›  finnw

位字段:设置与测试和设置(性能)

  •  6
  • finnw  · 技术社区  · 16 年前

    我有很多这样的C结构的实例:

    struct mystruct
    {
        /* ... */
        unsigned flag: 1;
        /* ... */
    };
    
    • flag 初始值为0,但退出某个函数时必须为1。

    void set_flag(struct mystruct *sp)
    {
        sp->flag = 1U;
    }
    

    void set_flag(struct mystruct *sp)
    {
        if (sp->flag == 0U)
        {
            sp->flag = 1U;
        }
    }
    

    哪些其他因素(如分支预测)可能会影响性能?

    到目前为止,我看到了一个小的速度增长,我希望随着数据集的增大,速度会变得更加显著。

    这种变化是否有可能使程序在处理大型数据集时变慢,如果有,在什么情况下会发生这种情况?

    9 回复  |  直到 16 年前
        1
  •  10
  •   BenMorel Manish Pradhan    12 年前

    设置前的测试确实有所不同,但具体程度取决于您的用例。

    在任何一种情况下,数据最终都会出现在缓存行中(例如,只是写入或测试和设置)。

    现在考虑一下,你的代码会破坏大量数据,而你只能访问每个数据块一两次。如果是这样,可以假设大多数内存访问都是缓存未命中。如果在发生缓存未命中时,大多数缓存行都是脏的,并且大多数缓存行也是脏的,会发生什么?

    这可能对一个CPU核心没有影响,因为现在内存很快,但另一个CPU(希望)也会做一些其他工作。可以肯定的是,如果总线不忙于将缓存行移入和移出,另一个CPU内核将执行所有操作更快。

    简而言之:保持缓存行干净将使带宽需求减半,并使缓存丢失成本降低一点。

    关于分支:当然:这很贵,但缓存丢失更糟糕!此外,如果你幸运的话,CPU会使用它的乱序执行功能来用分支的成本来抵消缓存缺失。

    如果你真的想从这段代码中获得最佳性能,并且如果你的大部分访问都是缓存未命中,你有两个选择:

    • 绕过缓存:x86架构为此目的提供了非临时加载和存储。它们隐藏在SSE指令集中的某个地方,可以通过内部函数从c语言中使用。

        2
  •  3
  •   Glorfindel Doug L.    6 年前

    这是一个有趣的问题,Nils对缓存行的回答绝对是一个很好的建议。

    --你能衡量在你遇到的数据中设置该标志的频率吗?根据答案的不同,性能可能会发生很大变化。

    comparison of set vs. test-then-set
    natekohl.net )

    当然,这只是一个玩具示例。但请注意非线性性能——这是我没有预料到的——当数组几乎完全被1填充时,测试集的速度会比普通集快。

        3
  •  2
  •   nik    16 年前

    这些是我对你的要求的解释,

    • 您需要单独初始化标志
    • 它只设置一次(设置为1),之后不会重置

    • 空间优化的权重远低于时间优化,

    我建议采取以下措施。

    • 但是,话虽如此,如果你的平台进行并行操作(例如,通常可以在代码执行的同时并行发送对磁盘的写入),那么跳过检查是值得的。
        4
  •  1
  •   sharptooth    16 年前

    当移动到更大的数据集时,这种优化可能不会导致速度降低。

    读取值相同时的缓存抖动、分支预测惩罚也将相同,这些是这里需要优化的关键因素。

        5
  •  0
  •   Viktor Sehr    16 年前

    你总是可以分析的,但我很确定第一个版本既快又不那么晦涩难懂。

        6
  •  0
  •   Glen    16 年前

    无论哪种方法都需要将数据加载到缓存中,因此您唯一的保存将是读/写和写之间的差异。

    我看不出这种变化会让你的代码在处理更大的数据集时变慢,所以你在这方面可能足够安全。

    与所有与性能相关的事情一样,确定代码更改效果的最佳方法是对其进行测量。您应该能够相对容易地创建大量测试数据。

        7
  •  0
  •   laalto    16 年前

    如果您真的担心时间性能,请将标志更改为完整的int而不是bitfield。然后设置它只是一个写操作,而不是像位字段那样的读写操作。

    但正如已经指出的那样,这闻起来像是微观优化。

        8
  •  0
  •   qrdl    16 年前

    设置之前的测试没有任何意义——没有测试的代码更干净,也更快。

    顺便说一句,像这样内联函数是有意义的,因为函数调用的开销比函数体大,尽管优化编译器应该毫不犹豫地做到这一点。

        9
  •  0
  •   Chris Arguin    16 年前

    既然没人说,我就说。

    你为什么要使用bit字段?布局因编译器而异,因此它们对接口毫无用处。它们可能更节省空间,也可能不更节省空间;编译器可能只是决定将它们推入32位字段,以便有效地填充内容。不能保证它们更快,事实上它们可能会更慢。