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-7546] Swift should support some mechanism to warn/error when heap allocations are happening #50088

Open
weissi opened this issue Apr 26, 2018 · 9 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself feature A feature request or implementation

Comments

@weissi
Copy link
Member

weissi commented Apr 26, 2018

Previous ID SR-7546
Radar rdar://problem/39762528
Original Reporter @weissi
Type Bug

Attachment: Download

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, LanguageFeatureRequest
Assignee None
Priority Medium

md5: 2a4c1feea37249b97a389c1143f2420d

Issue Description:

Accidental heap allocations are a major source of frustration when writing high-performance Swift. For SwiftNIO we had to create integration tests which literally count the number of mallocs (and frees) by hooking the functions. In CI we then compare those numbers against a baseline we set (by exporting environment variables).

This is quite cumbersome and a lot of work as the limits depend at least on the exact Swift version and also the OS. Also the tests are somewhat costly to write. In most cases the tests just exist to make sure that in certain areas of SwiftNIO we don't cause any new heap allocations. But even then it only makes sure we don't regress.

What would be incredibly handy would be some kind of mechanism that would get the compiler to warn/error of a heap allocation is happening in a certain bit of code. I don't really know yet how that should look in the source language but maybe someone has an idea.

Some links:
@gottesmm pointed out the already existing opt-remark system is does similar things:

here is a description (and the code) for the integration test that does allocation counting in SwiftNIO:
https://github.com/apple/swift-nio/tree/master/IntegrationTests/tests_04_performance/test_01_resources

and here's a sample output from CI:

17:00:27 Running test 'test_01_allocation_counts_for_http1.sh'... 
17:00:27 info: 1000_reqs_1_conn: allocations not freed: 0
17:00:27 info: 1000_reqs_1_conn: total number of mallocs: 51816
17:00:27 info: 1_reqs_1000_conn: allocations not freed: 0
17:00:27 info: 1_reqs_1000_conn: total number of mallocs: 866997
17:00:27 info: ping_pong_1000_reqs_1_conn: allocations not freed: 0
17:00:27 info: ping_pong_1000_reqs_1_conn: total number of mallocs: 4568
17:00:27 info: bytebuffer_lots_of_rw: allocations not freed: 0
17:00:27 info: bytebuffer_lots_of_rw: total number of mallocs: 3011
17:00:27 warning: no reference number of allocations set (set to $MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw)
17:00:27 warning: to set current number:
17:00:27 warning:     export MAX_ALLOCS_ALLOWED_bytebuffer_lots_of_rw=3011
17:00:27 OK (120s)
@weissi
Copy link
Member Author

weissi commented Apr 26, 2018

@swift-ci create

1 similar comment
@gottesmm
Copy link
Member

@swift-ci create

@belkadan
Copy link
Contributor

belkadan commented May 1, 2018

This is a huge language-design request, but one we've been interested in in the past. It's not just a matter of adding opt-remarks around compiler-inserted allocations, though.

@gottesmm
Copy link
Member

gottesmm commented May 2, 2018

@belkadan Is this really a language design request? To me this is a reasonable, incremental request for a build warning that is allowed to be noisy. Why should the ultimate "keep things on the stack language feature" stop us from exposing this information to our users?

Additionally, regardless of whether or not we expose this to users I think for compiler performance engineers this would be really useful in terms of quickly understanding where the optimizer fails without having to read SIL.

In terms of implementation @atrick and I spoke about this a little bit. The main interesting part is that we may run function passes many times with interleaving module passes in the SILPassManager. To implement this one would thus need to have some sort of way invalidating opt remarks from previous runs. There are a bunch of ways you could do this, but I imagine the simplest way is to have an analysis that invalidates opt remarks when a pass begins. The reason to use an analysis is that analyses are the standard way besides the IR to communicate information in between SILPasses. Then we could have a pass that at the end of the pipeline that consumes the analysis and emits the final opt remarks.

@atrick
Copy link
Member

atrick commented May 2, 2018

I think the spirit of this bug is: what information can the compiler provide without broaching language design? In the past, I've considered calls to malloc to be insignificant relative to other performance issues. But now we have developers attempting to minimize allocations using whatever unsafe APIs are at there disposal. Since there aren't language guarantees, they need to easily detect when, where, and hopefully why their tuning fails or regresses. I think this is just a expert level testing/diagnostic feature.

@belkadan
Copy link
Contributor

belkadan commented May 2, 2018

This is incredibly language design-y. At first glance, you want to know

  • whether you're using a language construct that requires malloc (existentials, classes, escaping closures with captures)

  • whether you're using a runtime feature that might call malloc (conformance lookup? ObjC message send?)

but the real problem is that you also need to know whether anything you call is going to allocate. If you don't have that, then the compiler won't tell you that your Swift.Set is also partly on the heap. That's ultimately an effects system, the same as "pure".

I don't think this means that we can't do anything here, but it does mean that it's not really a question about "where the optimizer fails". That's not the question users are asking. (If it helps us improve the compiler, it's worth adding, but not advertising.)

(Note that I'm assuming generics and non-escaping closures ought to require no extra allocations even without optimization. We're nearly there!)

@gottesmm
Copy link
Member

gottesmm commented May 2, 2018

Jordan. That is not what is being asked for here. You are trying to devise a solution that prevents any/all allocations. That would require an effects system. That is specifically not what we are trying to do here.

Rather, I am focused on the specific types of allocations that Johannes is hitting. Specifically, if I understand correctly, the allocations that Johannes is talking about are things where for some reason we are not stack promoting a class or a box. Using this opt-remark tool, Johannes can provide the performance team with better data on this and have knowledge if the optimizer failed.

That being said, I just pinged Johannes to double check the correctness of my assertion that the allocations are mostly classes that we are not stack promoting for some reason.

@weissi
Copy link
Member Author

weissi commented May 3, 2018

@belkadan/@gottesmm yes, I was after what Michael suggested: I'd like to get a warning/error for things that can be promoted to the stack but don't in a particular function.

@weissi
Copy link
Member Author

weissi commented Aug 28, 2019

IntelliJ apparently can at least show 'allocation in loop': https://twitter.com/tagir_valeev/status/1166606424329396224

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
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 feature A feature request or implementation
Projects
None yet
Development

No branches or pull requests

4 participants