2016-08-27 6 views
3

は、我々が設計時の型の安全性のためのいくつかの非常に素晴らしいソリューション持ってパフォーマンス:(!で起動すると無暗黙の変換)型の別名とシングルケース構造体組合:C#のマーカー構造物は、F#では

​​

C#の代替手段は何ですか?

私はマーカー構造物の実用的な使い方(単一の要素を含む)を見たことがないが、それは我々が明示的型変換を追加した場合、我々は、F#でのエイリアスを入力することは非常に似たデザイン時の動作を得ることができるようになります。つまり、IDEは型の不一致について不満を持ち、明示的に値をキャストする必要があります。

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); 
    } 
} 

ので、IDEのものがいいです:

以下は、いくつかのPOCコードです!しかし、私はに何がパフォーマンスの影響と他の弱点であるのかと尋ねるつもりでした。そして、「ちょうどそれを測定する」というコメントはちょうどそれを測定し、この質問を最初に書くのを止めました...しかし、結果は直感に反していました:

[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]; 
} 

驚くべきことに、[email protected] x64の上/ Offsetでケースをリリースラウンドすべてのテストに目に見えて速くなる - 典型的な値は次のとおりです。

Int64: 1046 
Offset: 932 
Direct: 730 

私はちょうどint64を使用する場合と比較して同等か、より遅い結果を期待します。だからここには何が起こっているのですか?同じ違いを再現するか、いくつかの欠点を見つけることができますか?私は別のものを測定する場合ですか?

+0

あなたは生成されたILを見ましたか? – thumbmunkeys

+0

@thumbmunkeys、はい、明らかに 'Offset'ではop_explicitメソッドを呼び出し、より多くの行を持っています。しかし、それ以外のコードは同じです。結果が再現可能であれば、いくつかのJIT特異性があると思われる。 –

+0

結果は "真の" 64ビット(32ビットオフを推奨)とは少し異なりますが、一般的にパフォーマンスは気にしません。単一のプリミティブメンバーをラッピングしている 'struct'は実際にはメンバー。 –

答えて

6

をに置き換えると、パフォーマンスは直接テストと同じになります。

intそのようなx86-64の命令を生成し使用している間:

inc   ecx 
cmp   ecx,0F4240h 

longを使用している間、それはそのようなx86-64の命令を生成:

inc   rcx 
cmp   rcx,0F4240h 

したがって、32ビットのレジスタを使用する際の唯一の違いをecxまたはその64ビットバージョンrcxであり、後でCPUの設計により高速になります。

2.オフセットテストのイテレータにlongを使用すると、同様のパフォーマンスが表示されます。

コードがリリースモードで最適化されているので、Int64又はOffsetを使用してほとんど差、しかし、いくつかの点で命令を少し再配置されているがあります。

(1つの命令未満)オフセット使用しながら:

movsxd  rdx,eax 
movsxd  r8,r14d 
cmp   rdx,r8 
jae   <address> 

Int64型(複数つの命令)を使用している間:

movsxd  rdx,r14d 
movsxd  r8,eax 
cmp   r8,rdx 
jae   <address> 
movsxd  rdx,eax 

それがないので、直接テストは、最速#3で上に示した命令で配列の境界チェックをしないでください。あなたはこのようなループを記述する場合、この最適化が行われます。

for (var i=0; i<array.Length; i++) { ... array[i] ... } 

通常、あなたのインデックスが配列の境界の外にある場合、それはIndexOutOfRangeExceptionをスローしますが、この場合には、コンパイラはそれが起こることができないことを知っているので、それは省略されています小切手

他のテストで余分な命令があっても、必要な場合は事前に命令を実行し始めるCPU分岐予測器のために類似のパフォーマンスを持ち、条件が満たされない場合は結果を破棄します。

+0

これはとても素晴らしいです!ありがとう! :) –