Repro steps
The problem is in this code pattern:
async {
let mutable i = 0
while i < "some length" do
i <- i + 1
return i
}
Benchmark code:
[<SimpleJob(runtimeMoniker = RuntimeMoniker.NetCoreApp31, launchCount = 3, warmupCount = 3, targetCount = 5)>]
[<GcServer(true)>]
[<MemoryDiagnoser>]
[<MarkdownExporterAttribute.GitHub>]
type Benchs() =
[<Params(100, 200, 300, 400, 500, 1000, 2000, 3000, 10000)>]
member val Length = 0 with get, set
[<Benchmark>]
member x.Run() =
async {
let mutable i = 0
while i < x.Length do
i <- i + 1
return i
} |> Async.StartAsTask
Result:
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18363
Intel Core i7-3770K CPU 3.50GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.101
[Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT DEBUG
Job-VRTOUB : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
Runtime=.NET Core 3.1 Server=True IterationCount=5
LaunchCount=3 WarmupCount=3
| Method |
Length |
Mean |
Error |
StdDev |
Gen 0 |
Gen 1 |
Gen 2 |
Allocated |
| Run |
100 |
8.443 us |
1.0161 us |
0.9504 us |
0.2136 |
- |
- |
7.59 KB |
| Run |
200 |
19.149 us |
1.5007 us |
1.4037 us |
0.3662 |
- |
- |
13.94 KB |
| Run |
300 |
21.909 us |
3.3153 us |
3.1011 us |
0.5493 |
- |
- |
20.25 KB |
| Run |
400 |
29.473 us |
0.5453 us |
0.5101 us |
0.7324 |
- |
- |
26.55 KB |
| Run |
500 |
34.433 us |
1.2715 us |
1.1893 us |
0.9155 |
- |
- |
32.86 KB |
| Run |
1000 |
59.594 us |
2.5140 us |
2.3516 us |
1.7090 |
- |
- |
64.38 KB |
| Run |
2000 |
104.767 us |
4.0733 us |
3.8102 us |
3.5400 |
- |
- |
127.43 KB |
| Run |
3000 |
154.497 us |
2.4013 us |
2.2462 us |
5.1270 |
- |
- |
190.48 KB |
| Run |
10000 |
484.288 us |
19.7594 us |
18.4830 us |
17.0898 |
- |
- |
631.8 KB |
Essentially the problem is that the loop internally turns into this:

Expected behavior
Allocations shouldn't depend(?) on the number of loops.
Actual behavior
Allocations depend on the number of loops.
Known workarounds
Using recursion:
async {
let mutable i = 0
let rec while' () =
if i = "some length"
then i
else
i <- i + 1
while' ()
return while' ()
}
Related information
Using TaskBuilder.fs helps, but not that much:
https://gist.github.com/grishace/83f540cb299867e94145551931fcbcb1
.NET Core 3.1
Repro steps
The problem is in this code pattern:
Benchmark code:
Result:
Essentially the problem is that the loop internally turns into this:
Expected behavior
Allocations shouldn't depend(?) on the number of loops.
Actual behavior
Allocations depend on the number of loops.
Known workarounds
Using recursion:
Related information
Using
TaskBuilder.fshelps, but not that much:https://gist.github.com/grishace/83f540cb299867e94145551931fcbcb1
.NET Core 3.1