Skip to content

Commit f104383

Browse files
committed
[benchmark] RomanNumeral pared back down
Remove the extended benchmark coverage to leave only bug-fixed version of `RomanNumeral` benchmark with the new name `Roman.Substring.startsWith.dropFirst`.
1 parent 0edd560 commit f104383

File tree

1 file changed

+3
-279
lines changed

1 file changed

+3
-279
lines changed

benchmark/single-source/RomanNumbers.swift

Lines changed: 3 additions & 279 deletions
Original file line numberDiff line numberDiff line change
@@ -13,87 +13,18 @@
1313
import TestsUtils
1414

1515
// Mini benchmark implementing roman numeral conversions to/from integers.
16-
// Measures performance of String/Substring/UTF8View with very short string
17-
// arguments and methods: hasPrefix/starts(with:), removeFirst/dropFirst and
18-
// String.append().
19-
//
20-
// For comparison, there's one extra variant with character based parsing
21-
// algorithm: `Roman.DictCharInt.map.reduce`.
16+
// Measures performance of Substring.starts(with:), dropFirst and String.append
17+
// with very short string arguments.
2218

2319
let t: [BenchmarkCategory] = [.api, .String, .algorithm]
24-
let N = 270 // 1100
20+
let N = 270
2521

2622
public let RomanNumbers = [
27-
// Imperative style variants:
28-
// String permuatations
29-
BenchmarkInfo(
30-
name: "Roman.String.hasPrefix.removeFirst",
31-
runFunction: {
32-
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanShPrF:)) },
33-
tags: t),
34-
BenchmarkInfo(
35-
name: "Roman.String.hasPrefix.dropFirst",
36-
runFunction: {
37-
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanShPdF:)) },
38-
tags: t),
39-
BenchmarkInfo(
40-
name: "Roman.String.startsWith.dropFirst",
41-
runFunction: {
42-
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanSsWdF:)) },
43-
tags: t),
44-
BenchmarkInfo(
45-
name: "Roman.String.startsWith.removeFirst",
46-
runFunction: {
47-
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanSsWrF:)) },
48-
tags: t),
49-
// Substring permutations
50-
BenchmarkInfo(
51-
name: "Roman.Substring.hasPrefix.removeFirst",
52-
runFunction: {
53-
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanSShPrF:)) },
54-
tags: t),
55-
BenchmarkInfo(
56-
name: "Roman.Substring.hasPrefix.dropFirst",
57-
runFunction: {
58-
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanSShPdF:)) },
59-
tags: t),
6023
BenchmarkInfo(
6124
name: "Roman.Substring.startsWith.dropFirst",
6225
runFunction: {
6326
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanSSsWdF:)) },
6427
tags: t),
65-
BenchmarkInfo(
66-
name: "Roman.Substring.startsWith.removeFirst",
67-
runFunction: {
68-
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanSSsWrF:)) },
69-
tags: t),
70-
// UTF8View SubSequence
71-
BenchmarkInfo(
72-
name: "Roman.UTF8ViewSS.startsWith.dropFirst",
73-
runFunction: {
74-
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanU8SSsWdF:)) },
75-
tags: t),
76-
BenchmarkInfo(
77-
name: "Roman.UTF8ViewSS.startsWith.removeFirst",
78-
runFunction: {
79-
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanU8SSsWrF:)) },
80-
tags: t),
81-
// FP-style variants:
82-
BenchmarkInfo(
83-
name: "Roman.String.hasPrefix.dropFirst.RI",
84-
runFunction: {
85-
checkId($0, upTo: N, { $0.romanReduceInto }, Int.init(romanReduceInto:))},
86-
tags: t),
87-
BenchmarkInfo(
88-
name: "Roman.String.hasPrefix.dropFirst.R",
89-
runFunction: {
90-
checkId($0, upTo: N, { $0.romanReduce }, Int.init(romanReduce:)) },
91-
tags: t),
92-
BenchmarkInfo(
93-
name: "Roman.DictCharInt.map.reduce",
94-
runFunction: {
95-
checkId($0, upTo: N, { $0.romanNumeral }, Int.init(romanMapReduce:)) },
96-
tags: t),
9728
]
9829

