Laden...

Neues in C#9 - Records, Native Sized Numbers und mehr Syntax-Zucker..

Erstellt von Abt vor 3 Jahren Letzter Beitrag vor 3 Jahren 3.150 Views
Abt Themenstarter:in
16.792 Beiträge seit 2008
vor 3 Jahren
Neues in C#9 - Records, Native Sized Numbers und mehr Syntax-Zucker..

Finally hat Microsoft nun auch öffentlich die NEuerungen um C#9 bekannt gegeben.

Die wichtigste Neuerung sind: Records.

Records sind eine Mischung aus Klassen und Strukturen, die nur das nötigste beinhalten.
Im Entwicklersprachgebraucht wird auch von POCOs geredet - also reine Datenstrukturen.

Der erste Vorschlag war mal

public record NominalRecord
{
  public int Property { get; }
  public readonly int Field;
}

Nun sieht es aber danach aus, dass wir folgendes erhalten: Data Classes.
[Edit, gfoidl: 23.08.2020] Es schaut so aus als ob doch record bleiben wird.

public data class NominalRecord
{
  public int Property { get; }
  public readonly int Field;
}

C# bekommt vielen weiteren Syntax-Zucker wie zum Beispiel

Person person = new Person(123);

kann nun auch

Person person = new(123);

sein.

Mit Native-Sized Number Types kommt ein neuer Datentyp in die .NET Welt.
Der Datentyp soll vor allem für die effizientere Interop-Kommunikation verwendet werden.

Die gesamte Featureübersicht seht ihr auf https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md

6.910 Beiträge seit 2009
vor 3 Jahren

Hallo,

Mit Native-Sized Number Types kommt ein neuer Datentyp in die .NET Welt.
Der Datentyp soll vor allem für die effizientere Interop-Kommunikation verwendet werden.

Nicht nur, sondern v.a. für effizienteren low-level-code, da direkt mit der Wortgröße der CPU gearbeitet werden kann und so der JIT besseren Maschinencode generieren kann. Eine Menge an Hacks und Workarounds die bisher dazu nötig waren werden dadurch eine saubere typsichere Lösung ersetzt.
Motiviert ist dies u.a. durch die starke Verwendung innerhalb von .NET selbst und hier insbesondere bei Methoden die Span<byte> basiert werken und oftmals vektorisiert sind.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

6.910 Beiträge seit 2009
vor 3 Jahren

Hallo,

siehe dazu auch Welcome to C# 9.0 von Mads Torgersen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Abt Themenstarter:in
16.792 Beiträge seit 2008
vor 3 Jahren

Nicht nur, sondern v.a. für effizienteren low-level-code, da direkt mit der Wortgröße der CPU gearbeitet werden kann und so der JIT besseren Maschinencode generieren kann.

Also einer der Gründe, der in der Vergangenheit mal genannt wurde, war hier auch Machine Learning.
Hier ist oft keine mega Genauigkeit notwendig, aber dafür eine sehr hohe Geschwindigkeit.

Warum auch immer fehlt dieser doch nachvollziehbare Grund im Blogpost.

6.910 Beiträge seit 2009
vor 3 Jahren

Hallo Abt,

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ü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

T
2.219 Beiträge seit 2008
vor 3 Jahren

@gfoidl
Danke für die Erklärung, jetzt wird mir erst der Sinn klar.
Beim durchlesen des Github Issue konnte ich das nicht ganz verstehen wo der Einsatzzweck genau liegen sollte.

Ansonsten warte ich mal ab bis C# 9 zur Verfügung steht und schau mir mal die Einsatzmöglichkeiten im Detail an.
Die Neuerung mit dem data class Ansatz klingt einleuchten und dürfte bestimmt auch Einzug in Entity Framework und andere OR Mapper finden um die Entitäten Klassen von regulären Klasse zu trennen.
Zu mindest kann ich mir diesen Ansatz vorstellen, ob er dann auch kommt steht in den Sternen 😮

T-Virus

Developer, Developer, Developer, Developer....

99 little bugs in the code, 99 little bugs. Take one down, patch it around, 117 little bugs in the code.

C
1.214 Beiträge seit 2006
vor 3 Jahren

@gfoidl: ich muss mich ehrlich wundern, dass der Compiler dass nicht sowieso optimieren kann. Gibt es konkrete Gründe, die das verhindern?

P
441 Beiträge seit 2014
vor 3 Jahren

@Coder007: der Compiler kennt die Zielplattform nicht, deswegen kann er das nicht alleine.
Anders als bei z.B. C wird nicht Maschinencode erzeugt beim kompilieren von C#

6.910 Beiträge seit 2009
vor 3 Jahren

Hallo Coder007,

Gibt es konkrete Gründe, die das verhindern?

Wenn ich mich nicht täusche, so die ECMA-Spec auf der .NET beruht. Wenn im Code int steht, so muss letztlich auch int verwendet werden. D.h. weder der C#-Compiler noch der JIT-Compiler dürfen das weg-optimieren.

Nachfolgend meine rein persönliche Einschätzung.

Das könnte zwar beim JITen "aufgeweicht" werden, aber der JIT selbst hat wenig Zeit und Möglichkeiten den Programmfluss genau zu analysieren, denn es könnte sein, dass es tatsächlich ein int bleiben muss damit die Absicht vom Programmierer nicht falsch umgesetzt wird. Daher halte ich es auch für besser, wenn explizit angegeben wird dass es sich um ein nint handelt.
Optimierende C/C++ Compiler haben hier aufgrund der Vorab-Kompilierung mehr Möglichkeiten (und Zeit) und können auch mit int bzw. int32_t optimalen Maschinencode generieren.

Der JIT in .NET an sich ist eine super Sache, es gibt aber auch ein paar "Baustellen" die offen sind und daher suboptimalen Code erzeugen. Durch das Open-Sourcen von .NET wurden aber schon viele Baustellen behoben / verbessert.
Seitens MS geht es dabei aber immer um Priorisierung der offenen Punkte, somit wurde speziell für diesen Fall noch nichts / wenig für automatische Optimierungen umgesetzt. V.a. mit dem Hintergrund dass es mit nint eine explizite Variante gibt.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"