Skip to content

Base64: Speedup encoding fix 3 #2803

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

Merged
merged 1 commit into from
Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions Sources/Foundation/Data.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2514,22 +2514,39 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
///
/// - parameter options: The options to use for the encoding. Default value is `[]`.
/// - returns: The Base-64 encoded string.
@inlinable // This is @inlinable as trivially forwarding.
public func base64EncodedString(options: Data.Base64EncodingOptions = []) -> String {
return _representation.withInteriorPointerReference {
return $0.base64EncodedString(options: options)
let dataLength = self.count
if dataLength == 0 { return "" }

let capacity = NSData.estimateBase64Size(length: dataLength)
let ptr = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: 4)
defer { ptr.deallocate() }
let buffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
let length = self.withUnsafeBytes { inputBuffer in
NSData.base64EncodeBytes(inputBuffer, options: options, buffer: buffer)
}

return String(decoding: UnsafeRawBufferPointer(start: ptr, count: length), as: Unicode.UTF8.self)
}

/// Returns a Base-64 encoded `Data`.
///
/// - parameter options: The options to use for the encoding. Default value is `[]`.
/// - returns: The Base-64 encoded data.
@inlinable // This is @inlinable as trivially forwarding.
public func base64EncodedData(options: Data.Base64EncodingOptions = []) -> Data {
return _representation.withInteriorPointerReference {
return $0.base64EncodedData(options: options)
let dataLength = self.count
if dataLength == 0 { return Data() }

let capacity = NSData.estimateBase64Size(length: dataLength)
let ptr = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: 4)
let outputBuffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)

let length = self.withUnsafeBytes { inputBuffer in
NSData.base64EncodeBytes(inputBuffer, options: options, buffer: outputBuffer)
}
return Data(bytesNoCopy: ptr, count: length, deallocator: .custom({ (ptr, length) in
ptr.deallocate()
}))
}

// MARK: -
Expand Down
26 changes: 15 additions & 11 deletions Sources/Foundation/NSData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {

// MARK: - Base64 Methods

private func estimateBase64Size(length: Int) -> Int {
internal static func estimateBase64Size(length: Int) -> Int {
// Worst case allow for 64bytes + \r\n per line 48 input bytes => 66 output bytes
return ((length + 47) * 66) / 48
}
Expand All @@ -614,11 +614,12 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
let dataLength = self.length
if dataLength == 0 { return "" }

let capacity = estimateBase64Size(length: dataLength)
let inputBuffer = UnsafeRawBufferPointer(start: self.bytes, count: dataLength)
let capacity = NSData.estimateBase64Size(length: dataLength)
let ptr = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: 4)
defer { ptr.deallocate() }
let buffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
let length = NSData.base64EncodeBytes(self, options: options, buffer: buffer)
let outputBuffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
let length = NSData.base64EncodeBytes(inputBuffer, options: options, buffer: outputBuffer)

return String(decoding: UnsafeRawBufferPointer(start: ptr, count: length), as: Unicode.UTF8.self)
}
Expand All @@ -628,11 +629,13 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
let dataLength = self.length
if dataLength == 0 { return Data() }

let capacity = estimateBase64Size(length: dataLength)
let inputBuffer = UnsafeRawBufferPointer(start: self.bytes, count: self.length)

let capacity = NSData.estimateBase64Size(length: dataLength)
let ptr = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: 4)
let buffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
let length = NSData.base64EncodeBytes(self, options: options, buffer: buffer)
let outputBuffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)

let length = NSData.base64EncodeBytes(inputBuffer, options: options, buffer: outputBuffer)
return Data(bytesNoCopy: ptr, count: length, deallocator: .custom({ (ptr, length) in
ptr.deallocate()
}))
Expand Down Expand Up @@ -761,13 +764,14 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
/**
This method encodes data in Base64.

- parameter data: The NSData object you want to encode
- parameter dataBuffer: The UnsafeRawBufferPointer buffer to encode
- parameter options: Options for formatting the result
- parameter buffer: The buffer to write the bytes into
- returns: The number of bytes written into the buffer
*/
private static func base64EncodeBytes(_ data: NSData, options: Base64EncodingOptions = [], buffer: UnsafeMutableRawBufferPointer) -> Int {

NOTE: dataBuffer would be better expressed as a <T: Collection> where T.Element == UInt8, T.Index == Int but this currently gives much poorer performance.
*/
static func base64EncodeBytes(_ dataBuffer: UnsafeRawBufferPointer, options: Base64EncodingOptions = [], buffer: UnsafeMutableRawBufferPointer) -> Int {
// Use a StaticString for lookup of values 0-63 -> ASCII values
let base64Chars = StaticString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
assert(base64Chars.utf8CodeUnitCount == 64)
Expand Down Expand Up @@ -811,7 +815,7 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
}

// Read three bytes at a time, which convert to 4 ASCII characters, allowing for byte2 and byte3 being nil
let dataBuffer = UnsafeRawBufferPointer(start: data.bytes, count: data.length)

var inputIndex = 0
var outputIndex = 0
var bytesLeft = dataBuffer.count
Expand Down