-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
Previous ID | SR-9604 |
Radar | rdar://problem/52529574 |
Original Reporter | @weissi |
Type | Bug |
Status | Resolved |
Resolution | Done |
Additional Detail from JIRA
Votes | 0 |
Component/s | Compiler, Standard Library |
Labels | Bug, Performance |
Assignee | @eeckstein |
Priority | Medium |
md5: 93fe6fdccf1fe595e6c2a712aacb4092
Issue Description:
John Connolly noticed a 4x slowdown of his NIO Redis driver (repro in https://github.com/John-Connolly/nio-performance) when switching from NIO 1.8.0 to NIO 1.9.0.
Turns out that ByteBuffer.getBytes
is a lot slower in NIO 1.9.0 which caused all the slowdown.
The only change in getBytes
however was from switching
Array.init(UnsafeBufferPointer<UInt8>(start: ptr.baseAddress?.advanced(by: index).assumingMemoryBound(to: UInt8.self),
count: length))
to
Array<UInt8>(ptr[index..<(index+length)])
as part of this PR: apple/swift-nio#524
This can also be reproduced super easily without NIO just standalone with this program:
import Foundation
let origin: [UInt8] = Array(repeating: 0xab, count: 1024 * 1024)
@inline(never)
func do1() -> Int {
let start = Date()
var x = 0
for _ in 0..<1000 {
x += origin.withUnsafeBytes { urbp in
Array(urbp).count
}
}
let end = Date()
print(end.timeIntervalSince(start))
return x
}
@inline(never)
func do2() -> Int {
let start = Date()
var x = 0
for _ in 0..<1000 {
x += origin.withUnsafeBufferPointer { ubp in
Array(ubp).count
}
}
let end = Date()
print(end.timeIntervalSince(start))
return x
}
@inline(never)
func do3() -> Int {
let start = Date()
var x = 0
for _ in 0..<1000 {
x += origin.withUnsafeBytes { urbp in
Array(urbp[...]).count
}
}
let end = Date()
print(end.timeIntervalSince(start))
return x
}
print("as UnsafeRawBufferPointer")
let one = do1()
print("as UnsafeBufferPointer<UInt8>")
let two = do2()
print("as Slice<UnsafeRawBufferPointer>")
let three = do3()
precondition(one == two)
precondition(one == three)
running this gives:
Swift 4.2.1
$ swift -O test.swift
as UnsafeRawBufferPointer
0.5475319623947144
as UnsafeBufferPointer<UInt8>
0.0333859920501709
as Slice<UnsafeRawBufferPointer>
0.7531230449676514
Swift 5 dev
$ /Library/Developer/Toolchains/swift-5.0-DEVELOPMENT-SNAPSHOT-2018-12-28-a.xctoolchain/usr/bin/swift -O test.swift
as UnsafeRawBufferPointer
0.5512470006942749
as UnsafeBufferPointer<UInt8>
0.03376805782318115
as Slice<UnsafeRawBufferPointer>
0.6322070360183716
Essentially, there seems to be a fast path missing that allows us to use memcpy
instead of byte-by-byte copy when using UnsafeRawBufferPointer
. Slices perform even worse...
CC @moiseev/@atrick/@airspeedswift/@milseman