什么时候值得使用位域?

发布于 2024-10-03 19:57:28 字数 145 浏览 3 评论 0原文

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

我正在查看一些模拟器代码,看起来芯片的寄存器没有使用位字段来实现。

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

还会有使用位域的时候吗? (即安装在实际芯片上的固件等)

Is it worthwhile using C's bit-field implementation? If so, when is it ever used?

I was looking through some emulator code and it looks like the registers for the chips are not being implemented using bit fields.

Is this something that is avoided for performance reasons (or some other reason)?

Are there still times when bit-fields are used? (ie firmware to put on actual chips, etc)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

豆芽 2024-10-10 19:57:29

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

在“过去”,计算机游戏经常使用位字段来尽可能充分利用计算机/磁盘内存。例如,对于 RPG 中的 NPC 定义,您可能会发现(编造的示例):

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 内存是一件大事,但当您有 4GB 时,节省 1MB 内存就不是那么重要了。

I've seen/used bit fields in two situations: Computer Games and Hardware Interfaces. The hardware use is pretty straight forward: the hardware expects data in a certain bit format you can either define manually or through pre-defined library structures. It depends on the specific library whether they use bit fields or just bit manipulation.

In the "old days" computers games used bit fields frequently to make the most use of computer/disk memory as possible. For example, for a NPC definition in a RPG you might find (made up example):

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...
};

You don't see it so much in more modern games/software as the space savings has gotten proportionally worse as computers get more memory. Saving a 1MB of memory when your computer only has 16MB is a big deal but not so much when you have 4GB.

荆棘i 2024-10-10 19:57:29

位字段的主要目的是提供一种通过实现更紧密的数据打包来节省大规模实例化聚合数据结构中的内存的方法。

整个想法是利用某些结构类型中有多个字段的情况,这些字段不需要某些标准数据类型的整个宽度(和范围)。这使您有机会将多个此类字段打包到一个分配单元中,从而减少结构类型的总体大小。极端的例子是布尔字段,它可以由单独的位表示(例如,其中 32 个位可以打包到单个 unsigned int 分配单元中)。

显然,只有在减少内存消耗的优点超过对存储在位字段中的值的访问速度较慢的缺点的情况下,这才有意义。然而,这种情况经常出现,这使得位域成为绝对不可或缺的语言特性。这应该回答您关于位字段的现代使用的问题:不仅使用它们,而且在任何面向处理大量同质数据(例如大图)的实际有意义的代码中,它们本质上是强制性的,因为它们的内存-节省的好处大大超过任何个人访问性能的损失。

在某种程度上,位域的用途与“小型”算术类型非常相似:signed/unsigned charshortfloat代码>.在实际的数据处理代码中,通常不会使用任何小于 intdouble 的类型(除了少数例外)。像 signed/unsigned charshortfloat 这样的算术类型的存在只是为了充当“存储”类型:作为结构体的节省内存的紧凑成员在已知其范围(或精度)足够的情况下类型。位字段只是朝同一方向迈出的又一步,它以更高的性能换取更大的内存节省优势。

因此,这为我们提供了一组相当明确的条件,在这些条件下值得使用位字段:

  1. 结构类型包含多个字段,这些字段可以打包为更少的位数。
  2. 该程序实例化大量该结构类型的对象。

如果满足条件,则连续声明所有可位打包字段(通常在结构类型的末尾),为它们分配适当的位宽度(并且通常采取一些步骤来确保位宽度适当) 。在大多数情况下,调整这些字段的顺序以实现最佳打包和/或性能是有意义的。


位域还有一个奇怪的二次用途:使用它们以各种外部指定的表示形式映射位组,例如硬件寄存器、浮点格式、文件格式等。这从来都不是位域的正确使用,尽管由于某些无法解释的原因,这种位域滥用继续在现实代码中出现。只是不要这样做。

The primary purpose of bit-fields is to provide a way to save memory in massively instantiated aggregate data structures by achieving tighter packing of data.

The whole idea is to take advantage of situations where you have several fields in some struct type, which don't need the entire width (and range) of some standard data type. This provides you with the opportunity to pack several of such fields in one allocation unit, thus reducing the overall size of the struct type. And extreme example would be boolean fields, which can be represented by individual bits (with, say, 32 of them being packable into a single unsigned int allocation unit).

