-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Split cache into read cache and write cache #1551
Conversation
I like the general idea, but |
Generated by 🚫 Danger |
Yes, but this |
lock.unlock() | ||
try json.write(to: url, atomically: true, encoding: .utf8) | ||
} | ||
|
||
internal func flushWriteCache() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flushWriteCacheToReadCache()
might be a clearer (but more verbose) name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise it isn't clear that this function is mutating readCache
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me - this method shouldn't be used often, so I'm cool with it being more verbose
I guess this brings us to whether or not we want to properly support arbitrary uses of |
Agreed, but I think we should be fine if we document this limitation. I think the performance win for |
I agree, but it should be clearly documented. |
Sure, will add that later and a changelog entry. Just wanted to get feedback on the general approach first 😊 |
guard !writeCache.isEmpty else { | ||
return | ||
} | ||
flushWriteCacheToReadCache() | ||
lock.lock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This lock()
(and subsequent unlock()
) can be removed if we maintain that:
readCache
can only be mutated influshWriteCacheToReadCache()
flushWriteCacheToReadCache()
can only be called fromsave()
save()
cannot be called concurrently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, we can make save()
thread-safe if we think about a way to support the unit tests. Maybe something like this
func flushWriteCacheToReadCache() {
readCache = merge()
}
func merge() -> [String: Any] {
// ...
}
func save() {
// ...
let cache = merge()
let json = toJSON(cache)
// ...
}
(please ignore the bad names and lack of details)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that's doable. Give it a try and see! 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! 💯
@@ -89,6 +87,8 @@ class LinterCacheTests: XCTestCase { | |||
private func cacheAndValidate(violations: [StyleViolation], forFile: String, configuration: Configuration, | |||
file: StaticString = #file, line: UInt = #line) { | |||
cache.cache(violations: violations, forFile: forFile, configuration: configuration) | |||
cache.flushWriteCacheToReadCache() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this test is the only reason that readCache
is mutable, how about making a method that returns a new LinterCache
instance from mergeCaches()
and using it here, and changing readCache
to immutable?
@@ -15,30 +15,35 @@ internal enum LinterCacheError: Error { | |||
} | |||
|
|||
public final class LinterCache { | |||
private var cache = [String: Any]() | |||
private var readCache = [String: Any]() | |||
private var writeCache = [String: Any]() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing readCache
and writeCache
from [String: Any]
to [String: [String: Any]]
boosted performance on my test.
Codecov Report
@@ Coverage Diff @@
## master #1551 +/- ##
=========================================
+ Coverage 83.75% 84.06% +0.3%
=========================================
Files 188 189 +1
Lines 9597 9726 +129
=========================================
+ Hits 8038 8176 +138
+ Misses 1559 1550 -9
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me other than one comment.
On linting Carthage's repository and hitting all the cache,
rc1:
swiftlint lint --enable-all-rules --quiet 13.27s user 1.22s system 128% cpu 11.245 total
swiftlint lint --enable-all-rules --quiet 14.30s user 1.27s system 197% cpu 7.900 total
swiftlint lint --enable-all-rules --quiet 5.72s user 1.06s system 143% cpu 4.737 total
👍
for (key, value) in writeCache { | ||
var filesCache = (cache[key] as? [String: Any]) ?? [:] | ||
for (file, fileCache) in (value as? [String: Any]) ?? [:] { | ||
var filesCache = cache[key] ?? [:] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If key
does not exist, value
can be inserted to cache[key]
directly.
Sorry, I was aware of it before but I forgot to write a comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but is it worth the extra complexity?
for (key, value) in writeCache {
if var filesCache = cache[key] {
for (file, fileCache) in value {
filesCache[file] = fileCache
}
cache[key] = filesCache
} else {
cache[key] = value
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
let cache = LinterCache() | ||
cache.fileManager = TestFileManager() | ||
return cache | ||
return LinterCache(fileManager: TestFileManager()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't need to be in a closure anymore.
@@ -49,12 +66,12 @@ public final class LinterCache { | |||
let configurationDescription = configuration.cacheDescription | |||
|
|||
lock.lock() | |||
var filesCache = (cache[configurationDescription] as? [String: Any]) ?? [:] | |||
var filesCache = (writeCache[configurationDescription]) ?? [:] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parens are surperfluous now.
CHANGELOG.md
Outdated
@@ -93,6 +93,10 @@ | |||
[Marcelo Fabri](https://github.com/marcelofabri) | |||
[#1504](https://github.com/realm/SwiftLint/issues/1504) | |||
|
|||
* Improve cache performance by splitting it into read cache and write cache. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just add your name & issue to the item introducing caching? We don't usually include fixes or improvements to unreleased things in the changelog.
🚢 |
Alternative fix for #1550 as suggested by @norio-nomura
This makes things slightly more complex, but it reduced by 50-70% the execution time when there's a cache available.