2011-12-31 10 views
10

इसलिए मैंने एफ # को देने का फैसला किया है और मैंने इसे ए # एल्गोरिदम को पोर्ट किया है जिसे मैंने सी # में लिखा है। एक बिंदु पर, मैंने देखा है कि रिलीज की तुलना में डिबग बिल्ड तेजी से चल रहा है। इसके बाद मैंने ऑप्टिमाइज़ेशन सेटिंग्स के साथ खेला और इन परिणामों को मिला:अनुकूलित कोड से गैर-अनुकूलित एफ # कोड तेज क्या हो सकता है?

बार 100000 रनों से एल्गोरिदम का कुल निष्पादन समय दिखाता है। मैं एफ # कंपाइलर का उपयोग कर रहा हूं जो विजुअल स्टूडियो 2010 एसपी 1 के साथ आता है। लक्ष्य मंच कोई सीपीयू है।

Opt off, tail calls off: 5.81s 
Opt off, tail calls on : 5.79s 
Opt on , tail calls off: 6.48s 
Opt on , tail calls on : 6.40s 

मैं वास्तव में इस हैरान हूँ - क्यों अनुकूलन कोड रन गति धीमी हो जाता है? एल्गोरिदम का सी # संस्करण इस व्यवहार को प्रदर्शित नहीं करता है (यह भी थोड़ा अलग तरीके से कार्यान्वित किया जाता है)

यहां एफ # कोड का एक अलग संस्करण है, यह एक एल्गोरिदम है जो अणुओं में पैटर्न पाता है। यह कोड जो इस एफ # प्रोग्राम पर निर्भर करता है वह एफ # में लिखा गया है।

namespace Motives 
    module Internal = 
    type Motive = 
     { ResidueSet: Set<Residue>; AtomSet: Set<IAtom> } 
     member this.Atoms : IAtom seq = 
     seq { 
      for r in this.ResidueSet do yield! r.Atoms 
      yield! this.AtomSet 
     } 
     static member fromResidues (residues : Residue seq) = residues |> Seq.fold (fun (m: Set<Residue>) r -> m.Add(r)) Set.empty |> fun rs -> { ResidueSet = rs; AtomSet = Set.empty } 
     static member fromAtoms (atoms : IAtom seq) = atoms |> Seq.fold (fun (m: Set<IAtom>) a -> m.Add(a)) Set.empty |> fun atoms -> { ResidueSet = Set.empty; AtomSet = atoms } 
     static member merge (m1: Motive) (m2: Motive) = { ResidueSet = Set.union m1.ResidueSet m2.ResidueSet; AtomSet = Set.union m1.AtomSet m2.AtomSet } 
     static member distance (m1: Motive) (m2: Motive) = Seq.min (seq { for a in m1.Atoms do for b in m2.Atoms -> a.Position.DistanceTo(b.Position) }) 

    type Structure with 
     static member fromMotive (m: Motive) (parent: IStructure) (addBonds: bool) : IStructure = 
     let atoms = AtomCollection.FromUniqueAtoms(m.Atoms) 
     let bonds = 
      match addBonds with 
      | true -> BondCollection.Create(atoms |> Seq.map (fun a -> parent.Bonds.[a]) |> Seq.concat) 
      | _ -> BondCollection.Empty 
     Structure.Create (parent.Id + "_" + atoms.[0].Id.ToString(), atoms, bonds) 

    // KDTree used for range queries 
    // AminoChains used for regex queries 
    type StructureContext = 
     { Structure: IStructure; KDTree: Lazy<KDAtomTree>; AminoChains: Lazy<(Residue array * string) list> } 
     static member create (structure: IStructure) = 
     match structure.IsPdbStructure() with 
     | false -> { Structure = structure; KDTree = Lazy.Create(fun() -> structure.Atoms.ToKDTree()); AminoChains = Lazy.CreateFromValue([]) } 
     | true -> 
      let aminoChains = new System.Func<(Residue array * string) list> (fun() -> 
      let residues = structure.PdbResidues() |> Seq.filter (fun r -> r.IsAmino) 
      residues 
      |> Seq.groupBy (fun r -> r.ChainIdentifier) 
      |> Seq.map (fun (k,rs) -> rs |> Array.ofSeq, String.concat "" (rs |> Seq.map (fun r -> r.ShortName))) 
      |> List.ofSeq) 
      { Structure = structure; KDTree = Lazy.Create(fun() -> structure.Atoms.ToKDTree()); AminoChains = Lazy.Create(aminoChains) } 

    // Remember the named motives from named patterns 
    type MatchContext = 
     { StructureContext: StructureContext; NamedMotives: Map<string, Motive> } 
     static member merge (c1: MatchContext) (c2: MatchContext) = 
     { StructureContext = c1.StructureContext; NamedMotives = c2.NamedMotives |> Map.fold (fun m k v -> m.Add(k,v)) c1.NamedMotives } 

    type MatchedMotive = Motive * MatchContext 

    type Pattern = 
     | EmptyPattern 
     | GeneratingPattern of (StructureContext -> MatchedMotive seq) 
     | ConstraintPattern of (MatchedMotive -> MatchedMotive option) * Pattern 
     static member matches (p: Pattern) (context: StructureContext) : MatchedMotive seq = 
     match p with 
     | GeneratingPattern generator -> generator context 
     | ConstraintPattern (transform, pattern) -> 
      Pattern.matches pattern context 
      |> Seq.choose (fun m -> transform m) 
     | _ -> Seq.empty 

    let ringPattern (names: string list) = 
     let fingerprint = 
     names 
     |> Seq.map (fun s -> ElementSymbol.Create(s).ToString()) 
     |> Seq.sort 
     |> String.concat "" 
     let generator (context: StructureContext) = 
     let rings = context.Structure.Rings().GetRingsByFingerprint(fingerprint) 
     rings |> Seq.map (fun r -> Motive.fromAtoms r.Atoms, { StructureContext = context; NamedMotives = Map.empty }) 
     GeneratingPattern generator 

    open Internal 

    type MotiveFinder (pattern: string) = 
    // I am using a hard coded pattern here for testing purposes 
    let pattern = ringPattern ["C"; "C"; "C"; "C"; "C"; "O"] 
    member this.Matches (structure: IStructure) = 
     Pattern.matches pattern (StructureContext.create structure) 
     |> Seq.map (fun (m, mc) -> Structure.fromMotive m mc.StructureContext.Structure false) 
     |> List.ofSeq 
     |> List.sortBy (fun s -> s.Atoms.[0].Id) 