Obviously, this only makes sense in situation where the pros of the reduced memory consumption outweigh the cons of slower access to values stored in bit-fields. However, such situations arise quite often, which makes bit-fields an absolutely indispensable language feature. This should answer your question about the modern use of bit-fields: not only they are used, they are essentially mandatory in any practically meaningful code oriented on processing large amounts of homogeneous data (like large graphs, for one example), because their memory-saving benefits greatly outweigh any individual-access performance penalties.

In a way, bit-fields in their purpose are very similar to such things as "small" arithmetic types: signed/unsigned char, short, float. In the actual data-processing code one would not normally use any types smaller than int or double (with few exceptions). Arithmetic types like signed/unsigned char, short, float exist just to serve as "storage" types: as memory-saving compact members of struct types in situations where their range (or precision) is known to be sufficient. Bit-fields is just another step in the same direction, that trades a bit more performance for much greater memory-saving benefits.

So, that gives us a rather clear set of conditions under which it is worthwhile to employ bit-fields:

  1. Struct type contains multiple fields that can be packed into a smaller number of bits.
  2. The program instantiates a large number of objects of that struct type.

If the conditions are met, you declare all bit-packable fields contiguously (typically at the end of the struct type), assign them their appropriate bit-widths (and, usually, take some steps to ensure that the bit-widths are appropriate). In most cases it makes sense to play around with ordering of these fields to achieve the best packing and/or performance.


There's also a weird secondary use of bit-fields: using them for mapping bit groups in various externally-specified representations, like hardware registers, floating-point formats, file formats etc. This has never been intended as a proper use of bit-fields, even though for some unexplained reason this kind of bit-field abuse continues to pop-up in real-life code. Just don't do this.

花辞树 2024-10-10 19:57:29

位字段的一种用途是在编写嵌入式代码时镜像硬件寄存器。但是,由于位顺序取决于平台,因此如果硬件对其位的顺序与处理器不同,则它们将不起作用。也就是说,我想不出位字段的用途了。您最好实现一个可以跨平台移植的位操作库。

One use for bit fields used to be to mirror hardware registers when writing embedded code. However, since the bit order is platform-dependent, they don't work if the hardware orders its bits different from the processor. That said, I can't think of a use for bit fields any more. You're better off implementing a bit manipulation library that can be ported across platforms.

她说她爱他 2024-10-10 19:57:29

过去使用位字段来节省程序内存。

它们会降低性能,因为寄存器无法使用它们,因此必须将它们转换为整数才能对它们执行任何操作。 这些代码不可移植且难以理解(因为您必须始终屏蔽和取消屏蔽事物才能实际使用这些值。)

它们往往会导致更复杂的代码, nethack.org/" rel="nofollow">http://www.nethack.org/ 来看看 pre ansi c 的所有位域荣耀!

Bit fields were used in the olden days to save program memory.

They degrade performance because registers can not work with them so they have to be converted to integers to do anything with them. They tend to lead to more complex code that is unportable and harder to understand (since you have to mask and unmask things all the time to actually use the values.)

Check out the source for http://www.nethack.org/ to see pre ansi c in all its bitfield glory!

百合的盛世恋 2024-10-10 19:57:29

在 70 年代,我使用位字段来控制 trs80 上的硬件。显示器/键盘/磁带/磁盘都是内存映射设备。各个位控制着不同的东西。

  1. 位控制的 32 列与 64 列显示。
  2. 同一存储单元中的位 0 是磁带串行数据输入/输出。

我记得,磁盘驱动器控件有很多这样的东西。总共有 4 个字节。我认为有一个 2 位驱动器选择。但那是很久以前的事了。当时令人印象深刻的是,该平台至少有两种不同的 C 编译器。

另一个观察结果是位字段确实是特定于平台的。不期望具有位字段的程序应该移植到另一个平台。

In the 70s I used bit fields to control hardware on a trs80. The display/keyboard/cassette/disks were all memory mapped devices. Individual bits controlled various things.

  1. A bit controlled 32 column vs 64 column display.
  2. Bit 0 in that same memory cell was the cassette serial data in/out.

As I recall, the disk drive control had a number of them. There were 4 bytes in total. I think there was a 2 bit drive select. But it was a long time ago. It was kind of impressive back then in that there were at least two different c compilers for the platform.

The other observation is that bit fields really are platform specific. There is no expectation that a program with bit fields should port to another platform.

陈年往事 2024-10-10 19:57:29

在现代代码中,使用位域实际上只有一个原因:控制结构/类中 boolenum 类型的空间需求。例如(C++):

