Erstens benötigen 3904 GB Speicher nur 42 Bits, um zu adressieren. Es besteht nur aus 3 904 000 000 000
Bytes statt dem, was Sie berechnet haben
PS C:\> [math]::Log(3904GB, 2) 41.9307373375629
Wenn Sie also selbst auf einem 64-Bit-Computer nur weniger als ~ 4 Milliarden Ganzzahlen (max. 32-Bit-Ganzzahl) verwenden, frage ich mich, warum Sie nicht einfach die Zeiger auf 32 Bit setzen können. Auf diese Weise sind Zeiger 32 Bit lang, bis der Speicherplatz ausgeht. Dann können Sie 64-Bit-Zeiger verwenden. Dann hätten Sie etwas mehr Platz, um mehr Objekte zu haben.
x32-ABI ist eine 64-Bit-x86-ABI, die 32-Bit-Zeiger verwendet. Prozesse haben nur einen 32-Bit-Adressraum, was bedeutet, dass sie nicht mehr als 4 GB Arbeitsspeicher benötigen (kein Problem für die meisten Benutzeranwendungen), sie können jedoch den größeren und breiteren Registerbereich nutzen. Der globale Speicherplatz beträgt immer noch 64 Bit, da dies hardwaremäßig festgelegt ist. Daher wird der 32-Bit-Zeiger anstelle eines direkten Zeigers als Versatz zur Basisadresse des Prozesses verwendet. Die Implementierung ist einfach so
void* getRealPointer(uintptr_t base_addr, uint32_t ptr) { return (void*)(base_addr + ptr); // value stored in pointer is the offset/distance from base }
Diese Technik ist auch auf vielen anderen 64-Bit-Architekturen wie Sparc üblich ( warum verwendet Linux in der Sparc64-Architektur 32-Bit-Zeiger im Benutzerbereich und 64-Bit-Zeiger im Kernel-Space? ), MIPS oder PowerPC seit dem Übergang auf 64-Bit haben sie die Anzahl der Register wie x86 und ARM nicht erhöht, was bedeutet, dass ein 32-Bit-Prozess wahrscheinlich schneller als ein 64-Bit-Register ist, es sei denn, er benötigt 64-Bit-Berechnungen oder mehr als 2/3/4 GB RAM
Auf 64-Bit-Prozessoren wie dem G5 verwendet Debian PPC einen 64-Bit-Kernel mit 32-Bit-Benutzerplatz. Dies liegt daran, dass die 64-Bit-PowerPC-Prozessoren keinen "32-Bit-Modus" wie die Intel 64 / AMD64-Architektur aufweisen. Daher laufen 64-Bit-PowerPC-Programme, die keine mathematischen 64-Bit-Funktionen benötigen, etwas langsamer als ihre 32-Bit-Pendants, da 64-Bit-Zeiger und lange Ganzzahlen doppelt so viel Speicher verbrauchen, den CPU-Cache schneller füllen und daher mehr benötigen häufige Speicherzugriffe.
https://lwn.net/Articles/395894/
Trotzdem können Sie nicht einfach 32-Bit-Zeiger verwenden until you run out of space then start using 64-bit pointers
, das macht keinen Sinn. Ein Typ hat immer eine feste Größe. Wenn Sie Platz für nur 32 Bit des Zeigers reservieren, was passiert dann, wenn Sie 64-Bit-Zeiger verwenden müssen? Wo lagern Sie den hohen Teil?
Wenn wir also 32-Bit-Zeiger haben, die auf 32-Bit-Blöcke im 64-Bit-Speicher verweisen, bin ich mir nicht sicher, wie das aussehen oder bedeuten soll
Das heißt wortadressierbares Gedächtnis . Statt auf jedes Byte zu zeigen, zeigt nun jeder Wert einfach auf ein anderes Wort
Es ist einfacher, sich vorzustellen, dass der Speicher aus einer Reihe linearer Zellen besteht, die durch eindeutige IDs identifiziert werden. Diese IDs nennen wir normalerweise "Adresse" und werden in Zeigern gespeichert. Die Zellengröße beträgt in modernen Systemen normalerweise 1 Byte (dh byteadressierbarer Speicher). Jedoch viele ältere Systeme wie die Unisys oder PDP tun Verwendung Wort-Speicher mit einer Zelle, die ein Wort enthält (36 Bit lang bei diesen Architekturen). Daher wäre in diesen Systemen char*
eine größere int*
Anzahl erforderlich, da Sie mehr Bits benötigen, um die Position des Bytes zu speichern, das Sie adressieren möchten
Ich verstehe dein Diagramm nicht ganz, aber die Leute müssen selten jedes Bit so ansprechen, da dies den gesamten Speicherplatz reduziert, den wir ansprechen können. Obwohl dies fair ist, gibt es einige Architekturen mit bitadressierbarem Speicher, hauptsächlich eingebettete Systeme. Wenn es so aussieht, als würden Sie das niedrige 32-Bit eines 64-Bit-Werts wünschen, wenn Sie der CPU eine 32-Bit-Adresse geben, dies funktioniert jedoch nicht. Um jede Hälfte zu adressieren, benötigen Sie ein weiteres signifikantes Bit anstelle der halben Anzahl solcher Bits. Das Prinzip ist einfach: Wenn wir eine größere Zellengröße als die gleiche Speichermenge verwenden, werden weniger Zellen benötigt, was bedeutet, dass weniger Bits für die ID benötigt werden. und umgekehrt. Auf der Hardwareebene ist die Zellengröße normalerweise festgelegt.
Nachfolgend finden Sie ein Beispiel für die ersten 16 Bytes im Speicher
╔══════╤══════╤══════╤══════╤══════╤══════╤══════╤══════╤══════╤══════╤══════╤══════╤══════╤══════╤══════╤══════╗ ║ 0000 │ 0001 │ 0010 │ 0011 │ 0100 │ 0101 │ 0110 │ 0111 │ 1000 │ 1001 │ 1010 │ 1011 │ 1100 │ 1101 │ 1110 │ 1111 ║ ╠══════╪══════╪══════╪══════╪══════╪══════╪══════╪══════╪══════╪══════╪══════╪══════╪══════╪══════╪══════╪══════╣ ║ b0 │ b1 │ b2 │ b3 │ b4 │ b5 │ b6 │ b7 │ b8 │ b9 │ b10 │ b11 │ b12 │ b13 │ b14 │ b15 ║ ╟──────┴──────┼──────┴──────┼──────┴──────┼──────┴──────┼──────┴──────┼──────┴──────┼──────┴──────┼──────┴──────╢ ║ w0 000 │ w1 001 │ w2 010 │ w3 011 │ w4 100 │ w5 101 │ w6 110 │ w7 111 ║ ╟─────────────┴─────────────┼─────────────┴─────────────┼─────────────┴─────────────┼─────────────┴─────────────╢ ║ dw0 00 │ dw1 01 │ dw2 10 │ dw3 11 ║ ╟───────────────────────────┴───────────────────────────┼───────────────────────────┴───────────────────────────╢ ║ o0 │ o1 ║ ╚═══════════════════════════════════════════════════════╧═══════════════════════════════════════════════════════╝
Sie können sich auch die Illustration in dieser Antwort ansehen
Wenn wir jedes 2-Byte-Wort adressieren, hat das N-te Wort die Byteadresse als N * 2. Dies gilt für alle anderen Blockgrößen, bei denen der tatsächliche Versatz berechnet werden kann offset*sizeof(chunk)
. Als Ergebnis sind die 2 niedrigen Bits in einer 4-Byte-ausgerichteten Adresse die 3 niedrigen Bits in einer 8-Byte-ausgerichteten Adresse immer Null. Wenn Sie keine wortadressierbaren Zeiger verwenden, können diese niedrigen Bits zum Speichern von Daten verwendet werden, die als markierter Zeiger bezeichnet werden
64-Bit-JVM verwendet diese Technik mit komprimierten Oops . Siehe den Trick hinter JVMs komprimierten Oops- Objekten in Java sind immer auf 8 Byte ausgerichtet, sodass sie 8 * 4 = 32 GB Speicher mit 32-Bit-Adresse adressieren können.
Verwaltete Zeiger im Java-Heap zeigen auf Objekte, die an 8-Byte-Adressgrenzen ausgerichtet sind. Komprimierte Oops stellen verwaltete Zeiger (an vielen, aber nicht allen Stellen in der JVM-Software) als 32-Bit-Objektversätze von der 64-Bit-Java-Heap-Basisadresse dar. Da es sich um Objekt-Offsets anstelle von Byte-Offsets handelt, können sie verwendet werden, um bis zu vier Milliarden Objekte (keine Bytes) oder eine Heap-Größe von bis zu etwa 32 Gigabyte zu adressieren. Um sie verwenden zu können, müssen sie um den Faktor 8 skaliert und zur Basisadresse des Java-Heap hinzugefügt werden, um das Objekt zu finden, auf das sie sich beziehen. Objektgrößen mit komprimierten Oops sind vergleichbar mit denen im ILP32-Modus.
https://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html#compressedOop
Anweisungen, die auf den meisten RISC-Architekturen sofort ausgeführt werden, speichern auch die Wortadresse, da es keinen Sinn macht, wertvollen Speicherplatz zu sparen, um diese immer Null-Bits zu sparen. Zum Beispiel MIPS-Verzweigungs- und Sprunganweisungen : JAL, J, BEQ, BLEZ, BGTZ ...