Skip to content

Commit

Permalink
Improved cache behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
nicklockwood committed Aug 8, 2018
1 parent be6fc19 commit 314afd9
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 36 deletions.
45 changes: 36 additions & 9 deletions Sources/CommandLine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,14 @@ func printHelp() {
""")
}

private func timeEvent(block: () throws -> Void) rethrows -> String {
func timeEvent(block: () throws -> Void) rethrows -> TimeInterval {
let start = CFAbsoluteTimeGetCurrent()
try block()
let time = round((CFAbsoluteTimeGetCurrent() - start) * 100) / 100 // round to nearest 10ms
return CFAbsoluteTimeGetCurrent() - start
}

private func formatTime(_ time: TimeInterval) -> String {
let time = round(time * 100) / 100 // round to nearest 10ms
return String(format: "%gs", time)
}

Expand Down Expand Up @@ -304,9 +308,9 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
print("inferring swiftformat options from source file(s)...")
var filesParsed = 0, formatOptions = FormatOptions.default, errors = [Error]()
let fileOptions = options.fileOptions ?? .default
let time = timeEvent {
let time = formatTime(timeEvent {
(filesParsed, formatOptions, errors) = inferOptions(from: inputURLs, options: fileOptions)
}
})
printWarnings(errors)
if filesParsed == 0 {
throw FormatError.parsing("failed to to infer options")
Expand Down Expand Up @@ -447,7 +451,7 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {

// Format the code
var filesWritten = 0, filesFailed = 0, filesChecked = 0
let time = timeEvent {
let time = formatTime(timeEvent {
var _errors = [Error]()
(filesWritten, filesFailed, filesChecked, _errors) = processInput(inputURLs,
andWriteToOutput: outputURL,
Expand All @@ -457,7 +461,7 @@ func processArguments(_ args: [String], in directory: String) -> ExitCode {
dryrun: dryrun,
cacheURL: cacheURL)
errors += _errors
}
})

if filesWritten == 0 {
if filesChecked == 0 {
Expand Down Expand Up @@ -518,6 +522,16 @@ func inferOptions(from inputURLs: [URL], options: FileOptions) -> (Int, FormatOp
return (filesParsed, inferFormatOptions(from: tokens), errors)
}

func computeHash(_ source: String) -> String {
var count = 0
var hash: UInt64 = 5381
for byte in source.utf8 {
count += 1
hash = 127 &* (hash & 0x00FF_FFFF_FFFF_FFFF) &+ UInt64(byte)
}
return "\(count)\(hash)"
}

func format(_ source: String, options: Options, verbose: Bool) throws -> String {
// Parse source
let originalTokens = tokenize(source)
Expand Down Expand Up @@ -597,14 +611,27 @@ func processInput(_ inputURLs: [URL],
if verbose {
print("formatting \(inputURL.path)")
}
var cacheHash: String?
var sourceHash: String?
if let cacheEntry = cache?[cacheKey], cacheEntry.hasPrefix(cachePrefix) {
cacheHash = String(cacheEntry[cachePrefix.endIndex...])
sourceHash = computeHash(input)
}
let output: String
if cache?[cacheKey] == cachePrefix + String(input.count) {
if let cacheHash = cacheHash, cacheHash == sourceHash {
output = input
if verbose {
print("-- no changes", as: .success)
}
} else {
output = try format(input, options: options, verbose: verbose)
if output != input {
sourceHash = nil
}
}
let cacheValue = cache.map { _ in
// Only bother computing this if cache is enabled
cachePrefix + (sourceHash ?? computeHash(output))
}
if outputURL != inputURL, (try? String(contentsOf: outputURL)) != output {
if !dryrun {
Expand All @@ -620,7 +647,7 @@ func processInput(_ inputURLs: [URL],
// No changes needed
return {
filesChecked += 1
cache?[cacheKey] = cachePrefix + String(output.count)
cache?[cacheKey] = cacheValue
}
}
if dryrun {
Expand All @@ -639,7 +666,7 @@ func processInput(_ inputURLs: [URL],
filesChecked += 1
filesFailed += 1
filesWritten += 1
cache?[cacheKey] = cachePrefix + String(output.count)
cache?[cacheKey] = cacheValue
}
} catch {
throw FormatError.writing("failed to write file \(outputURL.path), \(error)")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,104 +6,124 @@
<dict>
<key>PerformanceTests</key>
<dict>
<key>testCachedFormatting()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.022932</real>
<key>baselineIntegrationDisplayName</key>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testFormatting()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.82129</real>
<real>0.79811</real>
<key>baselineIntegrationDisplayName</key>
<string>1 Aug 2018 at 10:40:58</string>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testIndent()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.18496</real>
<real>0.1673</real>
<key>baselineIntegrationDisplayName</key>
<string>1 Aug 2018 at 10:40:58</string>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testInferring()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.36823</real>
<real>0.41674</real>
<key>baselineIntegrationDisplayName</key>
<string>1 Aug 2018 at 10:40:58</string>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testNumberFormatting()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.033598</real>
<real>0.03429</real>
<key>baselineIntegrationDisplayName</key>
<string>1 Aug 2018 at 10:40:58</string>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testRedundantSelf()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.03929</real>
<real>0.03939</real>
<key>baselineIntegrationDisplayName</key>
<string>1 Aug 2018 at 10:40:58</string>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testTokenizing()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.58491</real>
<real>0.57043</real>
<key>baselineIntegrationDisplayName</key>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testUncachedFormatting()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.59705</real>
<key>baselineIntegrationDisplayName</key>
<string>1 Aug 2018 at 10:40:58</string>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testWorstCaseFormatting()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>2.3938</real>
<real>2.3634</real>
<key>baselineIntegrationDisplayName</key>
<string>1 Aug 2018 at 10:40:58</string>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testWorstCaseIndent()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.34182</real>
<real>0.31771</real>
<key>baselineIntegrationDisplayName</key>
<string>1 Aug 2018 at 10:40:58</string>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testWorstCaseNumberFormatting()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.042647</real>
<real>0.04353</real>
<key>baselineIntegrationDisplayName</key>
<string>1 Aug 2018 at 10:40:58</string>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
<key>testWorstCaseRedundantSelf()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.19353</real>
<real>0.18684</real>
<key>baselineIntegrationDisplayName</key>
<string>1 Aug 2018 at 10:40:58</string>
<string>8 Aug 2018 at 15:43:52</string>
</dict>
</dict>
</dict>
Expand Down
59 changes: 59 additions & 0 deletions Tests/CommandLineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,63 @@ class CommandLineTests: XCTestCase {
range = match.upperBound ..< range.upperBound
}
}

// MARK: cache

func testHashIsFasterThanFormatting() throws {
let sourceFile = URL(fileURLWithPath: #file)
let source = try String(contentsOf: sourceFile, encoding: .utf8)
let hash = computeHash(source + ";")

let hashTime = timeEvent { _ = computeHash(source) == hash }
let formatTime = try timeEvent { _ = try format(source) }
XCTAssertLessThan(hashTime, formatTime)
}

func testCacheHit() throws {
let input = "let foo = bar"
XCTAssertEqual(computeHash(input), computeHash(input))
}

func testCacheMiss() throws {
let input = "let foo = bar"
let output = "let foo = bar\n"
XCTAssertNotEqual(computeHash(input), computeHash(output))
}

func testCachePotentialFalsePositive() throws {
let input = "let foo = bar;"
let output = "let foo = bar\n"
XCTAssertNotEqual(computeHash(input), computeHash(output))
}

func testCachePotentialFalsePositive2() throws {
let input = """
import Foo
import Bar
"""
let output = """
import Bar
import Foo
"""
XCTAssertNotEqual(computeHash(input), computeHash(output))
}

// MARK: end-to-end formatting

func testFormatting() {
CLI.print = { _, _ in }
let sourceDirectory = URL(fileURLWithPath: #file)
.deletingLastPathComponent().deletingLastPathComponent().path

#if swift(>=4.1.5)
let args = "."
#else
let args = ". --disable redundantSelf" // redundantSelf crashes Xcode 9.4 in debug mode
#endif

XCTAssertEqual(CLI.run(in: sourceDirectory, with: args), .ok)
}
}
27 changes: 21 additions & 6 deletions Tests/PerformanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ import SwiftFormat
import XCTest

class PerformanceTests: XCTestCase {
static let sourceDirectory = URL(fileURLWithPath: #file)
.deletingLastPathComponent().deletingLastPathComponent()

static let files: [String] = {
var files = [String]()
let inputURL = URL(fileURLWithPath: #file)
.deletingLastPathComponent().deletingLastPathComponent()

_ = enumerateFiles(withInputURL: inputURL) { url, _, _ in
_ = enumerateFiles(withInputURL: sourceDirectory) { url, _, _ in
return {
if let source = try? String(contentsOf: url) {
files.append(source)
Expand All @@ -63,7 +63,22 @@ class PerformanceTests: XCTestCase {
let files = PerformanceTests.files
let tokens = files.map { tokenize($0) }
measure {
_ = tokens.map { try! format($0, rules: FormatRules.default) }
_ = tokens.map { try! format($0) }
}
}

func testUncachedFormatting() {
CLI.print = { _, _ in }
measure {
XCTAssertEqual(CLI.run(in: PerformanceTests.sourceDirectory.path, with: ". --cache ignore"), .ok)
}
}

func testCachedFormatting() {
CLI.print = { _, _ in }
_ = CLI.run(in: PerformanceTests.sourceDirectory.path, with: ".") // warm the cache
measure {
XCTAssertEqual(CLI.run(in: PerformanceTests.sourceDirectory.path, with: "."), .ok)
}
}

Expand Down Expand Up @@ -95,7 +110,7 @@ class PerformanceTests: XCTestCase {
experimentalRules: true
)
measure {
_ = tokens.map { try! format($0, rules: FormatRules.default, options: options) }
_ = tokens.map { try! format($0, options: options) }
}
}

Expand Down
2 changes: 1 addition & 1 deletion format.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
if [[ -z "${TRAVIS}" ]]; then
CommandLineTool/swiftformat . --config .swiftformat --cache ignore
CommandLineTool/swiftformat . --cache ignore
fi

0 comments on commit 314afd9

Please sign in to comment.