enum token_code { TK_a, TK_b, TK_c, ... /* less than 255 codes */ };
struct token {
    token_code code      : 8;
    bool number_unsigned : 1;
    bool is_keyword      : 1;
    /* etc */
};

IMO 基本上没有理由不对 bool 使用 :1 位域,因为现代编译器将为它生成非常高效的代码。不过,在 C 中,请确保您的 bool typedef 是 C99 _Bool 或失败的 无符号 int,因为有符号的 1 位字段只能保存值 0-1 (除非您有一台非补码机器)。

对于枚举类型,请始终使用与原始整数类型之一(8/16/32/64 位,在普通 CPU 上)的大小相对应的大小,以避免低效的代码生成(通常是重复的读取-修改-写入周期) 。

通常建议使用位域将结构与某些外部定义的数据格式(数据包标头、内存映射 I/O 寄存器)对齐,但我实际上认为这是一个不好的做法,因为 C 无法提供对字节顺序的足够控制、填充和(对于 I/O 寄存器)确切发出的汇编序列。如果您想了解这方面缺少多少 C,请找个时间看看 Ada 的表示子句。

In modern code, there's really only one reason to use bitfields: to control the space requirements of a bool or an enum type, within a struct/class. For instance (C++):

enum token_code { TK_a, TK_b, TK_c, ... /* less than 255 codes */ };
struct token {
    token_code code      : 8;
    bool number_unsigned : 1;
    bool is_keyword      : 1;
    /* etc */
};

IMO there's basically no reason not to use :1 bitfields for bool, as modern compilers will generate very efficient code for it. In C, though, make sure your bool typedef is either the C99 _Bool or failing that an unsigned int, because a signed 1-bit field can hold only the values 0 and -1 (unless you somehow have a non-twos-complement machine).

With enumeration types, always use a size that corresponds to the size of one of the primitive integer types (8/16/32/64 bits, on normal CPUs) to avoid inefficient code generation (repeated read-modify-write cycles, usually).

Using bitfields to line up a structure with some externally-defined data format (packet headers, memory-mapped I/O registers) is commonly suggested, but I actually consider it a bad practice, because C doesn't give you enough control over endianness, padding, and (for I/O regs) exactly what assembly sequences get emitted. Have a look at Ada's representation clauses sometime if you want to see how much C is missing in this area.

南…巷孤猫 2024-10-10 19:57:29

Boost.Thread 在其 shared_mutex 中使用位域,至少在 Windows 上:

    struct state_data
    {
        unsigned shared_count:11,
        shared_waiting:11,
        exclusive:1,
        upgrade:1,
        exclusive_waiting:7,
        exclusive_waiting_blocked:1;
    };

Boost.Thread uses bitfields in its shared_mutex, on Windows at least:

    struct state_data
    {
        unsigned shared_count:11,
        shared_waiting:11,
        exclusive:1,
        upgrade:1,
        exclusive_waiting:7,
        exclusive_waiting_blocked:1;
    };
小镇女孩 2024-10-10 19:57:29

另一种可以考虑的方法是使用虚拟结构(从未实例化)指定位域结构,其中每个字节代表一个位:

struct Bf_format
{
  char field1[5];
  char field2[9];
  char field3[18];
};

使用这种方法,sizeof 给出位域的宽度,offsetof< /strong> 给出位字段的偏移量。至少在 GNU gcc 的情况下,按位操作的编译器优化(使用恒定的移位和掩码)似乎已经与(基本语言)位字段大致相同。

我编写了一个 C++ 头文件(使用这种方法),它允许以高性能、更便携、更灵活的方式定义和使用位字段的结构: https://github.com/wkaras/C-plus-plus-library-bit-fields 。因此,除非您坚持使用 C,否则我认为很少有充分的理由对位字段使用基本语言工具。

An alternative to consider is to specify bit field structures with a dummy structure (never instantiated) where each byte represents a bit:

struct Bf_format
{
  char field1[5];
  char field2[9];
  char field3[18];
};

With this approach sizeof gives the width of the bit field, and offsetof give the offset of the bit field. At least in the case of GNU gcc, compiler optimization of bit-wise operations (with constant shifts and masks) seems to have gotten to rough parity with (base language) bit fields.

I have written a C++ header file (using this approach) which allows structures of bit fields to be defined and used in a performant, much more portable, much more flexible way: https://github.com/wkaras/C-plus-plus-library-bit-fields . So, unless you are stuck using C, I think there would rarely be a good reason to use the base language facility for bit fields.

