swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.58k stars 10.36k forks source link

[SR-7286] non-escaping closures aren't stack allocated #49834

Closed weissi closed 5 years ago

weissi commented 6 years ago
Previous ID SR-7286
Radar rdar://problem/38913071
Original Reporter @weissi
Type Bug
Status Resolved
Resolution Duplicate

Attachment: Download

Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Compiler | |Labels | Bug, Optimizer, Performance | |Assignee | None | |Priority | Medium | md5: e7067f5ef2bdbe07d6a03ec02a586cdf

duplicates:

Issue Description:

Even non-escaping closures aren't stack allocated which kind of defeats their purpose. Take the following SwiftPM project:

./Sources/nectc/main.swift
=============
import OtherModule

func foo() {
    var counter = 0
    for _ in 0..<100_000 {
        Applicator().apply {
            counter += 1
        }
    }
    print(counter)
}

foo()

./Sources/OtherModule/OtherModule.swift
=============
public struct Applicator {
    public init() {}
    public func apply(_ body: () -> Void) -> Void {
        body()
    }
}

./Package.swift
=============
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "nectc",
    dependencies: [
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "OtherModule",
            dependencies: []),
        .target(
            name: "nectc",
            dependencies: ["OtherModule"]),
    ]
)

(also attached as .tar.gz)

when run with

$ swift build -c release && sudo dtrace -n 'pid$target::malloc:entry { @malloc_calls = count(); } :::END { printa(@malloc_calls); } ' -c ./.build/x86_64-apple-macosx10.10/release/nectc

I'd expect it to first print 100000 (the counter value) and then O(1) allocations which should be very few. In reality it looks like this though:

$ swift build -c release && sudo dtrace -n 'pid$target::malloc:entry { @malloc_calls = count(); } :::END { printa(@malloc_calls); } ' -c ./.build/x86_64-apple-macosx10.10/release/nectc
dtrace: description 'pid$target::malloc:entry ' matched 5 probes
100000
dtrace: pid 81882 has exited
CPU     ID                    FUNCTION:NAME
  6      2                             :END 
           100027

which are 100027 heap allocations! Which is unexpected.

We hit this problem in the real world in a NIO PR: https://github.com/apple/swift-nio/pull/242/files

We needed to add @_inlineable to the withLock functions in order for them to not heap allocate 🙁. In this case just making it inlineable fortunately works as the methods are small enough so that the whole thing is inlined and the allocation therefore elided.

Tested under

$ swift --version
Apple Swift version 4.0.3 (swiftlang-900.0.71 clang-900.0.38)
Target: x86_64-apple-macosx10.9

With the latest 4.1 dev snapshot it's only one allocation less:

$ /Library/Developer/Toolchains/swift-4.1-DEVELOPMENT-SNAPSHOT-2018-03-26-a.xctoolchain/usr/bin/swift build -c release && sudo dtrace -n 'pid$target::malloc:entry { @malloc_calls = count(); } :::END { printa(@malloc_calls); } ' -c ./.build/x86_64-apple-macosx10.10/release/nectc
Compile Swift Module 'OtherModule' (1 sources)
Compile Swift Module 'nectc' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/release/nectc
Password:
dtrace: description 'pid$target::malloc:entry ' matched 5 probes
100000
dtrace: pid 82143 has exited
CPU     ID                    FUNCTION:NAME
  4      2                             :END 
           100026
weissi commented 6 years ago

@swift-ci create

aschwaighofer commented 5 years ago

Fixed with: https://github.com/apple/swift/pull/21933

$ sudo dtrace -n 'pid$target::malloc:entry { @malloc_calls = count(); } :::END { printa(@malloc_calls); } ' -c ./.build/x86_64-apple-macosx/release/nectc
dtrace: system integrity protection is on, some features will not be available
dtrace: description 'pid$target::malloc:entry ' matched 5 probes
100000
dtrace: pid 62508 has exited
CPU     ID                    FUNCTION:NAME
  6      2                             :END 
               48

weissi commented 5 years ago

You made my day 🙂