-
-
Save michaeleisel/e2414e93a73601c7e1f99733f8f96a42 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Copyright (c) 2018 Michael Eisel. All rights reserved. | |
import Foundation | |
import QuartzCore | |
// import IkigaJSON | |
import ZippyJSON | |
import os | |
struct User2: Decodable, Equatable { | |
let firstName: String | |
let lastName: String | |
let email: String | |
let login: String | |
let passwordHash: String | |
let gender: String | |
let avatarUrl: String | |
let country: String | |
let city: String | |
let zipCode: Int | |
let phone: String | |
let isVip: Bool | |
let isFamous: Bool | |
} | |
let appleOn = true | |
struct Aa: Codable { | |
let b: Int | |
let c: [Float] | |
enum Keys: CodingKey { | |
case b | |
case c | |
} | |
init(from decoder: Decoder) throws { | |
let container = try decoder.container(keyedBy: Keys.self) | |
self.b = try container.decode(Int.self, forKey: .b) | |
self.c = try container.decode([Float].self, forKey: .c) | |
} | |
} | |
//try! ZippyJSONDecoder().decode(Aa.self, from: #"{"b": 1, "c": [2, 3]}"#.data(using: .utf8)!) | |
func dataFromFile(_ file: String) -> Data { | |
let url = Bundle.main.url(forResource: file, withExtension: "")! | |
return try! Data(contentsOf: url) | |
} | |
var shouldContinue = true | |
func standardDeviation(_ arr : [CFTimeInterval]) -> CFTimeInterval { | |
let length = CFTimeInterval(arr.count) | |
let avg = arr.reduce(0, {$0 + $1}) / length | |
let sumOfSquaredAvgDiff = arr.map { pow($0 - avg, 2.0)}.reduce(0, {$0 + $1}) | |
return sqrt(sumOfSquaredAvgDiff / length) | |
} | |
func threadTime() -> CFTimeInterval { | |
var tp: timespec = timespec() | |
if #available(macOS 10.12, *) { | |
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tp) | |
} else { | |
abort() | |
} | |
return Double(tp.tv_sec) + Double(tp.tv_nsec) / 1e9; | |
} | |
func time<T>(_ closure: () -> (T)) -> CFTimeInterval { | |
var start: CFTimeInterval = 0 | |
var end: CFTimeInterval = 0 | |
let _: Int = autoreleasepool { | |
start = threadTime() | |
let _ = closure() | |
end = threadTime() | |
return 0 | |
} | |
return end - start | |
} | |
func prettify(_ time: CFTimeInterval) -> String { | |
return String(format: "%.2e", time) | |
} | |
func profileSingle<T>(runAfter: Bool, _ closure: () -> (T)) -> CFTimeInterval { | |
var times: [CFTimeInterval] = [] | |
for _ in 0..<1 { | |
times.append(time(closure)) | |
} | |
allTimes += times | |
// print(times) | |
// summary(times) | |
if runAfter { | |
for _ in 0..<500 { | |
let _ = closure() | |
} | |
} | |
return times.reduce(0, +) / CFTimeInterval(times.count) | |
} | |
func profile<T>(runAfter: Bool, testClosure: () -> (T), appleClosure: () -> (T), otherClosure: () -> ()) { | |
/*print("Ikiga") | |
profile(runAfter: false, otherClosure)*/ | |
// print("Apple") | |
if appleOn { | |
let appleTime = profileSingle(runAfter: false, appleClosure) | |
// print("Jaunt") | |
let zippyTime = profileSingle(runAfter: false, testClosure) | |
print(appleTime / zippyTime, appleTime, zippyTime) | |
} else { | |
while true { | |
let _ = profileSingle(runAfter: false, testClosure) | |
} | |
} | |
} | |
func pp(_ time: CFTimeInterval) -> String { | |
return String(format: "%.2e", time) | |
} | |
func p(_ times: [CFTimeInterval], _ percent: Double) -> CFTimeInterval { | |
let idx = Int(Double(times.count) * percent / 100) | |
return times.sorted()[idx] | |
} | |
func summary(_ times: [CFTimeInterval]) { | |
let average = times.reduce(0, +) / CFTimeInterval(times.count) | |
print("\(pp(average)), \(pp(p(times, 25))), \(pp(p(times, 50))), \(pp(p(times, 75))), standard deviation: \(String(format: "%.02lf", standardDeviation(times) / average * 100))%, min: \(pp(times.min()!)), max: \(pp(times.max()!))") | |
} | |
var allTimes: [CFTimeInterval] = [] | |
var twitterData = dataFromFile("twitter.json") | |
var canadaData = dataFromFile("canada.json") | |
var githubData = dataFromFile("github_events.json") | |
var apacheData = dataFromFile("apache_builds.json") | |
// var data = canadaData | |
// var data = twitterData | |
// var data = githubData | |
// var data = apacheData | |
//typealias ToDecode = Twitter | |
// typealias ToDecode = canada | |
// typealias ToDecode = ghEvents | |
// typealias ToDecode = ApacheBuilds | |
/*let iDecoder = IkigaJSONDecoder() | |
//iDecoder.settings.keyDecodingStrategy = .convertFromSnakeCase | |
let ikigaBlock = {() -> TwitterPayload in | |
return try! iDecoder.decode(TwitterPayload.self, from: data) | |
}*/ | |
func eq(_ o1: Any, _ o2: Any) { | |
if let array1 = o1 as? Array<Any>, let array2 = o2 as? Array<Any> { | |
assert(array1.count == array2.count) | |
for (element1, element2) in zip(array1, array2) { | |
eq(element1, element2) | |
} | |
} else if let dictionary1 = o1 as? Dictionary<String, Any>, let dictionary2 = o2 as? Dictionary<String, Any> { | |
assert(dictionary1.count == dictionary2.count) | |
for key in dictionary1.keys.sorted() { | |
eq(dictionary1[key]!, dictionary2[key]!) | |
} | |
} else if let string1 = o1 as? String, let string2 = o2 as? String { | |
assert(string1 == string2) | |
} else if let int1 = o1 as? Int, let int2 = o2 as? Int { | |
assert(int1 == int2) | |
} else if let null1 = o1 as? NSNull, let null2 = o2 as? NSNull { | |
assert(null1 == null2) | |
} else if let bool1 = o1 as? Bool, let bool2 = o2 as? Bool { | |
assert(bool1 == bool2) | |
} else if let d1 = o1 as? Double, let d2 = o2 as? Double { | |
assert(d1 == d2) | |
} else { | |
} | |
} | |
func test<T: Equatable>(_ a: [T], _ b: [T]) { | |
if let zz = zip(a, b).first(where: { (x, y) in x != y }) { | |
print("\(zz)") | |
abort() | |
} | |
} | |
/*func runTests() { | |
let a = testBlock() | |
let b = appleBlock() | |
assert(a == b) | |
}*/ | |
extension DecodingError: Equatable { | |
public static func == (lhs: DecodingError, rhs: DecodingError) -> Bool { | |
switch lhs { | |
case .typeMismatch(let lType, let lContext): | |
if case let DecodingError.typeMismatch(rType, rContext) = rhs { | |
return lType == rType && rContext == lContext | |
} | |
case .valueNotFound(let lType, let lContext): | |
if case let DecodingError.valueNotFound(rType, rContext) = rhs { | |
return lType == rType && rContext == lContext | |
} | |
case .keyNotFound(let lKey, let lContext): | |
if case let DecodingError.keyNotFound(rKey, rContext) = rhs { | |
return keysEqual(lKey, rKey) && rContext == lContext | |
} | |
case .dataCorrupted(let lContext): | |
if case let DecodingError.dataCorrupted(rContext) = rhs { | |
return rContext == lContext | |
} | |
@unknown default: | |
return false | |
} | |
return false | |
} | |
} | |
func keysEqual(_ lhs: CodingKey, _ rhs: CodingKey) -> Bool { | |
return lhs.stringValue == rhs.stringValue || (lhs.intValue != nil && lhs.intValue == rhs.intValue) | |
} | |
fileprivate struct JSONKey : CodingKey { | |
public var stringValue: String | |
public var intValue: Int? | |
public init?(stringValue: String) { | |
self.stringValue = stringValue | |
self.intValue = nil | |
} | |
public init?(intValue: Int) { | |
self.stringValue = "\(intValue)" | |
self.intValue = intValue | |
} | |
public init(stringValue: String, intValue: Int?) { | |
self.stringValue = stringValue | |
self.intValue = intValue | |
} | |
fileprivate init(index: Int) { | |
self.stringValue = "Index \(index)" | |
self.intValue = index | |
} | |
fileprivate static let `super` = JSONKey(stringValue: "super")! | |
} | |
extension DecodingError.Context: Equatable { | |
public static func == (lhs: DecodingError.Context, rhs: DecodingError.Context) -> Bool { | |
let pathsEqual = lhs.codingPath.count == rhs.codingPath.count && zip(lhs.codingPath, rhs.codingPath).allSatisfy { (a, b) in | |
keysEqual(a, b) | |
} | |
return pathsEqual && lhs.debugDescription == rhs.debugDescription | |
} | |
} | |
public func benchmark<T: Decodable & Equatable>(type: T.Type, data: Data) { | |
//testDecoder.keyDecodingStrategy = .convertFromSnakeCase | |
//appleDecoder.keyDecodingStrategy = .convertFromSnakeCase | |
let appleDecoder = Foundation.JSONDecoder() | |
let testDecoder = ZippyJSONDecoder() | |
if #available(OSX 10.12, *) { | |
testDecoder.dateDecodingStrategy = .iso8601 | |
appleDecoder.dateDecodingStrategy = .iso8601 | |
} | |
let testBlock = {() -> T in | |
return try! testDecoder.decode(T.self, from: data) | |
} | |
//appleDecoder.keyDecodingStrategy = .convertFromSnakeCase | |
let appleBlock = {() -> T in | |
if appleOn { | |
return try! appleDecoder.decode(T.self, from: data) | |
} else { | |
abort() | |
} | |
} | |
if appleOn { | |
if (testBlock() != appleBlock()) { | |
fatalError() | |
} | |
} | |
print(String(describing: T.self)) | |
for _ in 0..<1 { | |
profile(runAfter: true, testClosure: testBlock, appleClosure: appleBlock, otherClosure: {}) | |
} | |
usleep(UInt32(1e5)) | |
//summary(allTimes) | |
} | |
// Running just one of these vs. all of them can impact the benchmarks for Apple's decoder (not sure why) | |
struct Zz: Decodable { | |
let a: Int | |
} | |
var users10 = dataFromFile("users10.json") | |
var users100 = dataFromFile("users100.json") | |
var users1000 = dataFromFile("users1000.json") | |
//print(try! ZippyJSONDecoder().decode(Zz.self, from: #"{"a": 2}"#.data(using: .utf8)!)) | |
//exit(0) | |
func b() { | |
benchmark(type: [User2].self, data: users10) | |
benchmark(type: [User2].self, data: users100) | |
benchmark(type: [User2].self, data: users1000) | |
/*benchmark(type: Twitter.self, data: twitterData) | |
benchmark(type: ghEvents.self, data: githubData) | |
benchmark(type: ApacheBuilds.self, data: apacheData) | |
benchmark(type: canada.self, data: canadaData)*/ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment