damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit c996e5f8b3b827eb1091aaa2954eea3fab968172
parent e9c1671d06f74c38bc62348f84db353b48ec2a38
Author: William Casarin <jb55@jb55.com>
Date:   Thu,  6 Mar 2025 10:42:10 -0800

perf: don't use regex in trim_{prefix,suffix}

regex is overkill for this, and performance is quite bad

Fixes: b131c74ee367 ("Add prefix and suffix string trimming functions")
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4++++
Mdamus/Models/NoteContent.swift | 12++++++++++--
AdamusTests/Benchmarking.swift | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 86 insertions(+), 2 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0A3F92280F66F5000448DE /* ReplyMap.swift */; }; 4C0C03992A61E27B0098B3B8 /* primal.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C0C03972A61E27B0098B3B8 /* primal.wasm */; }; 4C0C039A2A61E27B0098B3B8 /* bool_setting.wasm in Resources */ = {isa = PBXBuildFile; fileRef = 4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */; }; + 4C0ED07F2D7A1E260020D8A2 /* Benchmarking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0ED07E2D7A1E260020D8A2 /* Benchmarking.swift */; }; 4C1253502A76C5B20004F4B8 /* UnfollowedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C12534F2A76C5B20004F4B8 /* UnfollowedNotify.swift */; }; 4C1253522A76C6130004F4B8 /* ComposeNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253512A76C6130004F4B8 /* ComposeNotify.swift */; }; 4C1253542A76C7D60004F4B8 /* LogoutNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C1253532A76C7D60004F4B8 /* LogoutNotify.swift */; }; @@ -1925,6 +1926,7 @@ 4C0A3F92280F66F5000448DE /* ReplyMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyMap.swift; sourceTree = "<group>"; }; 4C0C03972A61E27B0098B3B8 /* primal.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = primal.wasm; path = nostrscript/primal.wasm; sourceTree = SOURCE_ROOT; }; 4C0C03982A61E27B0098B3B8 /* bool_setting.wasm */ = {isa = PBXFileReference; lastKnownFileType = file; name = bool_setting.wasm; path = nostrscript/bool_setting.wasm; sourceTree = SOURCE_ROOT; }; + 4C0ED07E2D7A1E260020D8A2 /* Benchmarking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Benchmarking.swift; sourceTree = "<group>"; }; 4C12534F2A76C5B20004F4B8 /* UnfollowedNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnfollowedNotify.swift; sourceTree = "<group>"; }; 4C1253512A76C6130004F4B8 /* ComposeNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeNotify.swift; sourceTree = "<group>"; }; 4C1253532A76C7D60004F4B8 /* LogoutNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoutNotify.swift; sourceTree = "<group>"; }; @@ -3772,6 +3774,7 @@ 4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */, D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */, 3A96E3FD2D6BCE3800AE1630 /* RepostedTests.swift */, + 4C0ED07E2D7A1E260020D8A2 /* Benchmarking.swift */, ); path = damusTests; sourceTree = "<group>"; @@ -5002,6 +5005,7 @@ 4C9B0DEE2A65A75F00CBDA21 /* AttrStringTestExtensions.swift in Sources */, 4C19AE552A5D977400C90DB7 /* HashtagTests.swift in Sources */, D72927AD2BAB515C00F93E90 /* RelayURLTests.swift in Sources */, + 4C0ED07F2D7A1E260020D8A2 /* Benchmarking.swift in Sources */, 3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */, D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */, 3AAC7A022A60FE72002B50DF /* LocalizationUtilTests.swift in Sources */, diff --git a/damus/Models/NoteContent.swift b/damus/Models/NoteContent.swift @@ -257,12 +257,20 @@ func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText // trim suffix whitespace and newlines func trim_suffix(_ str: String) -> String { - return str.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression) + var result = str + while result.last?.isWhitespace == true { + result.removeLast() + } + return result } // trim prefix whitespace and newlines func trim_prefix(_ str: String) -> String { - return str.replacingOccurrences(of: "^\\s+", with: "", options: .regularExpression) + var result = str + while result.first?.isWhitespace == true { + result.removeFirst() + } + return result } struct LongformContent { diff --git a/damusTests/Benchmarking.swift b/damusTests/Benchmarking.swift @@ -0,0 +1,72 @@ +// +// Benchmarking.swift +// damusTests +// +// Created by William Casarin on 3/6/25. +// + +import Testing +import XCTest +@testable import damus + +class BenchmarkingTests: XCTestCase { + + // Old regex-based implementations for comparison + func trim_suffix_regex(_ str: String) -> String { + return str.replacingOccurrences(of: "\\s+$", with: "", options: .regularExpression) + } + + func trim_prefix_regex(_ str: String) -> String { + return str.replacingOccurrences(of: "^\\s+", with: "", options: .regularExpression) + } + + // Test strings with different characteristics + lazy var testStrings: [String] = [ + " Hello World ", // Simple whitespace + " \n\t Hello World \n\t ", // Mixed whitespace + String(repeating: " ", count: 1000) + "Hello", // Large prefix + "Hello" + String(repeating: " ", count: 1000), // Large suffix + String(repeating: " ", count: 500) + "Hello" + String(repeating: " ", count: 500) // Both + ] + + func testTrimSuffixRegexPerformance() throws { + measure { + for str in testStrings { + _ = trim_suffix_regex(str) + } + } + } + + func testTrimSuffixNewPerformance() throws { + measure { + for str in testStrings { + _ = trim_suffix(str) + } + } + } + + func testTrimPrefixRegexPerformance() throws { + measure { + for str in testStrings { + _ = trim_prefix_regex(str) + } + } + } + + func testTrimPrefixNewPerformance() throws { + measure { + for str in testStrings { + _ = trim_prefix(str) + } + } + } + + func testTrimFunctionCorrectness() throws { + // Verify that both implementations produce the same results + for str in testStrings { + XCTAssertEqual(trim_suffix(str), trim_suffix_regex(str), "New trim_suffix implementation produces different results") + XCTAssertEqual(trim_prefix(str), trim_prefix_regex(str), "New trim_prefix implementation produces different results") + } + } +} +