9930
@inline(__always)
@@ -112,8 +43,6 @@ let romanTable: KeyValuePairs<String, Int> = [
11243
"I": 1,
11344
]
11445

115-
let romanTableUTF8 = romanTable.map { ($0.utf8, $1) }
116-
11746
extension BinaryInteger {
11847
// Imperative Style
11948
// See https://www.rosettacode.org/wiki/Roman_numerals/Encode#Swift
@@ -131,82 +60,6 @@ extension BinaryInteger {
13160
return result
13261
}
13362

134-
// String permutations (romanS)
135-
136-
init?(romanShPrF number: String) {
137-
self = 0
138-
var raw = number
139-
for (numeral, value) in romanTable {
140-
while raw.hasPrefix(numeral) {
141-
self += Self(value)
142-
raw.removeFirst(numeral.count)
143-
}
144-
}
145-
guard raw.isEmpty else { return nil }
146-
}
147-
148-
init?(romanShPdF number: String) {
149-
self = 0
150-
var raw = number
151-
for (numeral, value) in romanTable {
152-
while raw.hasPrefix(numeral) {
153-
self += Self(value)
154-
raw = String(raw.dropFirst(numeral.count))
155-
}
156-
}
157-
guard raw.isEmpty else { return nil }
158-
}
159-
160-
init?(romanSsWdF number: String) {
161-
self = 0
162-
var raw = number
163-
for (numeral, value) in romanTable {
164-
while raw.starts(with: numeral) {
165-
self += Self(value)
166-
raw = String(raw.dropFirst(numeral.count))
167-
}
168-
}
169-
guard raw.isEmpty else { return nil }
170-
}
171-
172-
init?(romanSsWrF number: String) {
173-
self = 0
174-
var raw = number
175-
for (numeral, value) in romanTable {
176-
while raw.starts(with: numeral) {
177-
self += Self(value)
178-
raw.removeFirst(numeral.count)
179-
}
180-
}
181-
guard raw.isEmpty else { return nil }
182-
}
183-
184-
// Substring permutations (romanSS)
185-
186-
init?(romanSShPrF number: String) {
187-
self = 0
188-
var raw = Substring(number)
189-
for (numeral, value) in romanTable {
190-
while raw.hasPrefix(numeral) {
191-
self += Self(value)
192-
raw.removeFirst(numeral.count)
193-
}
194-
}
195-
guard raw.isEmpty else { return nil }
196-
}
197-
198-
init?(romanSShPdF number: String) {
199-
self = 0
200-
var raw = Substring(number)
201-
for (numeral, value) in romanTable {
202-
while raw.hasPrefix(numeral) {
203-
self += Self(value)
204-
raw = raw.dropFirst(numeral.count)
205-
}
206-
}
207-
guard raw.isEmpty else { return nil }
208-
}
209-
21063
init?(romanSSsWdF number: String) {
21164
self = 0
21265
var raw = Substring(number)
@@ -218,133 +71,4 @@ extension BinaryInteger {
21871
}
21972
guard raw.isEmpty else { return nil }
22073
}
221-
222-
init?(romanSSsWrF number: String) {
223-
self = 0
224-
var raw = Substring(number)
225-
for (numeral, value) in romanTable {
226-
while raw.starts(with: numeral) {
227-
self += Self(value)
228-
raw.removeFirst(numeral.count)
229-
}
230-
}
231-
guard raw.isEmpty else { return nil }
232-
}
233-
234-
// UTF8View SubSequence
235-
236-
init?(romanU8SSsWdF number: String) {
237-
self = 0
238-
var raw = number.utf8[...]
239-
for (numeral, value) in romanTableUTF8 {
240-
while raw.starts(with: numeral) {
241-
self += Self(value)
242-
raw = raw.dropFirst(numeral.count)
243-
}
244-
}
245-
guard raw.isEmpty else { return nil }
246-
}
247-
248-
init?(romanU8SSsWrF number: String) {
249-
self = 0
250-
var raw = number.utf8[...]
251-
for (numeral, value) in romanTableUTF8 {
252-
while raw.starts(with: numeral) {
253-
self += Self(value)
254-
raw.removeFirst(numeral.count)
255-
}
256-
}
257-
guard raw.isEmpty else { return nil }
258-
}
259-
}
260-
261-
extension BinaryInteger {
262-
// FP-style
263-
// Following is a translation of the imperative algorithm into functional
264-
// style: for-in loop is replaced with reduction and while loop with recusion.
265-
// XXX: These functions are not tail call optimized... 🤷‍♂️ (ARC on Strings?)
266-
267-
typealias State = (number: String, value: Self)
268-
typealias Roman = (numeral: String, value: Int)
269-
270-
// Classic functional style with reduce
271-
272-
static func parseRomanNumeral(_ running: State, candidate r: Roman) -> State {
273-
guard running.number.hasPrefix(r.numeral) else { return running }
274-
return parseRomanNumeral((String(running.number.dropFirst(r.numeral.count)),
275-
running.value + Self(r.value)), candidate: r)
276-
}
277-
278-
static func buildRomanNumeral(_ running: State, candidate r: Roman) -> State {
279-
guard running.value >= r.value else { return running }
280-
return buildRomanNumeral(
281-
(running.number + r.numeral, running.value - Self(r.value)), candidate: r)
282-
}
283-
284-
var romanReduce: String {
285-
return romanTable.reduce(("", self), Self.buildRomanNumeral).number
286-
}
287-
288-
init?(romanReduce number: String) {
289-
let (remainder, value) = romanTable.reduce(
290-
(number, Self(0)), Self.parseRomanNumeral)
291-
guard remainder.isEmpty else { return nil }
292-
self = value
293-
}
294-
295-
// Swifty mutable hybrid functional style with reduce(into:)
296-
297-
static func parseRomanNumeral(_ running: inout State, candidate r: Roman) {
298-
guard running.number.hasPrefix(r.numeral) else { return }
299-
running.number = String(running.number.dropFirst(r.numeral.count))
300-
running.value += Self(r.value)
301-
parseRomanNumeral(&running, candidate: r)
302-
}
303-
304-
static func buildRomanNumeral(_ running: inout State, candidate r: Roman) {
305-
guard running.value >= r.value else { return }
306-
running.value -= Self(r.value)
307-
running.number += r.numeral
308-
buildRomanNumeral(&running, candidate: r)
309-
}
310-
311-
var romanReduceInto: String {
312-
return romanTable.reduce(into: ("", self), Self.buildRomanNumeral).number
313-
}
314-
315-
init?(romanReduceInto number: String) {
316-
let (remainder, value) = romanTable.reduce(into:
317-
(number, Self(0)), Self.parseRomanNumeral)
318-
guard remainder.isEmpty else { return nil }
319-
self = value
320-
}
321-
}
322-
323-
// Parsing with Dictionary and map reduce.
324-
// See `fromRoman2` https://www.rosettacode.org/wiki/Roman_numerals/Decode#Scala
325-
326-
let romanDigits: Dictionary<Character, Int> = [
327-
"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000
328-
]
329-
330-
extension BinaryInteger {
331-
typealias RunningSum = (sum: Self, last: Self)
332-
333-
static func sumRomanDigits(r: RunningSum?, digitValue: Self?) -> RunningSum? {
334-
switch (r, digitValue) {
335-
case let (r?, value?):
336-
return (r.sum + value - (r.last < value ? 2 * r.last : 0), value)
337-
default:
338-
return nil
339-
}
340-
}
341-
342-
init?(romanMapReduce number: String) {
343-
guard let r = (number
344-
.lazy // brings about 2x improvement over eager
345-
.map { romanDigits[$0].map { Self($0) } }
346-
.reduce((Self(0), Self(0)), Self.sumRomanDigits)
347-
) else { return nil }
348-
self = r.sum
349-
}
35074
}

0 commit comments

Comments
 (0)