In F #, wir einige sehr schöne Lösungen für Design-Zeit Typsicherheit haben: (! Und keine impliziten Konvertierungen beginnen) Typ-Aliasnamen und Einzelfall struct union:C# Markerstrukturen leistung
// type aliases are erased at compile time
type Offset = int64<offset>
// no allocations
[<Struct>]
type Offset = Offset of int64
Was wäre eine Alternative für C#?
Ich habe noch nie eine praktische Verwendung von Markerstrukturen (die ein einzelnes Element), aber es sieht aus wie gesehen, wenn wir explizites Typkonvertierungen hinzufügen dann könnten wir Design-Zeit-Verhalten sehr ähnlich Typen Aliase in F # bekommen. Das heißt - IDE wird sich über Typinkongruenzen beschweren und man muss explizit Werte werfen.
Nachfolgend finden Sie einige POC-Code:
public struct Offset {
private readonly long _value;
private Offset(long value) {
_value = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Offset(long value) {
return new Offset(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator long(Offset offset) {
return offset._value;
}
}
public interface IIndex<T> {
Offset OffsetOf(T value);
T AtOffset(Offset offset);
}
public class SmapleUsage
{
public void Test(IIndex<long> idx)
{
// without explicit cast we have nice red squiggles
var valueAt = idx.AtOffset((Offset)123);
long offset = (long)idx.OffsetOf(42L);
}
}
Also, die IDE, was schön ist! Aber ich wollte fragen, was sind die Auswirkungen auf die Leistung und andere Nachteile, und zu vermeiden "nur messen" Kommentare haben es nur gemessen und aufgehört zu schreiben diese Frage zunächst ... Aber die Ergebnisse kamen kontraintuitiv:
[Test]
public void OffsetTests() {
var array = Enumerable.Range(0, 1024).ToArray();
var sw = new Stopwatch();
for (int rounds = 0; rounds < 10; rounds++) {
sw.Restart();
long sum = 0;
for (int rp = 0; rp < 1000000; rp++) {
for (int i = 0; i < array.Length; i++) {
sum += GetAtIndex(array, i);
}
}
sw.Stop();
if (sum < 0) throw new Exception(); // use sum after loop
Console.WriteLine($"Index: {sw.ElapsedMilliseconds}");
sw.Restart();
sum = 0;
for (int rp = 0; rp < 1000000; rp++) {
for (int i = 0; i < array.Length; i++) {
sum += GetAtOffset(array, (Offset)i);
}
}
if (sum < 0) throw new Exception(); // use sum after loop
sw.Stop();
Console.WriteLine($"Offset: {sw.ElapsedMilliseconds}");
sw.Restart();
sum = 0;
for (int rp = 0; rp < 1000000; rp++) {
for (int i = 0; i < array.Length; i++) {
sum += array[i];
}
}
if (sum < 0) throw new Exception(); // use sum after loop
sw.Stop();
Console.WriteLine($"Direct: {sw.ElapsedMilliseconds}");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetAtIndex(int[] array, long index) {
return array[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetAtOffset(int[] array, Offset offset) {
return array[(long)offset];
}
Überraschenderweise auf [email protected] x64/mit Offset
den Fall lösen ist sichtbar schneller auf jede Testrunde - typische Werte sind:
Int64: 1046
Offset: 932
Direct: 730
würde ich gleich oder langsames Ergebnis im Vergleich erwartet int64
nur verwenden. Also was geht hier hin? Können Sie den gleichen Unterschied reproduzieren oder einen Mangel feststellen, z. wenn ich verschiedene Dinge messe?
haben Sie sich die generierte IL angesehen? – thumbmunkeys
@thumbmunkeys, ja, offensichtlich für 'Offset' ruft es die op_explicit Methoden auf und hat mehr Zeilen - so macht es aus IL Perspektive mehr Arbeit. Aber ansonsten ist der Code derselbe. Wahrscheinlich einige JIT-Besonderheiten, wenn das Ergebnis reproduzierbar ist. –
Die Ergebnisse sind ein bisschen anders für "echte" 64-Bit (lieber 32-Bit aus), aber im Allgemeinen würde ich nicht für die Leistung stören - eine "Struktur", die ein einzelnes primitives Mitglied wickelt, sollte wirklich das gleiche wie die Verwendung sein das Mitglied. –