Skip to content
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

[SR-9604] Array initialisation from UnsafeRawBufferPointer takes 10x longer than from UnsafeBufferPointer<UInt8> #52050

Closed
weissi opened this issue Jan 6, 2019 · 7 comments
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself performance standard library Area: Standard library umbrella

Comments

@weissi
Copy link
Member

weissi commented Jan 6, 2019

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

@milseman
Copy link
Mannequin

milseman mannequin commented Jan 7, 2019

They should be roughly similar or the same semantic operations. CC @eeckstein, this seems like a missing optimization.

@eeckstein
Copy link
Member

We'll take a look

@weissi
Copy link
Member Author

weissi commented Jan 19, 2019

working around now in NIO: apple/swift-nio#761 & apple/swift-nio#762

@weissi
Copy link
Member Author

weissi commented Jul 2, 2019

@swift-ci create

@glessard
Copy link
Contributor

The slice version is improved in Swift 5.4. On macOS 11.4 (20F71) with Swift 5.4 (Xcode 12.5) on M1, the reduced test case gets the following output:

as UnsafeRawBufferPointer
0.35432207584381104
as UnsafeBufferPointer<UInt8>
0.024224042892456055
as Slice<UnsafeRawBufferPointer>
0.02411496639251709

(similar results on x86 iMac)

@glessard
Copy link
Contributor

Should be fixed by #38828

@glessard
Copy link
Contributor

Fixed on main as of August 28th snapshot.

as UnsafeRawBufferPointer
0.01928997039794922
as UnsafeBufferPointer<UInt8>
0.01868903636932373
as Slice<UnsafeRawBufferPointer>
0.02171909809112549

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself performance standard library Area: Standard library umbrella
Projects
None yet
Development

No branches or pull requests

3 participants