Sto studiando come introdurre il parallelismo in un'applicazione per migliorare le prestazioni. In particolare, sto analizzando parallelamente i loop e le loro varianti e i miei esperimenti iniziali mostrano un sovraccarico significativo nell'utilizzo di raccolte dal namespace System.Collections.Concurrent
.
Scenario tipico
Vuoi eseguire un'iterazione su una raccolta in parallelo, eseguire alcuni calcoli per iterazione e quindi indirizzare / aggiungere i risultati in una raccolta appropriata in base ai dettagli dell'iterazione.
[Se non utilizzi le Concurrent Collections ottieni errori dovuti al solito multi-threading & problemi di sincronizzazione, quindi sembra che tu sia obbligato a utilizzare le raccolte simultanee.]
Sfortunatamente i miei test per lo più / spesso dimostrano che le soluzioni semplici a thread singolo sono più veloci a causa del sovraccarico dell'utilizzo delle Concurrent Collections - è prevedibile?
Conclusioni sperimentali
Dovresti eseguire una "grande" parte del lavoro per iterazione affinché le raccolte simultanee funzionino meglio di una singola soluzione con thread?
Esempio di codice
Come da richieste nei commenti:
open System.Collections.Generic
open System.Collections.Concurrent
open FSharp.Collections.ParallelSeq
type Data = {
Name:string
mutable Age:int
Description:string
}
[<EntryPoint>]
let main argv =
let xs = ResizeArray<Data>()
let n = 1000000
let rand = System.Random()
// create some random data
for i in 0..n do
let f1 = rand.NextDouble()
let f2 = rand.NextDouble()
let data = {Name = "My Name" + f1.ToString() + f2.ToString(); Age = 38; Description = "Happy"}
xs.Add(data)
// single-threaded example
let normalCollection = Dictionary<string,Data>()
let stopWatch = System.Diagnostics.Stopwatch()
stopWatch.Start()
xs |> Seq.iter (fun x ->
x.Age <- x.Age + rand.Next()
normalCollection.[x.Name] <- x
)
stopWatch.Stop()
printfn "Single Threaded: %A" stopWatch.Elapsed
System.GC.Collect(2)
// single-threaded + concurrent Collection example
let concurrentCollection = ConcurrentDictionary<string,Data>()
let stopWatch = System.Diagnostics.Stopwatch()
stopWatch.Start()
xs |> Seq.iter (fun x ->
x.Age <- x.Age + rand.Next()
concurrentCollection.[x.Name] <- x
)
stopWatch.Stop()
printfn "Single Threaded + Concurrent Collection: %A" stopWatch.Elapsed
concurrentCollection.Clear()
System.GC.Collect(2)
// multi-threaded example
let concurrentCollection = ConcurrentDictionary<string,Data>()
let stopWatch = System.Diagnostics.Stopwatch()
stopWatch.Start()
xs |> PSeq.iter (fun x ->
x.Age <- x.Age + rand.Next()
concurrentCollection.[x.Name] <- x
)
stopWatch.Stop()
printfn "Multi-Threaded: %A" stopWatch.Elapsed
// Functional Style
normalCollection.Clear()
System.GC.Collect(2)
let stopWatch = System.Diagnostics.Stopwatch()
stopWatch.Start()
let ys =
xs
|> PSeq.map (fun x -> {x with Age = x.Age + rand.Next()} )
|> Seq.iter (fun x -> normalCollection.[x.Name] <- x)
stopWatch.Stop()
printfn "Functional Style: %A" stopWatch.Elapsed
printfn "%A" argv
0 // return an integer exit code
Timing
Costante su più esecuzioni ...
n = 10.000 (set di dati di piccole dimensioni)
Single Threaded: 00:00:00.0048328
Single Threaded + Concurrent Collection: 00:00:00.0053433
Multi-Threaded: 00:00:00.0197919
Functional Style: 00:00:00.0073613
n = 10.000.000 (set di dati di grandi dimensioni)
Single Threaded: 00:00:04.0683213
Single Threaded + Concurrent Collection: 00:00:13.6469918
Multi-Threaded: 00:00:10.9768615
Functional Style: 00:00:04.6633076