Skip to content

Commit 4398811

Browse files
committed
Perf improvement ~2.5x for String.mapi, see dotnet#9390 (comment) for details.
1 parent 91ca1d4 commit 4398811

File tree

2 files changed

+20
-8
lines changed

2 files changed

+20
-8
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,4 @@ msbuild.binlog
133133
/tests/fsharp/regression/5531/compilation.output.test.txt
134134
/tests/fsharp/core/fsfromfsviacs/compilation.langversion.old.output.txt
135135
/tests/fsharp/core/fsfromfsviacs/compilation.errors.output.txt
136+
*.user

src/fsharp/FSharp.Core/string.fs

+19-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ namespace Microsoft.FSharp.Core
1212
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
1313
[<RequireQualifiedAccess>]
1414
module String =
15+
[<CompiledName("Length")>]
16+
let length (str:string) = if isNull str then 0 else str.Length
17+
1518
[<CompiledName("Concat")>]
1619
let concat sep (strings : seq<string>) =
1720
String.Join(sep, strings)
@@ -40,13 +43,24 @@ namespace Microsoft.FSharp.Core
4043

4144
[<CompiledName("MapIndexed")>]
4245
let mapi (mapping: int -> char -> char) (str:string) =
43-
if String.IsNullOrEmpty str then
46+
let len = length str
47+
if len = 0 then
4448
String.Empty
4549
else
46-
let res = StringBuilder str.Length
47-
let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(mapping)
48-
str |> iteri (fun i c -> res.Append(f.Invoke(i, c)) |> ignore)
49-
res.ToString()
50+
let result = str.ToCharArray()
51+
let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt mapping
52+
53+
// x2 unrolled loop gives 10-20% boost, overall 2.5x SB perf
54+
let mutable i = 0
55+
while i < len - len % 2 do
56+
result.[i] <- f.Invoke(i, result.[i])
57+
result.[i + 1] <- f.Invoke(i, result.[i + 1])
58+
i <- i + 2
59+
60+
if i % 2 = 1 then
61+
result.[i] <- f.Invoke(i, result.[i])
62+
63+
new String(result)
5064

5165
[<CompiledName("Filter")>]
5266
let filter (predicate: char -> bool) (str:string) =
@@ -101,6 +115,3 @@ namespace Microsoft.FSharp.Core
101115
else
102116
let rec check i = (i < str.Length) && (predicate str.[i] || check (i+1))
103117
check 0
104-
105-
[<CompiledName("Length")>]
106-
let length (str:string) = if isNull str then 0 else str.Length

0 commit comments

Comments
 (0)