野の 2024-10-10 19:57:28

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

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

如果您将位字段用于除在某些物理接口上组装(或反汇编)结构之外的任何其他用途,则性能可能会受到影响。这是因为每次从位字段读取或写入时,编译器都必须生成代码来执行屏蔽和移位,这会消耗周期。

Bit-fields are typically only used when there's a need to map structure fields to specific bit slices, where some hardware will be interpreting the raw bits. An example might be assembling an IP packet header. I can't see a compelling reason for an emulator to model a register using bit-fields, as it's never going to touch real hardware!

Whilst bit-fields can lead to neat syntax, they're pretty platform-dependent, and therefore non-portable. A more portable, but yet more verbose, approach is to use direct bitwise manipulation, using shifts and bit-masks.

If you use bit-fields for anything other than assembling (or disassembling) structures at some physical interface, performance may suffer. This is because every time you read or write from a bit-field, the compiler will have to generate code to do the masking and shifting, which will burn cycles.

一百个冬季 2024-10-10 19:57:28

尚未提及的位域的一种用途是无符号位域“免费”提供对 2 的幂取模的算术运算。例如,给定:

struct { unsigned x:10; } foo;

foo.x 上的算术将以 210 = 1024 为模进行计算。

(同样可以直接使用按位 & 操作,当然 - 但有时它可能会导致更清晰的代码让编译器为您做这件事)。

One use for bitfields which hasn't yet been mentioned is that unsigned bitfields provide arithmetic modulo a power-of-two "for free". For example, given:

struct { unsigned x:10; } foo;

arithmetic on foo.x will be performed modulo 210 = 1024.

(The same can be achieved directly by using bitwise & operations, of course - but sometimes it might lead to clearer code to have the compiler do it for you).

阪姬 2024-10-10 19:57:28

FWIW,仅查看相对性能问题 - 一个庞大的基准测试:

#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%):

bitfields: 0.140586
1449991808
separate ints: 0.039374
1449991808
explicit and/or/shift: 0.252723
1449991808

表明在最近的 Athlon 上使用 g++ -O3,位域比一些位域更糟糕比单独的整数慢几倍,并且这个特定的和/或/位移实现至少再次糟糕两倍(“更糟糕”,因为上面的波动性强调了内存读/写等其他操作,并且存在循环开销等,因此差异是结果中被低估)。

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

<块引用>
<块引用>

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


bitfields: 0.0224893
1449991808
separate ints: 0.0288447
1449991808
explicit and/or/shift: 0.0190325
1449991808

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


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

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):

 0.19016
 bitfields: 1449991808
 0.342756
 separate ints: 1449991808
 0.215243
 explicit and/or/shift: 1449991808

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

FWIW, and looking only at the relative performance question - a bodgy benchmark:

#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';
}

Output on my test machine (numbers vary by ~20% run to run):

bitfields: 0.140586
1449991808
separate ints: 0.039374
1449991808
explicit and/or/shift: 0.252723
1449991808

Suggests that with g++ -O3 on a pretty recent Athlon, bitfields are worse than a few times slower than separate ints, and this particular and/or/bitshift implementation's at least twice as bad again ("worse" as other operations like memory read/writes are emphasised by the volatility above, and there's loop overhead etc, so the differences are understated in the results).

If you're dealing in hundreds of megabytes of structs that can be mainly bitfields or mainly distinct ints, the caching issues may become dominant - so benchmark in your system.

update from 2021 with an AMD Ryzen 9 3900X and -O2 -march=native:

bitfields: 0.0224893
1449991808
separate ints: 0.0288447
1449991808
explicit and/or/shift: 0.0190325
1449991808

Here we see everything has changed massively, the main implication being - benchmark with the systems you care about.


UPDATE: user2188211 attempted an edit which was rejected but usefully illustrated how bitfields become faster as the amount of data increases: "when iterating over a vector of a few million elements in [a modified version of] the above code, such that the variables do not reside in cache or registers, the bitfield code may be the fastest."

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;
}

Results on from an example run (g++ -03, Core2Duo):

 0.19016
 bitfields: 1449991808
 0.342756
 separate ints: 1449991808
 0.215243
 explicit and/or/shift: 1449991808

Of course, timing's all relative and which way you implement these fields may not matter at all in the context of your system.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文