Skip to content

Commit 4499edd

Browse files
authored
Implement Data reading and writing (swiftlang#377)
* Implement Data reading and writing * Use swift-foundation-local for dependency name in benchmark
1 parent c7d2c88 commit 4499edd

File tree

14 files changed

+1694
-168
lines changed

14 files changed

+1694
-168
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2022-2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Benchmark
14+
import func Benchmark.blackHole
15+
@testable import FoundationEssentials
16+
17+
#if canImport(Glibc)
18+
import Glibc
19+
#endif
20+
#if canImport(Darwin)
21+
import Darwin
22+
#endif
23+
24+
func testPath() -> String {
25+
// Generate a random file name
26+
String.temporaryDirectoryPath.appendingPathComponent("testfile-\(UUID().uuidString)")
27+
}
28+
29+
func generateTestData() -> Data {
30+
// 16 MB file, big enough to trigger things like chunking
31+
let count = 1 << 24
32+
33+
let memory = malloc(count)!
34+
let ptr = memory.bindMemory(to: UInt8.self, capacity: count)
35+
36+
// Set a few bytes so we're sure to not be all zeros
37+
let buf = UnsafeMutableBufferPointer(start: ptr, count: count)
38+
for i in 0..<128 {
39+
buf[i] = UInt8.random(in: 1..<42)
40+
}
41+
42+
return Data(bytesNoCopy: ptr, count: count, deallocator: .free)
43+
}
44+
45+
func cleanup(at path: String) {
46+
_ = unlink(path)
47+
// Ignore any errors
48+
}
49+
50+
let data = generateTestData()
51+
let readMe = testPath()
52+
53+
let benchmarks = {
54+
Benchmark.defaultConfiguration.maxIterations = 1_000_000_000
55+
Benchmark.defaultConfiguration.maxDuration = .seconds(3)
56+
Benchmark.defaultConfiguration.scalingFactor = .kilo
57+
// Benchmark.defaultConfiguration.metrics = .arc + [.cpuTotal, .wallClock, .mallocCountTotal, .throughput] // use ARC to see traffic
58+
// Benchmark.defaultConfiguration.metrics = [.cpuTotal, .wallClock, .mallocCountTotal, .throughput] // skip ARC as it has some overhead
59+
Benchmark.defaultConfiguration.metrics = .all // Use all metrics to easily see which ones are of interest for this benchmark suite
60+
61+
Benchmark("read-write-emptyFile") { benchmark in
62+
let path = testPath()
63+
let data = Data()
64+
try data.write(to: path)
65+
let read = try Data(contentsOf: path, options: [])
66+
cleanup(at: path)
67+
}
68+
69+
Benchmark("write-regularFile") { benchmark in
70+
let path = testPath()
71+
try data.write(to: path)
72+
cleanup(at: path)
73+
}
74+
75+
Benchmark("read-regularFile",
76+
configuration: .init(
77+
setup: {
78+
try! data.write(to: readMe)
79+
},
80+
teardown: {
81+
cleanup(at: readMe)
82+
}
83+
)
84+
) { benchmark in
85+
blackHole(try Data(contentsOf: readMe))
86+
}
87+
}

Benchmarks/Package.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,17 @@ let package = Package(
4444
.plugin(name: "BenchmarkPlugin", package: "package-benchmark")
4545
]
4646
),
47+
.executableTarget(
48+
name: "DataIOBenchmarks",
49+
dependencies: [
50+
.product(name: "FoundationEssentials", package: "swift-foundation-local"),
51+
.product(name: "Benchmark", package: "package-benchmark"),
52+
],
53+
path: "Benchmarks/DataIO",
54+
plugins: [
55+
.plugin(name: "BenchmarkPlugin", package: "package-benchmark")
56+
]
57+
),
58+
4759
]
4860
)

