Skip to content

Instantly share code, notes, and snippets.

@michaeleisel
Created February 23, 2020 02:21
Show Gist options
  • Save michaeleisel/e2414e93a73601c7e1f99733f8f96a42 to your computer and use it in GitHub Desktop.
Save michaeleisel/e2414e93a73601c7e1f99733f8f96a42 to your computer and use it in GitHub Desktop.
//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