首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >什么时候使用位字段是值得的?

什么时候使用位字段是值得的?
EN

Stack Overflow用户
提问于 2010-11-22 07:11:48
回答 10查看 34.9K关注 0票数 55

是否值得使用C的位域实现?如果是这样,它什么时候使用过?

我查看了一些仿真器代码,它看起来芯片的寄存器不是使用位字段实现的。

这是出于性能原因(或其他原因)而避免的吗?

还有没有使用位字段的时候?(即安装在实际芯片上的固件等)

EN

回答 10

Stack Overflow用户

发布于 2010-11-22 07:14:16

位字段通常仅在需要将结构字段映射到特定位片时使用,其中一些硬件将解释原始位。一个例子可能是组装IP数据包报头。我看不出有什么令人信服的理由让仿真器使用位域对寄存器进行建模,因为它永远不会接触到真正的硬件!

虽然位字段可以产生简洁的语法,但它们是非常依赖于平台的,因此不可移植。一种更可移植但更冗长的方法是使用直接的位操作,使用移位和位掩码。

如果您将位域用于除在某些物理接口上组装(或拆卸)结构之外的任何事情,则性能可能会受到影响。这是因为每次读取或写入位域时,编译器都必须生成代码来进行掩码和移位,这将消耗周期。

票数 33
EN

Stack Overflow用户

发布于 2010-11-22 11:21:59

FWIW,并且只看相对性能问题-一个健壮的基准:

代码语言:javascript
复制
#include <time.h>
#include <iostream>

struct A
{
    void a(unsigned n) { a_ = n; }
    void b(unsigned n) { b_ = n; }
    void c(unsigned n) { c_ = n; }
    void d(unsigned n) { d_ = n; }
    unsigned a() { return a_; }
    unsigned b() { return b_; }
    unsigned c() { return c_; }
    unsigned d() { return d_; }
    volatile unsigned a_:1,
                      b_:5,
                      c_:2,
                      d_:8;
};

struct B
{
    void a(unsigned n) { a_ = n; }
    void b(unsigned n) { b_ = n; }
    void c(unsigned n) { c_ = n; }
    void d(unsigned n) { d_ = n; }
    unsigned a() { return a_; }
    unsigned b() { return b_; }
    unsigned c() { return c_; }
    unsigned d() { return d_; }
    volatile unsigned a_, b_, c_, d_;
};

struct C
{
    void a(unsigned n) { x_ &= ~0x01; x_ |= n; }
    void b(unsigned n) { x_ &= ~0x3E; x_ |= n << 1; }
    void c(unsigned n) { x_ &= ~0xC0; x_ |= n << 6; }
    void d(unsigned n) { x_ &= ~0xFF00; x_ |= n << 8; }
    unsigned a() const { return x_ & 0x01; }
    unsigned b() const { return (x_ & 0x3E) >> 1; }
    unsigned c() const { return (x_ & 0xC0) >> 6; }
    unsigned d() const { return (x_ & 0xFF00) >> 8; }
    volatile unsigned x_;
};

struct Timer
{
    Timer() { get(&start_tp); }
    double elapsed() const {
        struct timespec end_tp;
        get(&end_tp);
        return (end_tp.tv_sec - start_tp.tv_sec) +
               (1E-9 * end_tp.tv_nsec - 1E-9 * start_tp.tv_nsec);
    }
  private:
    static void get(struct timespec* p_tp) {
        if (clock_gettime(CLOCK_REALTIME, p_tp) != 0)
        {
            std::cerr << "clock_gettime() error\n";
            exit(EXIT_FAILURE);
        }
    }
    struct timespec start_tp;
};

template <typename T>
unsigned f()
{
    int n = 0;
    Timer timer;
    T t;
    for (int i = 0; i < 10000000; ++i)
    {
        t.a(i & 0x01);
        t.b(i & 0x1F);
        t.c(i & 0x03);
        t.d(i & 0xFF);
        n += t.a() + t.b() + t.c() + t.d();
    }
    std::cout << timer.elapsed() << '\n';
    return n;
}