Sources/FoundationEssentials/CocoaError.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public struct CocoaError : CustomNSError, _StoredError, Hashable {
4242
public let code: Code
4343
public let userInfo: [String: AnyHashable]
4444

45-
public init(code: Code, userInfo: [String: AnyHashable]) {
45+
public init(_ code: Code, userInfo: [String: AnyHashable] = [:]) {
4646
self.code = code
4747
self.userInfo = userInfo
4848
}
@@ -187,7 +187,7 @@ extension CocoaError {
187187
if let url = url {
188188
info["NSURLErrorKey"] = url
189189
}
190-
return CocoaError(code: code, userInfo: info)
190+
return CocoaError(code, userInfo: info)
191191
}
192192
#endif
193193
}
@@ -275,3 +275,11 @@ public extension Error {
275275
#endif
276276

277277

278+
#if FOUNDATION_FRAMEWORK
279+
#else
280+
// These are loosely typed, as a typedef for String called NSErrorUserInfoKey
281+
internal let NSUnderlyingErrorKey = "NSUnderlyingErrorKey"
282+
internal let NSUserStringVariantErrorKey = "NSUserStringVariantErrorKey"
283+
internal let NSFilePathErrorKey = "NSFilePathErrorKey"
284+
internal let NSURLErrorKey = "NSURLErrorKey"
285+
#endif
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if FOUNDATION_FRAMEWORK
14+
// For Logger
15+
@_implementationOnly import os
16+
@_implementationOnly import _ForSwiftFoundation
17+
@_implementationOnly import _CShims
18+
#else
19+
package import _CShims
20+
#endif
21+
22+
#if canImport(Darwin)
23+
import Darwin
24+
#elseif canImport(Glibc)
25+
import Glibc
26+
#endif
27+
28+
internal func fileReadingOrWritingError(posixErrno: Int32, path: PathOrURL?, reading: Bool, variant: String? = nil, extraUserInfo: [String: AnyHashable] = [:]) -> Error {
29+
let code: CocoaError.Code
30+
if reading {
31+
switch posixErrno {
32+
case EFBIG:
33+
code = .fileReadTooLarge
34+
case ENOENT:
35+
code = .fileReadNoSuchFile
36+
case EPERM, EACCES:
37+
code = .fileReadNoPermission
38+
case ENAMETOOLONG:
39+
code = .fileReadInvalidFileName
40+
default:
41+
code = .fileReadUnknown
42+
}
43+
} else {
44+
switch posixErrno {
45+
case ENOENT:
46+
code = .fileNoSuchFile
47+
case EPERM, EACCES:
48+
code = .fileWriteNoPermission
49+
case ENAMETOOLONG:
50+
code = .fileWriteInvalidFileName
51+
case EDQUOT, ENOSPC:
52+
code = .fileWriteOutOfSpace
53+
case EROFS:
54+
code = .fileWriteVolumeReadOnly
55+
case EEXIST:
56+
code = .fileWriteFileExists
57+
default:
58+
code = .fileWriteUnknown
59+
}
60+
}
61+
62+
var userInfo : [String : AnyHashable] = [:]
63+
if let posixError = POSIXErrorCode(rawValue: posixErrno) {
64+
userInfo[NSUnderlyingErrorKey] = POSIXError(posixError)
65+
}
66+
67+
if let variant {
68+
userInfo[NSUserStringVariantErrorKey] = [variant]
69+
}
70+
71+
if let path {
72+
switch path {
73+
case .path(let path):
74+
userInfo[NSFilePathErrorKey] = path
75+
case .url(let url):
76+
userInfo[NSURLErrorKey] = url
77+
}
78+
}
79+
80+
if !extraUserInfo.isEmpty {
81+
for (k, v) in extraUserInfo {
82+
userInfo[k] = v
83+
}
84+
}
85+
86+
return CocoaError(code, userInfo: userInfo)
87+
}
88+
89+
internal func logFileIOErrno(_ err: Int32, at place: String) {
90+
#if FOUNDATION_FRAMEWORK && !os(bridgeOS)
91+
let errnoDesc = String(cString: strerror(err))
92+
Logger(_NSOSLog()).error("Encountered \(place) failure \(err) \(errnoDesc)")
93+
#endif
94+
}

0 commit comments

Comments
 (0)