Skip to content

Commit 127e4ac

Browse files
authored
Change Async.Sequence to use StartImmediateAsTask (fsprojects#517)
1 parent c290e60 commit 127e4ac

File tree

6 files changed

+174
-4
lines changed

6 files changed

+174
-4
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,6 @@ tests/FSharpPlusFable.Tests/node_modules/
213213
tests/FSharpPlusFable.Tests/FSharpPlusFable
214214
/node_modules
215215
*.fs.js
216+
217+
tests/Benchmarks/BenchmarkDotNet.Artifacts
218+
tests/benchmarks/BenchmarkDotNet.Artifacts

FSharpPlus.sln

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpLib", "tests\CSharpLi
9595
EndProject
9696
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpPlusFable.Tests", "tests\FSharpPlusFable.Tests\FSharpPlusFable.Tests.fsproj", "{1CCD1BFB-60E4-40AA-B534-3C5EEE5E1E83}"
9797
EndProject
98+
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Benchmarks", "tests\benchmarks\Benchmarks.fsproj", "{EEFF08EB-8B0C-4F63-9425-4281EFF12087}"
99+
EndProject
98100
Global
99101
GlobalSection(SolutionConfigurationPlatforms) = preSolution
100102
Debug|Any CPU = Debug|Any CPU
@@ -158,6 +160,14 @@ Global
158160
{1CCD1BFB-60E4-40AA-B534-3C5EEE5E1E83}.Fable|Any CPU.Build.0 = Fable|Any CPU
159161
{1CCD1BFB-60E4-40AA-B534-3C5EEE5E1E83}.Fable3|Any CPU.ActiveCfg = Fable3|Any CPU
160162
{1CCD1BFB-60E4-40AA-B534-3C5EEE5E1E83}.Fable3|Any CPU.Build.0 = Fable3|Any CPU
163+
{EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
164+
{EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Debug|Any CPU.Build.0 = Debug|Any CPU
165+
{EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Release|Any CPU.ActiveCfg = Release|Any CPU
166+
{EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Release|Any CPU.Build.0 = Release|Any CPU
167+
{EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Fable|Any CPU.ActiveCfg = Debug|Any CPU
168+
{EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Fable|Any CPU.Build.0 = Debug|Any CPU
169+
{EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Fable3|Any CPU.ActiveCfg = Debug|Any CPU
170+
{EEFF08EB-8B0C-4F63-9425-4281EFF12087}.Fable3|Any CPU.Build.0 = Debug|Any CPU
161171
EndGlobalSection
162172
GlobalSection(SolutionProperties) = preSolution
163173
HideSolutionNode = FALSE
@@ -170,6 +180,7 @@ Global
170180
{9B93F5E5-3D53-42F1-96E2-06E6A7B496A0} = {81F5F559-FD23-4E90-9EE6-3E2A2C1A7F96}
171181
{7A5B766E-8141-4D8A-B3EB-91422FDBDF71} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4}
172182
{1CCD1BFB-60E4-40AA-B534-3C5EEE5E1E83} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4}
183+
{EEFF08EB-8B0C-4F63-9425-4281EFF12087} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4}
173184
EndGlobalSection
174185
GlobalSection(ExtensibilityGlobals) = postSolution
175186
SolutionGuid = {789B5FFA-7891-4F60-831E-42C3C5ED2C51}

src/FSharpPlus/Extensions/Extensions.fs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ module Extensions =
6767
#if !FABLE_COMPILER
6868
/// Combine all asyncs in one, chaining them in sequence order.
6969
static member Sequence (t:seq<Async<_>>) : Async<seq<_>> = async {
70+
let startImmediateAsTask ct a =
71+
Async.StartImmediateAsTask(a, ct).Result
72+
7073
let! ct = Async.CancellationToken
71-
return seq {
72-
use enum = t.GetEnumerator ()
73-
while enum.MoveNext() do
74-
yield Async.RunSynchronously (enum.Current, cancellationToken = ct) }}
74+
return t |> Seq.map (startImmediateAsTask ct) }
7575
#endif
7676

7777
/// Combine all asyncs in one, chaining them in sequence order.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
module AsyncSequences
2+
3+
open BenchmarkDotNet.Attributes
4+
open System.Threading
5+
open System.Threading.Tasks
6+
7+
type AsyncSeqTaskState<'t> =
8+
| Idle
9+
| Ok of 't
10+
| Error of exn
11+
| Cancelled
12+
13+
let sequence0 (t: seq<Async<_>>) : Async<seq<_>> =
14+
async {
15+
let! ct = Async.CancellationToken
16+
17+
return
18+
seq {
19+
use enum = t.GetEnumerator()
20+
21+
while enum.MoveNext() do
22+
yield Async.RunSynchronously(enum.Current, cancellationToken = ct)
23+
}
24+
}
25+
26+
let sequence_StartImmediateAsTask (t: seq<Async<_>>) : Async<seq<_>> =
27+
async {
28+
let startImmediateAsTask ct a =
29+
Async.StartImmediateAsTask(a, ct).Result
30+
31+
let! ct = Async.CancellationToken
32+
return t |> Seq.map (startImmediateAsTask ct)
33+
}
34+
35+
let sequence_ManualResetEventSlim (t: seq<Async<_>>) : Async<seq<_>> =
36+
async {
37+
let startImmediateAsTask ct (a: Async<'t>) : 't =
38+
let mutable state =
39+
AsyncSeqTaskState<'t>.Idle
40+
41+
let mutex = new ManualResetEventSlim(false)
42+
43+
let setState newState =
44+
try
45+
state <- newState
46+
finally
47+
mutex.Set()
48+
49+
Async.StartWithContinuations(
50+
a,
51+
(fun k -> AsyncSeqTaskState<'t>.Ok k |> setState),
52+
(fun e -> AsyncSeqTaskState<'t>.Error e |> setState),
53+
(fun _ -> setState AsyncSeqTaskState<'t>.Cancelled),
54+
ct
55+
)
56+
57+
mutex.Wait()
58+
59+
match state with
60+
| Idle
61+
| Cancelled -> TaskCanceledException() |> raise
62+
| Ok res -> res
63+
| Error e -> raise e
64+
65+
let! ct = Async.CancellationToken
66+
return t |> Seq.map (startImmediateAsTask ct)
67+
}
68+
69+
let SyncSum = async {
70+
return 1 + 1
71+
}
72+
73+
let AsyncWith1SecSleep = async {
74+
do! Async.Sleep 1
75+
return 1 + 1
76+
}
77+
78+
let SyncAsyncSleepOverAsync = async {
79+
Async.RunSynchronously (Async.Sleep 5)
80+
return 1 + 1
81+
}
82+
83+
type Benchmarks() =
84+
[<Params(10, 100, 1000)>]
85+
member val public times = 0 with get, set
86+
87+
[<Params(1)>]
88+
member val public threads = 0 with get, set
89+
90+
[<GlobalSetup>]
91+
member self.GlobalSetup() =
92+
if self.threads > 0 then
93+
ThreadPool.SetMinThreads (self.threads, self.threads) |> ignore
94+
ThreadPool.SetMaxThreads (self.threads, self.threads) |> ignore
95+
96+
[<Benchmark(Baseline = true)>]
97+
member this.Base() =
98+
if this.threads = 1 then
99+
failwith "This function will fail with just one available thread."
100+
seq {
101+
for _ = 1 to this.times do
102+
yield SyncSum
103+
yield AsyncWith1SecSleep
104+
yield SyncAsyncSleepOverAsync }
105+
|> sequence0
106+
|> Async.RunSynchronously
107+
|> Seq.toArray
108+
109+
[<Benchmark>]
110+
member this.StartImmediateAsTask() =
111+
seq {
112+
for _ = 1 to this.times do
113+
yield SyncSum
114+
yield AsyncWith1SecSleep
115+
yield SyncAsyncSleepOverAsync }
116+
|> sequence_StartImmediateAsTask
117+
|> Async.RunSynchronously
118+
|> Seq.toArray
119+
120+
[<Benchmark>]
121+
member this.ManualResetEventSlim() =
122+
seq {
123+
for _ = 1 to this.times do
124+
yield SyncSum
125+
yield AsyncWith1SecSleep
126+
yield SyncAsyncSleepOverAsync }
127+
|> sequence_ManualResetEventSlim
128+
|> Async.RunSynchronously
129+
|> Seq.toArray

tests/benchmarks/Benchmarks.fsproj

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net6.0</TargetFramework>
4+
<OutputType>Exe</OutputType>
5+
<PlatformTarget>AnyCPU</PlatformTarget>
6+
<Optimize>true</Optimize>
7+
<Configuration>Release</Configuration>
8+
<IsPackable>false</IsPackable>
9+
</PropertyGroup>
10+
<PropertyGroup Condition="'$(Configuration)'=='Release'">
11+
<Tailcalls>true</Tailcalls>
12+
</PropertyGroup>
13+
<ItemGroup>
14+
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
15+
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.12.1" Condition="'$(OS)' == 'Windows_NT'" />
16+
</ItemGroup>
17+
<ItemGroup>
18+
<Compile Include="AsyncSequenceBenchmarks.fs" />
19+
<Compile Include="Program.fs" />
20+
</ItemGroup>
21+
</Project>

tests/benchmarks/Program.fs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
open AsyncSequences
2+
3+
[<EntryPoint>]
4+
let main _ =
5+
do BenchmarkDotNet.Running.BenchmarkRunner.Run<Benchmarks>() |> ignore
6+
0

0 commit comments

Comments
 (0)