int main()
{
    std::cout << "bitfields: " << f<A>() << '\n';
    std::cout << "separate ints: " << f<B>() << '\n';
    std::cout << "explicit and/or/shift: " << f<C>() << '\n';
}

在我的测试机器上的输出(运行到运行的数字会有大约20%的变化):

代码语言:javascript
复制
bitfields: 0.140586
1449991808
separate ints: 0.039374
1449991808
explicit and/or/shift: 0.252723
1449991808

建议在最近的Athlon上使用g++ -O3,位字段比单独的整数慢几倍,这种特殊的和/或/位移位实现至少比两倍糟糕(由于上面的波动性强调了内存读/写等其他操作,并且存在循环开销等,因此结果中的差异被低估了)。

如果您正在处理数百兆字节的结构,这些结构可能主要是位字段或主要是不同的整数,缓存问题可能会成为主要问题-因此在您的系统中进行基准测试。

使用AMD Ryzen 9 3900X和-O2 -march=native从2021年开始更新

代码语言:javascript
复制
bitfields: 0.0224893
1449991808
separate ints: 0.0288447
1449991808
explicit and/or/shift: 0.0190325
1449991808

在这里,我们看到一切都发生了巨大的变化,主要的含义是-与您关心的系统进行基准测试。

更新: user2188211尝试了一次编辑,但被拒绝了,但很有用地说明了随着数据量的增加,位域是如何变得更快的:“当在上述代码的修改版本中迭代几百万个元素的向量时,使得变量不驻留在缓存或寄存器中,位域代码可能是最快的。”

代码语言:javascript
复制
template <typename T>
unsigned f()
{
    int n = 0;
    Timer timer;
    std::vector<T> ts(1024 * 1024 * 16);
    for (size_t i = 0, idx = 0; i < 10000000; ++i)
    {
        T& t = ts[idx];
        t.a(i & 0x01);
        t.b(i & 0x1F);
        t.c(i & 0x03);
        t.d(i & 0xFF);
        n += t.a() + t.b() + t.c() + t.d();
        idx++;
        if (idx >= ts.size()) {
            idx = 0;
        }
    }
    std::cout << timer.elapsed() << '\n';
    return n;
}

示例运行的结果(g++ -03,Core2Duo):

代码语言:javascript
复制
 0.19016
 bitfields: 1449991808
 0.342756
 separate ints: 1449991808
 0.215243
 explicit and/or/shift: 1449991808

当然,时间都是相对的,在您的系统上下文中,实现这些字段的方式可能根本不重要。

票数 11
EN

Stack Overflow用户

发布于 2010-11-22 07:34:03

我在两种情况下见过/使用过位字段:计算机游戏和硬件接口。硬件的使用非常简单:硬件要求数据采用某种位格式,您可以手动定义,也可以通过预定义的库结构来定义。这取决于特定的库,它们是使用位字段还是仅使用位操作。

在“旧时代”,计算机游戏经常使用位字段,以尽可能地利用计算机/磁盘内存。例如,对于RPG中的NPC定义,您可能会找到(虚构的示例):

代码语言:javascript
复制
struct charinfo_t
{
     unsigned int Strength : 7;  // 0-100
     unsigned int Agility : 7;  
     unsigned int Endurance: 7;  
     unsigned int Speed : 7;  
     unsigned int Charisma : 7;  
     unsigned int HitPoints : 10;    //0-1000
     unsigned int MaxHitPoints : 10;  
     //etc...
};

你在更现代的游戏/软件中看不到这一点,因为随着计算机获得更多的内存,节省的空间成比例地变得更差。当你的计算机只有16MB时,节省1MB的内存是一件很大的事情,但当你有4 4GB的内存时,节省的内存就不那么多了。

票数 9
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/4240974

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档