|
| 1 | +import Foundation |
| 2 | +@testable import BSText |
| 3 | + |
| 4 | +public struct BenchmarkResult { |
| 5 | + let name: String |
| 6 | + let iterations: Int |
| 7 | + let totalTime: TimeInterval |
| 8 | + let averageTime: TimeInterval |
| 9 | + let minTime: TimeInterval |
| 10 | + let maxTime: TimeInterval |
| 11 | +} |
| 12 | + |
| 13 | +public class BSTextBenchmark { |
| 14 | + |
| 15 | + public static let shared = BSTextBenchmark() |
| 16 | + |
| 17 | + private init() {} |
| 18 | + |
| 19 | + public func measure(name: String, iterations: Int = 100, block: () throws -> Void) rethrows -> BenchmarkResult { |
| 20 | + var times: [TimeInterval] = [] |
| 21 | + times.reserveCapacity(iterations) |
| 22 | + |
| 23 | + for _ in 0..<iterations { |
| 24 | + let start = Date() |
| 25 | + try block() |
| 26 | + let end = Date() |
| 27 | + let time = end.timeIntervalSince(start) |
| 28 | + times.append(time) |
| 29 | + } |
| 30 | + |
| 31 | + let totalTime = times.reduce(0, +) |
| 32 | + let averageTime = totalTime / Double(iterations) |
| 33 | + let minTime = times.min() ?? 0 |
| 34 | + let maxTime = times.max() ?? 0 |
| 35 | + |
| 36 | + return BenchmarkResult( |
| 37 | + name: name, |
| 38 | + iterations: iterations, |
| 39 | + totalTime: totalTime, |
| 40 | + averageTime: averageTime, |
| 41 | + minTime: minTime, |
| 42 | + maxTime: maxTime |
| 43 | + ) |
| 44 | + } |
| 45 | + |
| 46 | + public func printResult(_ result: BenchmarkResult) { |
| 47 | + print(""" |
| 48 | + ===== Benchmark: \(result.name) ===== |
| 49 | + Iterations: \(result.iterations) |
| 50 | + Total Time: \(String(format: "%.4f", result.totalTime))s |
| 51 | + Average Time: \(String(format: "%.6f", result.averageTime))s |
| 52 | + Min Time: \(String(format: "%.6f", result.minTime))s |
| 53 | + Max Time: \(String(format: "%.6f", result.maxTime))s |
| 54 | + """) |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +public class BSTextMemoryMonitor { |
| 59 | + |
| 60 | + public static func getMemoryUsage() -> UInt64? { |
| 61 | + var info = mach_task_basic_info() |
| 62 | + var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4 |
| 63 | + |
| 64 | + let result = withUnsafeMutablePointer(to: &info) { |
| 65 | + $0.withMemoryRebound(to: integer_t.self, capacity: 1) { |
| 66 | + task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count) |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + guard result == KERN_SUCCESS else { |
| 71 | + return nil |
| 72 | + } |
| 73 | + |
| 74 | + return info.resident_size |
| 75 | + } |
| 76 | + |
| 77 | + public static func formatMemory(_ bytes: UInt64) -> String { |
| 78 | + let kb = Double(bytes) / 1024.0 |
| 79 | + let mb = kb / 1024.0 |
| 80 | + let gb = mb / 1024.0 |
| 81 | + |
| 82 | + if gb >= 1 { |
| 83 | + return String(format: "%.2f GB", gb) |
| 84 | + } else if mb >= 1 { |
| 85 | + return String(format: "%.2f MB", mb) |
| 86 | + } else if kb >= 1 { |
| 87 | + return String(format: "%.2f KB", kb) |
| 88 | + } else { |
| 89 | + return String(format: "%d bytes", bytes) |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + public static func measureMemory(name: String, block: () throws -> Void) rethrows { |
| 94 | + let before = getMemoryUsage() ?? 0 |
| 95 | + print("Before \(name): \(formatMemory(before))") |
| 96 | + |
| 97 | + try block() |
| 98 | + |
| 99 | + let after = getMemoryUsage() ?? 0 |
| 100 | + print("After \(name): \(formatMemory(after))") |
| 101 | + |
| 102 | + let diff = Int64(after) - Int64(before) |
| 103 | + if diff > 0 { |
| 104 | + print("Memory increase: +\(formatMemory(UInt64(diff)))") |
| 105 | + } else if diff < 0 { |
| 106 | + print("Memory decrease: \(formatMemory(UInt64(abs(diff))))") |
| 107 | + } else { |
| 108 | + print("No memory change") |
| 109 | + } |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +public class BSTextProfilingHelper { |
| 114 | + |
| 115 | + public static func profileTextViewPerformance() { |
| 116 | + print("\n===== BSText Performance Benchmarks =====\n") |
| 117 | + |
| 118 | + let benchmark = BSTextBenchmark.shared |
| 119 | + |
| 120 | + let textViewResult = benchmark.measure(name: "BSTextView Initialization", iterations: 1000) { |
| 121 | + _ = BSTextView(frame: CGRect(x: 0, y: 0, width: 320, height: 200)) |
| 122 | + } |
| 123 | + benchmark.printResult(textViewResult) |
| 124 | + |
| 125 | + let textView = BSTextView(frame: CGRect(x: 0, y: 0, width: 320, height: 200)) |
| 126 | + let largeText = (0..<1000).map { "Line \($0): Test content\n" }.joined() |
| 127 | + |
| 128 | + let setTextResult = benchmark.measure(name: "BSTextView Set Text", iterations: 100) { |
| 129 | + textView.text = largeText |
| 130 | + } |
| 131 | + benchmark.printResult(setTextResult) |
| 132 | + |
| 133 | + let sizeThatFitsResult = benchmark.measure(name: "BSTextView Size That Fits", iterations: 100) { |
| 134 | + _ = textView.sizeThatFits(CGSize(width: 320, height: CGFloat.greatestFiniteMagnitude)) |
| 135 | + } |
| 136 | + benchmark.printResult(sizeThatFitsResult) |
| 137 | + } |
| 138 | + |
| 139 | + public static func profileMarkdownPerformance() { |
| 140 | + print("\n===== Markdown Performance Benchmarks =====\n") |
| 141 | + |
| 142 | + let benchmark = BSTextBenchmark.shared |
| 143 | + let parser = BSTextMarkdownParser() |
| 144 | + |
| 145 | + let simpleMarkdown = "# Heading\n**Bold** *italic*\n- List item\n- Another item" |
| 146 | + let simpleResult = benchmark.measure(name: "Simple Markdown Parsing", iterations: 1000) { |
| 147 | + _ = parser.parse(simpleMarkdown) |
| 148 | + } |
| 149 | + benchmark.printResult(simpleResult) |
| 150 | + |
| 151 | + let largeMarkdown = (0..<100).map { |
| 152 | + "# Heading \($0)\n**Bold** *italic* `code`\n- List item 1\n- List item 2\n\n" |
| 153 | + }.joined() |
| 154 | + |
| 155 | + let largeResult = benchmark.measure(name: "Large Markdown Parsing", iterations: 100) { |
| 156 | + _ = parser.parse(largeMarkdown) |
| 157 | + } |
| 158 | + benchmark.printResult(largeResult) |
| 159 | + } |
| 160 | + |
| 161 | + public static func profileSyntaxPerformance() { |
| 162 | + print("\n===== Syntax Highlighting Performance Benchmarks =====\n") |
| 163 | + |
| 164 | + let benchmark = BSTextBenchmark.shared |
| 165 | + let parser = BSTextSyntaxParser() |
| 166 | + |
| 167 | + let simpleCode = """ |
| 168 | + let x = 5 |
| 169 | + func hello() { |
| 170 | + print("world") |
| 171 | + } |
| 172 | + """ |
| 173 | + |
| 174 | + parser.language = .swift |
| 175 | + let simpleSwiftResult = benchmark.measure(name: "Simple Swift Syntax Highlighting", iterations: 1000) { |
| 176 | + _ = parser.parse(simpleCode) |
| 177 | + } |
| 178 | + benchmark.printResult(simpleSwiftResult) |
| 179 | + |
| 180 | + let largeCode = (0..<50).map { |
| 181 | + """ |
| 182 | + func function\($0)(param: String) -> String { |
| 183 | + let result = "Result: \\(param)" |
| 184 | + return result |
| 185 | + } |
| 186 | + """ |
| 187 | + }.joined(separator: "\n\n") |
| 188 | + |
| 189 | + let largeSwiftResult = benchmark.measure(name: "Large Swift Syntax Highlighting", iterations: 100) { |
| 190 | + _ = parser.parse(largeCode) |
| 191 | + } |
| 192 | + benchmark.printResult(largeSwiftResult) |
| 193 | + } |
| 194 | + |
| 195 | + public static func profileMemoryUsage() { |
| 196 | + print("\n===== Memory Usage Benchmarks =====\n") |
| 197 | + |
| 198 | + BSTextMemoryMonitor.measureMemory(name: "Create BSTextView") { |
| 199 | + _ = BSTextView(frame: CGRect(x: 0, y: 0, width: 320, height: 200)) |
| 200 | + } |
| 201 | + |
| 202 | + BSTextMemoryMonitor.measureMemory(name: "Create Large Text Content") { |
| 203 | + let textView = BSTextView(frame: CGRect(x: 0, y: 0, width: 320, height: 200)) |
| 204 | + textView.text = (0..<10000).map { "Line \($0)\n" }.joined() |
| 205 | + } |
| 206 | + } |
| 207 | +} |
0 commit comments