While loop pattern in Async builder allocates a lot
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 876
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 131
Description
**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:
```
[]
[]
[]
[]
type Benchs() =
[]
member val Length = 0 with get, set
[]
member x.Run() =
async {
let mutable i = 0
while i < x.Length do
i <- i + 1
return i
} |> Async.StartAsTask
```
Result:
``` ini
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
Contributor guide
Assessment
This issue has not been assessed yet.