/////////////////////////////////////////////////////////////////// 
// performance test 

let warmUp = (new MotiveFinder("")).Matches (StructureReader.ReadPdb(filename, computeBonds = true)) 
printfn "%i" (List.length warmUp) 

let structure = StructureReader.ReadPdb(filename, computeBonds = true) 
let stopWatch = System.Diagnostics.Stopwatch.StartNew() 
let nruns = 100000 
let result = 
    seq { 
    for i in 1 .. nruns do 
     yield (new MotiveFinder("")).Matches structure 
    } |> Seq.nth (nruns-1) 

stopWatch.Stop() 
printfn "Time elapsed: %f seconds" stopWatch.Elapsed.TotalSeconds 

EDIT2:

मैं लग सेट प्रकार के कार्यान्वयन के लिए समस्या को संकुचित होता है करने के लिए। पर अनुकूलन के साथ अनुकूलन बंद साथ

let stopWatch = System.Diagnostics.Stopwatch.StartNew() 
let runs = 1000000 
let result = 
    seq { 
    for i in 1 .. runs do 
     let setA = [ 1 .. (i % 10) + 5 ] |> Set.ofList 
     let setB = [ 1 .. (i % 10) + 5 ] |> Set.ofList 
     yield Set.union setA setB 
    } |> Seq.nth (runs - 1) 

stopWatch.Stop() 
printfn "Time elapsed: %f seconds" stopWatch.Elapsed.TotalSeconds 
printfn "%A" result 

मैं ~ 7.5s और ~ 8.0s:

इस कोड के लिए। अभी भी लक्ष्य = कोई भी CPU (और मेरे पास i7-860 प्रोसेसर है)।

EDIT3: और मैंने पिछले संपादन को पोस्ट करने के ठीक बाद मुझे लगा कि मुझे इसे केवल सूचियों पर आज़माएं।

तो के लिए

let stopWatch = System.Diagnostics.Stopwatch.StartNew() 
let runs = 1000000 
let result1 = 
    seq { 
    for i in 1 .. runs do 
     let list = [ 1 .. i % 100 + 5 ] 
     yield list 
    } |> Seq.nth (runs - 1) 

stopWatch.Stop() 
printfn "Time elapsed: %f seconds" stopWatch.Elapsed.TotalSeconds 
printfn "%A" result1 

मैं ~ ऑप्ट साथ 3s मिलता है। ऑप्ट के साथ बंद और ~ 3.5s। पर।

EDIT4:

अगर मैं seq बिल्डर को हटा दें और सिर्फ

let stopWatch = System.Diagnostics.Stopwatch.StartNew() 
let runs = 1000000 
let mutable ret : int list = [] 
for i in 1 .. runs do 
    let list = [ 1 .. i % 100 + 5 ] 
    ret <- list 

stopWatch1.Stop() 
printfn "Time elapsed: %f seconds" stopWatch.Elapsed.TotalSeconds 
printfn "%A" ret 

मैं ~ दोनों पर और बंद अनुकूलन के साथ 3s है। तो ऐसा लगता है कि सीईसी बिल्डर कोड को अनुकूलित करने में समस्या कहीं है।

अजीब पर्याप्त, मैं सी # में एक परीक्षण एप्लिकेशन लिखा है:

var watch = Stopwatch.StartNew(); 

int runs = 1000000; 

var result = Enumerable.Range(1, runs) 
    .Select(i => Microsoft.FSharp.Collections.ListModule.OfSeq(Enumerable.Range(1, i % 100 + 5))) 
    .ElementAt(runs - 1); 

