FWIW Penso che i problemi descritti qui si riducano al fatto che il compilatore C# è più intelligente e rende efficiente una macchina a stati modello per gestire il codice asincrono, mentre il compilatore F # crea una miriade di oggetti e chiamate di funzione che sono generalmente meno efficienti.Prestazioni di asincrona in F # rispetto a C# (esiste un modo migliore per scrivere asincrona {...})
Comunque, se ho la funzione C# di seguito:
public async static Task<IReadOnlyList<T>> CSharpAsyncRead<T>(
SqlCommand cmd,
Func<SqlDataReader, T> createDatum)
{
var result = new List<T>();
var reader = await cmd.ExecuteReaderAsync();
while (await reader.ReadAsync())
{
var datum = createDatum(reader);
result.Add(datum);
}
return result.AsReadOnly();
}
E poi convertire questo a F # come segue:
let fsharpAsyncRead1 (cmd:SqlCommand) createDatum = async {
let! reader =
Async.AwaitTask (cmd.ExecuteReaderAsync())
let rec readRows (results:ResizeArray<_>) = async {
let! readAsyncResult = Async.AwaitTask (reader.ReadAsync())
if readAsyncResult then
let datum = createDatum reader
results.Add datum
return! readRows results
else
return results.AsReadOnly() :> IReadOnlyList<_>
}
return! readRows (ResizeArray())
}
poi scopro che le prestazioni del codice F # è notevolmente più lento e più affamati di CPU, rispetto alla versione C#. Mi stavo chiedendo se fosse meglio comporlo. Ho provato a rimuovere la funzione ricorsiva come segue (che è apparso un po 'brutto con il no po' e non lasciare mutevole s!):
let fsharpAsyncRead2 (cmd:SqlCommand) createDatum = async {
let result = ResizeArray()
let! reader =
Async.AwaitTask (cmd.ExecuteReaderAsync())
let! moreData = Async.AwaitTask (reader.ReadAsync())
let mutable isMoreData = moreData
while isMoreData do
let datum = createDatum reader
result.Add datum
let! moreData = Async.AwaitTask (reader.ReadAsync())
isMoreData <- moreData
return result.AsReadOnly() :> IReadOnlyList<_>
}
Ma la prestazione è stata fondamentalmente la stessa.
Come esempio delle prestazioni, quando stavo caricando una barra dei dati di mercato, quali:
type OHLC = {
Time : DateTime
Open : float
High : float
Low : float
Close : float
}
Sulla mia macchina, la versione # asincrona F preso ~ il doppio del tempo, e consumò ~ due volte tanto Risorse della CPU per tutto il tempo in cui è stata eseguita, quindi circa 4 volte il numero di risorse (vale a dire internamente che deve generare più thread?).
(Forse è un po 'discutibile fare una lettura di una struttura così banale? Sto solo spiando la macchina per vedere cosa fa. Rispetto alla versione non asincrona C# uno completa in ~ stesso tempo, ma consuma> il doppio della CPU, cioè il diritto Read() consuma < 1/8 delle risorse f #)
Quindi la mia domanda è, mentre sto facendo il F # async il "giusto" modo (questo è stato il mio primo tentativo di utilizzo)?
(... e se io sono, poi fare ho solo bisogno di andare a modificare il compilatore per aggiungere una macchina a stati implementazione basata per Asyncs compilati ... quanto sia difficile potrebbe essere che :-))
Sei sicuro che entrambi corrano con le stesse impostazioni di testimone, ottimizzazione e così via? –
Hai guardato 'hopac' (https://github.com/Hopac/Hopac)? ha un sovraccarico significativo della CPU inferiore rispetto a 'Async' (e IIRC inferiore a' Task'). FYI; Personalmente non considererei l'uso di 'Async' o' TPL' e così via quando ho bisogno del parallelismo. Se sono legato alla CPU, molto probabilmente non posso supportare le astrazioni. – FuleSnabel
@FuleSnabel BTW, ho preso in considerazione Hopac perché è effettivamente più veloce di 'async {}'. Ma l'interoperabilità con C# (mancanza di) era un dealbreaker. L'espressione di calcolo 'task {}' offre un modo per usare TPL (che è probabilmente il pezzo più ottimizzato di .NET) nel modo idiomatico F #. –