Nur die Designer der .NET-Laufzeit können Ihnen ihre tatsächlichen Gründe mitteilen. Ich vermute jedoch, dass die Speichersicherheit in .NET von größter Bedeutung ist und es sehr teuer wäre, sowohl die Speichersicherheit als auch die veränderlichen Array-Längen aufrechtzuerhalten, ganz zu schweigen davon, wie kompliziert Code mit Arrays wäre.
Betrachten Sie den einfachen Fall:
var fun = 0;
for (var i = 0; i < array.Length; i++)
{
fun ^= array[i];
}
Um die Speichersicherheit zu gewährleisten, arraymuss jeder Zugriff auf Grenzen überprüft werden, während sichergestellt werden muss, dass die Grenzen nicht von anderen Threads überprüft werden (die .NET-Laufzeit hat viel strengere Garantien als beispielsweise der C-Compiler).
Sie benötigen also eine thread-sichere Operation, die Daten aus dem Array liest und gleichzeitig die Grenzen überprüft. Es gibt keine solche Anweisung auf der CPU, daher ist Ihre einzige Option ein Synchronisationsprimitiv. Ihr Code wird zu:
var fun = 0;
for (var i = 0; i < array.Length; i++)
{
lock (array)
{
if (i >= array.Length) throw new IndexOutOfBoundsException(...);
fun ^= array[i];
}
}
Das ist natürlich schrecklich teuer. Wenn Sie die Array-Länge unveränderlich machen, erhalten Sie zwei massive Leistungssteigerungen:
- Da sich die Länge nicht ändern kann, muss die Begrenzungsprüfung nicht synchronisiert werden. Dies macht jede einzelne Grenzüberprüfung erheblich billiger.
- ... und Sie können die Grenzen weglassen, um zu überprüfen, ob Sie die Sicherheit dafür nachweisen können.
In Wirklichkeit ist das, was die Laufzeit tatsächlich tut, eher so:
var fun = 0;
var len = array.Length;
for (var i = 0; i < len; i++)
{
fun ^= array[i];
}
Am Ende haben Sie eine enge Schleife, die sich nicht von der in C unterscheidet - aber gleichzeitig ist sie absolut sicher.
Lassen Sie uns nun die Vor- und Nachteile des Hinzufügens eines Arrays so sehen, wie Sie es möchten:
Vorteile:
- In dem sehr seltenen Szenario, in dem Sie ein Array verkleinern möchten, bedeutet dies, dass das Array nicht kopiert werden muss, um seine Länge zu ändern. Es würde jedoch auch in Zukunft eine Heap-Komprimierung erfordern, die viel Kopieren erfordert.
- Wenn Sie Objektreferenzen im Array speichern, können Sie einige Vorteile aus der Cache-Lokalität ziehen, wenn die Zuordnung des Arrays und der Elemente zufällig zusammengeführt wird. Dies ist natürlich noch seltener als Pro # 1.
Nachteile:
- Jeder Array-Zugriff würde selbst in engen Schleifen schrecklich teuer werden. Also würde jeder
unsafestattdessen Code verwenden, und da geht Ihre Speichersicherheit.
- Jeder einzelne Code, der sich mit Arrays befasst, muss damit rechnen, dass sich die Länge des Arrays jederzeit ändern kann. Jeder einzelne Array-Zugriff würde ein benötigen
try ... catch (IndexOutOfRangeException), und jeder, der über ein Array iteriert, müsste in der Lage sein, mit der sich ändernden Größe umzugehen - jemals gefragt, warum Sie keine Elemente hinzufügen oder entfernen könnenList<T> Sie iterieren?
- Eine enorme Menge an Arbeit für das CLR-Team, die nicht für eine andere, wichtigere Funktion verwendet werden konnte.
Es gibt einige Implementierungsdetails, die dies noch weniger vorteilhaft machen. Am wichtigsten ist, dass .NET-Heap nichts mit malloc/ freepattern zu tun hat . Wenn wir den LOH ausschließen, verhält sich der aktuelle MS.NET-Heap ganz anders:
- Zuordnungen erfolgen immer von oben, wie in einem Stapel. Dies macht die Zuweisung im Gegensatz dazu fast so billig wie die Stapelzuweisung
malloc.
- Aufgrund der Zuordnungsmuster, um tatsächlich „frei“ Speicher, müssen Sie verdichten die Haufen nach tut eine Sammlung. Dadurch werden Objekte so verschoben, dass die freien Speicherplätze im Heap gefüllt werden. Dadurch wird die "Oberseite" des Heaps niedriger, sodass Sie mehr Objekte im Heap zuweisen oder einfach den Speicher für die Verwendung durch andere Anwendungen auf dem System freigeben können .
- Um die Cache-Lokalität beizubehalten (unter der Annahme, dass Objekte, die üblicherweise zusammen verwendet werden, auch nahe beieinander zugewiesen werden, was eine gute Annahme ist), muss möglicherweise jedes einzelne Objekt im Heap über dem freigegebenen Speicherplatz nach unten verschoben werden. Sie haben sich vielleicht eine Kopie eines 100-Byte-Arrays gespeichert, müssen dann aber trotzdem 100 MiB anderer Objekte verschieben.
Darüber hinaus bedeutet, wie Hans in seiner Antwort sehr gut erklärte, nur weil das Array kleiner ist, nicht unbedingt, dass aufgrund der Objektheader genügend Platz für ein kleineres Array in der gleichen Speichermenge vorhanden ist (denken Sie daran, wie .NET dafür ausgelegt ist Speichersicherheit? Die Kenntnis des richtigen Objekttyps ist ein Muss für die Laufzeit. Er weist jedoch nicht darauf hin, dass Sie das Array auch dann verschieben müssen, wenn Sie über genügend Speicher verfügen . Betrachten Sie ein einfaches Array:
ObjectHeader,1,2,3,4,5
Jetzt entfernen wir die letzten beiden Elemente:
OldObjectHeader;NewObjectHeader,1,2,3
Hoppla. Wir benötigen den alten Objektheader, um die Freiraumliste beizubehalten, da wir den Heap sonst nicht richtig komprimieren könnten. Nun könnte es möglich sein, den alten Objektheader über das Array hinaus zu verschieben, um die Kopie zu vermeiden, aber das ist noch eine weitere Komplikation. Dies stellt sich als ziemlich teures Feature für etwas heraus, das noöne jemals wirklich verwenden wird.
Und das ist alles noch in der verwalteten Welt. Mit .NET können Sie jedoch bei Bedarf auf unsicheren Code zurückgreifen - beispielsweise bei der Interaktion mit nicht verwaltetem Code. Wenn Sie Daten an eine native Anwendung übergeben möchten, haben Sie zwei Möglichkeiten: Sie können entweder das verwaltete Handle anheften, um zu verhindern, dass es erfasst und verschoben wird, oder Sie kopieren die Daten. Wenn Sie einen kurzen, synchronen Anruf tätigen, ist das Fixieren sehr billig (obwohl gefährlicher - der native Code hat keine Sicherheitsgarantien). Das Gleiche gilt beispielsweise für die Bearbeitung von Daten in einer engen Schleife wie bei der Bildverarbeitung - das Kopieren der Daten ist eindeutig keine Option. Wenn Sie Array.Resizedas vorhandene Array ändern dürfen , wird dies vollständig unterbrochenArray.Resize Sie müssen überprüfen, ob dem Array, dessen Größe Sie ändern möchten, ein Handle zugeordnet ist, und in diesem Fall eine Ausnahme auslösen.
Weitere Komplikationen, über die man viel schwerer nachdenken kann (Sie werden jede Menge Spaß daran haben, den Fehler zu verfolgen, der nur gelegentlich auftritt, wenn der Array.ResizeVersuch, die Größe eines Arrays zu ändern, das gerade passiert, festgesteckt wird Erinnerung).
Wie andere erklärt haben, ist nativer Code nicht viel besser. Sie müssen zwar nicht dieselben Sicherheitsgarantien einhalten (was ich nicht wirklich als Vorteil nutzen würde, aber na ja), aber es gibt immer noch Komplikationen in Bezug auf die Art und Weise, wie Sie Speicher zuweisen und verwalten. Wird aufgerufen realloc, um ein Array mit 10 Elementen und 5 Elementen zu erstellen? Nun, es wird entweder kopiert oder es wird immer noch die Größe eines Arrays mit 10 Elementen haben, da es keine Möglichkeit gibt, den verbleibenden Speicher auf vernünftige Weise zurückzugewinnen.
Um es kurz zusammenzufassen: Sie fragen nach einer sehr teuren Funktion, die in einem äußerst seltenen Szenario nur von sehr begrenztem Nutzen ist (falls vorhanden) und für die es eine einfache Problemumgehung gibt (Erstellen einer eigenen Array-Klasse). . Ich sehe nicht, dass die Messlatte für "Sicher, lassen Sie uns diese Funktion implementieren!" :) :)
mallocundreallocmanchmalreallocObjekte, manchmal nicht. Einer der Gründe dafür ist manchmal, dass der Speichermanager über einen separaten Pool für kleine Speicherblöcke verfügt. Wenn Sie einen großen Speicherblock auf einen kleinen reduzieren, muss er möglicherweise in den separaten Pool verschoben werden. Aber ich kann nicht sagen, ob es für .NET dasselbe ist.