Skip to content

Optimiser performs worse on lazy Collections/Sequences than for-in-loop versions #69751

@wadetregaskis

Description

@wadetregaskis

Description
A functional-style pipeline a la data.lazy.filter { … }.map { … }.reduce(…) should in principle be equivalent to the imperative phrasing, e.g. for value in data where … { result += foo(value) }. The compiler already does a pretty miraculous job of removing all the functional boilerplate and ceremony to turn the former style into the latter. However, the performance is not always comparable.

e.g. for this example benchmark the performance is basically identical on x86-64 (on a Xeon W-2150B) but on arm64 (on an M2) the lazy functional style is four times slower. One obvious difference is that the lazy functional style gets compiled down to a moderately-long sequence of conditional instructions, whereas the imperative form uses plain old branching.

Formal write-up of observations and results.

Some discussion in Swift Forums.

Steps to reproduce

git clone https://github.com/wadetregaskis/Swift-Benchmarks.git
cd Swift-Benchmarks
swift package benchmark --target ArrayProcessing

Expected behavior
Roughly equivalent performance for either approach ("Filter, map, and reduce (lazily)" and "for-in loop").

(ideally the eager functional style, without .lazy involved, would also perform similarly, but that's probably a different optimiser challenge)

Environment
macOS Sonoma (14.1)
Xcode 15.0.1
Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)

Test hardware:

  • iMac Pro (10-cores) w/ 64 GiB RAM
  • M2 MacBook Air w/ 24 GiB RAM

Metadata

Metadata

Assignees

No one assigned

    Labels

    SILOptimizerArea → compiler: SIL optimization passesbugA deviation from expected or documented behavior. Also: expected but undesirable behavior.performance

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions