Skip to content

Commit 35e2caa

Browse files
authored
String map performance improvement (#9470)
* Simplify and improve perf of String.length * Improve performance of String.map * Revert "Simplify and improve perf of String.length" * Resolves #9470 (comment) * Lingering space * Change `String` to use `new` to clarify use of ctor * Add some better tests for String.map, add side-effect test * Add tests to ensure the mapping function is called a deterministically amount of times * Fix typo * Remove "foo" from String.map tests
1 parent 7b29d44 commit 35e2caa

File tree

2 files changed

+35
-7
lines changed

2 files changed

+35
-7
lines changed

src/fsharp/FSharp.Core/string.fs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,13 @@ namespace Microsoft.FSharp.Core
4242
if String.IsNullOrEmpty str then
4343
String.Empty
4444
else
45-
let res = StringBuilder str.Length
46-
str |> iter (fun c -> res.Append(mapping c) |> ignore)
47-
res.ToString()
45+
let result = str.ToCharArray()
46+
let mutable i = 0
47+
for c in result do
48+
result.[i] <- mapping c
49+
i <- i + 1
50+
51+
new String(result)
4852

4953
[<CompiledName("MapIndexed")>]
5054
let mapi (mapping: int -> char -> char) (str:string) =

tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/StringModule.fs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,35 @@ type StringModule() =
7171

7272
[<Test>]
7373
member this.Map() =
74-
let e1 = String.map (fun c -> c) "foo"
75-
Assert.AreEqual("foo", e1)
74+
let e1 = String.map id "xyz"
75+
Assert.AreEqual("xyz", e1)
7676

77-
let e2 = String.map (fun c -> c) null
78-
Assert.AreEqual("", e2)
77+
let e2 = String.map (fun c -> c + char 1) "abcde"
78+
Assert.AreEqual("bcdef", e2)
79+
80+
let e3 = String.map (fun c -> c) null
81+
Assert.AreEqual("", e3)
82+
83+
let e4 = String.map (fun c -> c) String.Empty
84+
Assert.AreEqual("", e4)
85+
86+
let e5 = String.map (fun _ -> 'B') "A"
87+
Assert.AreEqual("B", e5)
88+
89+
let e6 = String.map (fun _ -> failwith "should not raise") null
90+
Assert.AreEqual("", e6)
91+
92+
// this tests makes sure mapping function is not called too many times
93+
let mutable x = 0
94+
let e7 = String.map (fun _ -> if x > 2 then failwith "should not raise" else x <- x + 1; 'x') "abc"
95+
Assert.AreEqual(x, 3)
96+
Assert.AreEqual(e7, "xxx")
97+
98+
// side-effect and "order of operation" test
99+
let mutable x = 0
100+
let e8 = String.map (fun c -> x <- x + 1; c + char x) "abcde"
101+
Assert.AreEqual(x, 5)
102+
Assert.AreEqual(e8, "bdfhj")
79103

80104
[<Test>]
81105
member this.MapI() =

0 commit comments

Comments
 (0)