Ja aber...
Es ist sicherlich möglich , aber normalerweise unsinnig (für jedes Programm, das nicht Milliarden dieser Zahlen verwendet):
#include <stdint.h> // don't want to rely on something like long long
struct bad_idea
{
uint64_t var : 40;
};
Hier var
wird in der Tat eine Breite von 40 Bit auf Kosten von viel weniger effizientem Code generiert (es stellt sich heraus, dass "viel" sehr viel falsch ist - der gemessene Overhead beträgt nur 1-2%, siehe Timings unten) und normalerweise ohne Erfolg. Sofern Sie keinen weiteren 24-Bit-Wert (oder einen 8- und 16-Bit-Wert) benötigen, den Sie in dieselbe Struktur packen möchten, verfällt bei der Ausrichtung alles, was Sie möglicherweise gewinnen.
In jedem Fall wird der effektive Unterschied im Speicherverbrauch nicht spürbar sein, es sei denn, Sie haben Milliarden davon (aber der zusätzliche Code, der zur Verwaltung des Bitfelds benötigt wird, wird spürbar sein!).
Hinweis:
Die Frage wurde in der Zwischenzeit aktualisiert, um zu berücksichtigen, dass tatsächlich Milliarden von Zahlen benötigt werden. Dies kann daher sinnvoll sein, vorausgesetzt, Sie ergreifen Maßnahmen, um die Gewinne aufgrund von Strukturausrichtung und Polsterung nicht zu verlieren indem Sie etwas anderes in den verbleibenden 24 Bit speichern oder indem Sie Ihre 40-Bit-Werte in Strukturen von jeweils 8 oder mehreren davon speichern).
Das milliardenfache Speichern von drei Bytes lohnt sich, da dadurch deutlich weniger Speicherseiten benötigt werden und somit weniger Cache- und TLB-Fehler und vor allem Seitenfehler (ein einzelner Seitenfehler mit einer Gewichtung von mehreren zehn Millionen Anweisungen) verursacht werden.
Während das obige Snippet die verbleibenden 24 Bit nicht verwendet (es zeigt lediglich den Teil "40 Bit verwenden"), ist etwas Ähnliches wie das Folgende erforderlich, um den Ansatz wirklich nützlich zu machen, um Speicher zu erhalten - vorausgesetzt, dass Sie haben in der Tat andere "nützliche" Daten, die Sie in die Löcher stecken müssen:
struct using_gaps
{
uint64_t var : 40;
uint64_t useful_uint16 : 16;
uint64_t char_or_bool : 8;
};
Strukturgröße und Ausrichtung entsprechen einer 64-Bit-Ganzzahl, sodass nichts verschwendet wird, wenn Sie beispielsweise ein Array von einer Milliarde solcher Strukturen erstellen (auch ohne Verwendung von compilerspezifischen Erweiterungen). Wenn Sie keinen 8-Bit-Wert verwenden können, können Sie auch einen 48-Bit- und einen 16-Bit-Wert verwenden (was einen größeren Überlaufspielraum ergibt).
Alternativ können Sie auf Kosten der Benutzerfreundlichkeit 8 40-Bit-Werte in eine Struktur einfügen (das kleinste gemeinsame Vielfache von 40 und 64 ist 320 = 8 * 40). Natürlich wird dann Ihr Code, der auf Elemente im Array von Strukturen zugreift, viel komplizierter (obwohl man wahrscheinlich einen implementieren könnte operator[]
, der die Funktionalität des linearen Arrays wiederherstellt und die Komplexität der Struktur verbirgt).
Update:
Schrieb eine schnelle Testsuite, um zu sehen, welchen Overhead die Bitfelder (und die Überladung des Operators mit Bitfeldreferenzen) haben würden. Der auf gcc.godbolt.org veröffentlichte Code (aufgrund der Länge) lautet : Die Testausgabe von meinem Win7-64-Computer lautet:
Running test for array size = 1048576
what alloc seq(w) seq(r) rand(w) rand(r) free
-----------------------------------------------------------
uint32_t 0 2 1 35 35 1
uint64_t 0 3 3 35 35 1
bad40_t 0 5 3 35 35 1
packed40_t 0 7 4 48 49 1
Running test for array size = 16777216
what alloc seq(w) seq(r) rand(w) rand(r) free
-----------------------------------------------------------
uint32_t 0 38 14 560 555 8
uint64_t 0 81 22 565 554 17
bad40_t 0 85 25 565 561 16
packed40_t 0 151 75 765 774 16
Running test for array size = 134217728
what alloc seq(w) seq(r) rand(w) rand(r) free
-----------------------------------------------------------
uint32_t 0 312 100 4480 4441 65
uint64_t 0 648 172 4482 4490 130
bad40_t 0 682 193 4573 4492 130
packed40_t 0 1164 552 6181 6176 130
Was man sehen kann, ist, dass der zusätzliche Overhead von Bitfeldern vernachlässigbar ist, aber das Überladen des Operators mit Bitfeldreferenz als Annehmlichkeitssache ist ziemlich drastisch (etwa 3-fache Zunahme), wenn auf Daten auf cachefreundliche Weise linear zugegriffen wird. Auf der anderen Seite spielt es beim Direktzugriff kaum eine Rolle.
Diese Timings legen nahe, dass die einfache Verwendung von 64-Bit-Ganzzahlen besser wäre, da sie insgesamt immer noch schneller sind als Bitfelder (obwohl mehr Speicher berührt wird), aber natürlich berücksichtigen sie nicht die Kosten für Seitenfehler bei viel größeren Datensätzen. Es könnte ganz anders aussehen, wenn Ihnen der physische Arbeitsspeicher ausgeht (das habe ich nicht getestet).