watch.Stop(); 

Console.WriteLine(result); 
Console.WriteLine("Time: {0}s", watch.Elapsed.TotalSeconds); 

और कोड पर एफ # समाधान के रूप में लगभग दो बार के रूप में तेजी से चलाने के लिए होता है ~ 1.7s।

EDIT5:

मैं बात यह है कि धीमी चलाने अनुकूलित कोड उत्पन्न कर रहा है पता चला है जॉन हारॉप साथ चर्चा के आधार पर (मैं अभी भी क्यों यद्यपि पता नहीं है)।

दोनों अनुकूलित और गैर-अनुकूलित संस्करण में

अगर मैं

member this.Atoms : IAtom seq = 
    Seq.append (this.ResidueSet |> Seq.collect (fun r -> r.Atoms)) this.AtomSet 

को

member this.Atoms : IAtom seq = 
    seq { 
    for r in this.ResidueSet do yield! r.Atoms 
     yield! this.AtomSet 
    } 

से Motive.Atoms बदल तो कार्यक्रम चलाता है ~ 7.1s। जो seq संस्करण से धीमा है, लेकिन कम से कम संगत।

तो ऐसा लगता है कि एफ # संकलक सिर्फ गणना भाव अनुकूलित नहीं कर सकते और वास्तव में उन्हें इतना कोशिश कर रहा द्वारा धीमा हो जाता है।

+0

ऐसा लगता है कि संकलक बस कि विशिष्ट एल्गोरिथ्म के अनुकूलन में विफल रहता है। –

+1

@ निकलास: ठीक है, हाँ। लेकिन इसके लिए एक कारण होना चाहिए और मुझे आश्चर्य है कि यह क्या है ... – Dave

+1

काम कोड के बिना कुछ भी कहना असंभव है। आपका 'अवशेष' और 'IAtom' प्रकार अपरिभाषित हैं। आपकी 'संरचना' प्रकार की परिभाषा मान्य एफ # कोड भी प्रतीत नहीं होती है। क्या आप एक कामकाजी उदाहरण प्रदान कर सकते हैं? –

उत्तर

6

मैं आपके रैपर कोड और अंतिम उदाहरण को ऑप्टिमाइज़ेशन सक्षम के साथ थोड़ा धीमा चल रहा हूं लेकिन अंतर 10% से कम है और हालांकि, असंगत है, मुझे आश्चर्य नहीं है कि अनुकूलन कभी-कभी प्रदर्शन को कम कर सकता है।

मैं नोट करना चाहिए कि कोडिंग की अपनी शैली अनुकूलन के लिए कमरे का एक बहुत छोड़ देता है बल्कि पूरे स्रोत कोड के बिना यह संभव मेरी मदद करने के लिए के लिए अनुकूलित नहीं है।

let result1 = 
    seq { 
    for i in 1 .. runs do 
     let list = [ 1 .. i % 100 + 5 ] 
     yield list 
    } |> Seq.nth (runs - 1) 

जब इस छोटे, अधिक मुहावरेदार है और तेजी से परिमाण के आदेश:

let result1 = 
    Seq.init runs (fun i -> List.init ((i+1) % 100 + 5) ((+) 1)) 
    |> Seq.nth (runs - 1) 

संपादित

अपनी टिप्पणी में नीचे आप कहते हैं कि आप चाहते हैं आपका उदाहरण निम्नलिखित कोड का उपयोग करता है समारोह तर्क जो मामले में मुझे लगता है नहीं होगा कि Seq.nth आप के लिए यह कर देगा निष्पादित करने के लिए तो मैं एक for पाश बजाय प्रयोग करेंगे:

let mutable list = [] 
for i=1 to runs do 
    list <- List.init (i % 100 + 5) ((+) 1) 
list 

यह अभी भी 9 × मूल से तेज़ है।

+0

मैं कोडिंग शैली के बारे में समझता हूं, मैं एफ # के लिए बिल्कुल नया हूं। मैं आश्चर्यचकित था कि ऑप्टिमाइज़र कोड को उतना सरल था जितना कि वास्तव में धीरे-धीरे धीमा हो गया था जितना कम से कम अनुकूलित संस्करण के रूप में तेज़ी से चलाने के लिए लगाया गया था। – Dave

+0

भी ऐसा लगता है कि 'Seq.init च |> Seq.nth मैं' घ केवल वास्तव में समारोह 'एक बार f' निष्पादित करता है। और यह वास्तव में वह व्यवहार नहीं है जो मैं चाहता हूं ... – Dave

+0

मेरे prev के उदाहरण के रूप में। टिप्पणी: 'टी = रेफ 0 दें; x = Seq.init 1000 (मजेदार I -> टी: =! टी + i;! टी) |> Seq.nth 999; printfn "% ए% ए"! टी एक्स' प्रिंट 99 99 999।क्या मैं कुछ भूल रहा हूँ? – Dave

संबंधित मुद्दे