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-7286] non-escaping closures aren't stack allocated #49834

Closed
weissi opened this issue Mar 27, 2018 · 3 comments
Closed

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

weissi opened this issue Mar 27, 2018 · 3 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself performance SILOptimizer Area → compiler: SIL optimization passes

Comments

@weissi
Copy link
Member

weissi commented Mar 27, 2018

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:

  • SR-904 Allocate closure context for closures passed @NoEscape on the stack

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
Copy link
Member Author

weissi commented Mar 27, 2018

@swift-ci create

@aschwaighofer
Copy link
Member

Fixed with: #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
Copy link
Member Author

weissi commented Jan 18, 2019

You made my day 🙂

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@AnthonyLatsis AnthonyLatsis added SILOptimizer Area → compiler: SIL optimization passes and removed Optimizer labels Nov 2, 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 SILOptimizer Area → compiler: SIL optimization passes
Projects
None yet
Development

No branches or pull requests

3 participants