Skip to content

Commit 5db60c0

Browse files
authored
Merge pull request #2803 from spevans/base64encode-speedup3
2 parents 092c58a + 0927904 commit 5db60c0

File tree

2 files changed

+38
-17
lines changed

2 files changed

+38
-17
lines changed

Sources/Foundation/Data.swift

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2514,22 +2514,39 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
25142514
///
25152515
/// - parameter options: The options to use for the encoding. Default value is `[]`.
25162516
/// - returns: The Base-64 encoded string.
2517-
@inlinable // This is @inlinable as trivially forwarding.
25182517
public func base64EncodedString(options: Data.Base64EncodingOptions = []) -> String {
2519-
return _representation.withInteriorPointerReference {
2520-
return $0.base64EncodedString(options: options)
2518+
let dataLength = self.count
2519+
if dataLength == 0 { return "" }
2520+
2521+
let capacity = NSData.estimateBase64Size(length: dataLength)
2522+
let ptr = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: 4)
2523+
defer { ptr.deallocate() }
2524+
let buffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
2525+
let length = self.withUnsafeBytes { inputBuffer in
2526+
NSData.base64EncodeBytes(inputBuffer, options: options, buffer: buffer)
25212527
}
2528+
2529+
return String(decoding: UnsafeRawBufferPointer(start: ptr, count: length), as: Unicode.UTF8.self)
25222530
}
25232531

25242532
/// Returns a Base-64 encoded `Data`.
25252533
///
25262534
/// - parameter options: The options to use for the encoding. Default value is `[]`.
25272535
/// - returns: The Base-64 encoded data.
2528-
@inlinable // This is @inlinable as trivially forwarding.
25292536
public func base64EncodedData(options: Data.Base64EncodingOptions = []) -> Data {
2530-
return _representation.withInteriorPointerReference {
2531-
return $0.base64EncodedData(options: options)
2537+
let dataLength = self.count
2538+
if dataLength == 0 { return Data() }
2539+
2540+
let capacity = NSData.estimateBase64Size(length: dataLength)
2541+
let ptr = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: 4)
2542+
let outputBuffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
2543+
2544+
let length = self.withUnsafeBytes { inputBuffer in
2545+
NSData.base64EncodeBytes(inputBuffer, options: options, buffer: outputBuffer)
25322546
}
2547+
return Data(bytesNoCopy: ptr, count: length, deallocator: .custom({ (ptr, length) in
2548+
ptr.deallocate()
2549+
}))
25332550
}
25342551

25352552
// MARK: -

Sources/Foundation/NSData.swift

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
604604

605605
// MARK: - Base64 Methods
606606

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

617-
let capacity = estimateBase64Size(length: dataLength)
617+
let inputBuffer = UnsafeRawBufferPointer(start: self.bytes, count: dataLength)
618+
let capacity = NSData.estimateBase64Size(length: dataLength)
618619
let ptr = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: 4)
619620
defer { ptr.deallocate() }
620-
let buffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
621-
let length = NSData.base64EncodeBytes(self, options: options, buffer: buffer)
621+
let outputBuffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
622+
let length = NSData.base64EncodeBytes(inputBuffer, options: options, buffer: outputBuffer)
622623

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

631-
let capacity = estimateBase64Size(length: dataLength)
632+
let inputBuffer = UnsafeRawBufferPointer(start: self.bytes, count: self.length)
633+
634+
let capacity = NSData.estimateBase64Size(length: dataLength)
632635
let ptr = UnsafeMutableRawPointer.allocate(byteCount: capacity, alignment: 4)
633-
let buffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
634-
let length = NSData.base64EncodeBytes(self, options: options, buffer: buffer)
636+
let outputBuffer = UnsafeMutableRawBufferPointer(start: ptr, count: capacity)
635637

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

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

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

813817
// Read three bytes at a time, which convert to 4 ASCII characters, allowing for byte2 and byte3 being nil
814-
let dataBuffer = UnsafeRawBufferPointer(start: data.bytes, count: data.length)
818+
815819
var inputIndex = 0
816820
var outputIndex = 0
817821
var bytesLeft = dataBuffer.count

0 commit comments

Comments
 (0)