2015-02-21 6 views
8

üzerindedir:Performans ceza Generic.List <T> .Add bir fonksiyonu son ifadedir ve tailcall optimizasyon ben bu kodu aşağı haşlanmış ettik garip performans cezası yaşamanıza

[<Struct>] 
type Vector3(x: float32, y: float32, z: float32) = 
    member this.X = x 
    member this.Y = y 
    member this.Z = z 

type Data(n: int) = 
    let positions = System.Collections.Generic.List<Vector3>() 
    let add j = positions.Add (Vector3(j, j, j)) 
    let add1 j = positions.Add (Vector3(j, j, j));() 
    member this.UseAdd() = for i = 1 to n do add (float32 i) 
    member this.UseAdd1() = for i = 1 to n do add1 (float32 i) 

let timeIt name (f: unit -> unit) = 
    let timer = System.Diagnostics.Stopwatch.StartNew() 
    f() 
    printfn "%s: %ims" name (int timer.ElapsedMilliseconds) 

let test() = 
    for i = 1 to 3 do timeIt "ADD" (fun() -> Data(1000000).UseAdd()) 
    for i = 1 to 3 do timeIt "ADD1" (fun() -> Data(1000000).UseAdd1()) 

[<EntryPoint>] 
let main argv = 
    test() 
    0 

add ve add1 arasındaki fark, sondaki fazladan (). Ben add ve add1 aynı şekilde davranması gerektiğini beklenebilir

ADD: 461ms 
ADD: 457ms 
ADD: 450ms 
ADD1: 25ms 
ADD1: 26ms 
ADD1: 16ms 

List<T>.Add tipine yana T -> unit edilir: Ben .NET 4.5.1 F # 3.1 kullanarak x64 sürüm oluşturma olarak inşa

Bu çıktıyı almak .

ben add için (sadece ilgili kısmı dahil) derler olduğunu tespit ettik ildasm kullanma

IL_000a: newobj  instance void Program/Vector3::.ctor(float32, 
                  float32, 
                  float32) 
IL_000f: tail. 
IL_0011: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<valuetype Program/Vector3>::Add(!0) 

ise add1 "kuyruk çağrısı" olmadan

IL_000a: newobj  instance void Program/Vector3::.ctor(float32, 
                  float32, 
                  float32) 
IL_000f: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<valuetype Program/Vector3>::Add(!0) 

yani

içine. Yani kuyruk arama optimizasyonunu kapattığımda, hem add hem de add1 aynı hızda çalışır.

tail. yönergesi, işlev çağrısının neden bu kadar yavaş olmasına neden oluyor? Ayrıca, bu bir hata mı yoksa bir özellik mi?


DÜZENLEME: Bu orijinal kod burada, bu davranışı fark ettim. Sonunda true değeri düştüğünde, yukarıdaki kodla aynı performans düşüşünü gösterir.

let makeAtom (ctx: CleanCifContext) (element: CleanCifAtomSiteElement) = 
    let residue = getResidue ctx element 

    let position = 
    Vector3(float32 (element.PositionX.ValueOrFail()), float32 (element.PositionY.ValueOrFail()), float32 (element.PositionZ.ValueOrFail())) 
    let atom = 
    CifAtom(id = ctx.Atoms.Count, element = element.ElementSymbol.ValueOrFail(), 
      residue = residue, serialNumber = element.Id.ValueOrFail(), 
      name = element.Name.ValueOrFail(), authName = element.AuthName.Value(), altLoc = element.AltLoc.Value(), 
      occupancy = float32 (element.Occupancy.ValueOrFail()), tempFactor = float32 (element.TempFactor.ValueOrFail())) 

    ctx.Atoms.Add atom 
    ctx.Positions.Add position 
    true 
+0

İlginçtir, yalnızca .NET 4 + 'da oluşur ve tutarsızlık x86'da veya "List" içindeki diğer veri türlerini kullanırken daha küçüktür. –

+0

@DaxFohl Evet, x86'da da düşük olduğunu fark ettim.Ama kodumun 64-bit olması gerekiyor, bu yüzden bu veriyi ekledim. – Dave

+0

Bunu RyuJIT veya normal eski JIT'de mi çalıştırdınız? Bağladığınız makale eskisiyle ilgili gibi görünüyor. –

cevap

2

Ben sorunun nerede olduğunu çözdüm ve F # derleyicisi veya .NET benim sorunun yanlış anlama ziyade böcek neden.

kod

let add j = positions.Add (Vector3(j, j, j)) 

kabaca "değeri Vector3(j, j, j) ile List<T>.Add çağrı ardından unit dönüş" anlamına gelir

let add1 j = positions.Add (Vector3(j, j, j));() 

ise "değeri Vector3(j, j, j) ile tailcall konumundan List<T>.Add çağrı" anlamına gelir.

Tip-bilge, unit yüzden yanlış positions.Add denilen olsun üstlendi List<T>.Add getiri olarak hiçbir fark yoktur ve daha sonra addList<T>.Add dönüş değeridir değeri unit dönecekti. Bununla birlikte, http://blogs.msdn.com/b/clrcodegeneration/archive/2009/05/11/tail-call-improvements-in-net-framework-4.aspx numaralı belgede belirtildiği gibi, JIT, kuyruk çağrılan işlevin argümanları önemsiz olduğunda bazı "yığın büyüsü" gerçekleştirmelidir. Ve bu, performans boşluğunun nereden geldiğini gösteriyor. Fark çok ince, ama o var.