Hallo Abt,
Zitat |
keine mega Genauigkeit
|
nint (Native sized integer) hat mit "Genauigkeit" nichts zu tun.
BTW: das wäre im Kontext von ML auch float / double und da ein float nur halb so groß ist wie eine double, passen in ein SIMD-Register doppelt so viele Werte und somit ist es (ganz grob) doppelt so schnell.
nint ist wirklich low-level auf Assembly-Ebene. Ohne allzu tief in diesem Thread auf die Materie eingehen zu wollen nur kurz ein Beispiel für x64.
Mit
int indizierte Zugriffe Arrays schauen (vereinfacht, ohne bound-checks) in Assembly-Code so aus:
movsxd rax, r8d
mov eax, ptr [rdx+rax]
Störend dabei ist das
movsxd, welches das Register
r8d, das den Index vom Typ
int hält, auf die CPU-Wortbreite "sign extended", also erweitert und das Vorzeichen berücksichtigt. Das ist korrekter Code, aber nicht unbedingt nötig und kann bei sehr vielen Iterationen schon etwas ausmachen.
Möglich wäre jetzt statt dem
int einfach
long nehmen, da dies 64bit groß ist und somit der CPU-Wortgröße entspricht.
So Problem gelöst. Oder doch nicht? .NET soll "überall" laufen können, also auch auf x86 und da wäre es wieder
int.
Genau hier setzt
nint an. Es ist unabhängig von der Plattform der Integertyp mit der CPU-Wortbreite. Sinnbildlich kann die "Implementierung" von
nint so dargestellt werden:
#if 64 bit
using nint = System.Int64; // long
#else
using nint = System.Int32; // int
#endif
Der gleiche indizierte Zugriff (von oben) mit
nint statt
int würde folgenden Code erzeugen:
mov rax, r8
mov eax, ptr [rdx+rax]
Es ist also statt dem
movsxd "nur" ein
mov und moderne CPUs können dieses spezielle
mov sogar eliminieren (dank dem Register-Allocator mit der Register-Renaming Technik). Vllt. generiert der JIT (dank Peephole-Optimization) auch den "idealen" Code:
mov eax, ptr [rdx+r8]
Es geht dabei nicht nur um den "Austausch" von den CPU-Instruktionen, sondern auch um eine Verringerung der Maschinencode-Größe, welche sich v.a. in Schleifen (positiv) bemerkbar machen kann.
Ohne
nint (und Konsorten) musste bisher auf
IntPtr-Tricks od.
void*-Tricks zurückgegriffen werden. Unleserlicher und fehleranfälliger Code war die Folge. Durch
nint kann jetzt der C#-Compiler die nötigen Prüfungen durchführen.
Der netto Effekt / Gewinn schaut relativ gering aus.
Beispielweise profitieren von dieser Änderung / Möglichkeit in .NET so gut wie alle Span-Erweiterungsmethoden (SequenceEquals, IndexOf, ...) und in Folge auch alle String-Vergleichmethoden. Weiter oben im .NET Stack hat dies dann z.B. Vorteile in Kestrel, da es so mehr RPS gibt ;-)
Da du oben Interop zitiert hast, so stimmt es dort auch. In C weiß ja niemand so recht wie groß
int ist ;-), daher gibt es dort
int32_t und
int64_t mit fest definierter Größe. Weiters gibt es dort
size_t für den Integertyp mit CPU-Wortgröße.
Bisher gab es bei Interop ein Problem, wenn die native Signatur (aka Deklaration) beispielsweise wie folgt ist:
API_EXPORT void do_something(char* data, size_t index);
Wie sollte nun in C# das
DllImport aussehen?
int od.
long?
V.a. da DllImports statisch typisiert sein müssen, d.h. es kann zur Laufzeit keine andere Signatur verwendet werden.
Das Workaround war keine DllImports zu verwenden und stattdessen per
LoadLibrary und Konsorten zur Laufzeiten letztlich einen Delegaten zu erzeugen, welche je nach
Environment.Is64BitProcess eine andere Überladung verwendete. Alles umständlich.
Od. man verwendete einfach
int od.
long und hoffte dass es gut geht. Auch nicht sehr zielführend.
Hatte man die "native Seite" selbst in der Hand -- wie in .NET z.B. die PAL-Layer (platform abstraction) -- so wurde bewusst auf
size_t verzichtet und (nur)
int32_t verwendet.
Mit
nint gehören diese Probleme der Vergangenheit an.
Wie gesagt ist das alles sehr low-level und ein Großteil der C#-Programmierer wird sich darum wohl nie kümmern brauchen ;-)
mfG Gü