> On Dec 29, 2017, at 4:58 PM, Félix Cloutier <felixclout...@icloud.com> wrote:
> 
> Thanks Michael! Other people might have different opinions, but knowing that 
> a "definitive" fix is on the way is plenty for me and I'm not looking to make 
> a fuss.

No worries. Happy to help = ). If you are interested, I would be happy to help 
guide you in implementing one of these optimizations.

> 
> If you still have some time, I have another question or two. You said that 
> only self is passed at +0. Does this mean, for instance, that custom 
> operators would still get +1 Expr values?

Yes. But again assuming a non-resilient operator, the optimizer can convert the 
+1 in many cases to +0 via the owned->guaranteed optimization.

> It seems to me that if you receive any boxed value at +0, then it's always 
> safe to also pass it further at +0 since it is immutable.
> That would have a huge impact on this program. Are there downsides to more 
> pervasive +0 parameters?

The main downside is that in some cases you do want to have +1 parameters, 
namely places where you are forwarding values into a function for storage. An 
example of such a case is a value passed to a setter or an initializer. I would 
think of it more as an engineering trade-off.

I am currently experimenting with changing normal function arguments to +0 for 
this purpose, leaving initializers and setters as taking values at +1. 
Regardless of the default, we can always provide an attribute for the purpose 
of allowing the user to specify such behavior.

Michael

> 
> Félix
> 
>> Le 29 déc. 2017 à 15:42, Michael Gottesman <mgottes...@apple.com> a écrit :
>> 
>> 
>> 
>>> On Dec 28, 2017, at 4:27 PM, Chris Lattner via swift-dev 
>>> <swift-dev@swift.org> wrote:
>>> 
>>> <moving to swift-dev, where most of the perf optimization people lurk>
>>> 
>>> This is a great question, I’m not sure what the answer is: maybe it is a 
>>> simple case missed by the arc optimizer?
>> 
>> It is a bit more complicated than that. There are codegen aspects and 
>> optimization aspects. In both cases there are "structural issues" that I am 
>> currently in the process of fixing as part of the "semantic sil" effort. 
>> There are some retain/release pairs that we could eliminate by teaching the 
>> optimizer about enums/indirect boxes. I go over all of this below.
>> 
>> TLDR: There are structural issues here that make this difficult/unsafe in 
>> the general case. Semantic SIL eliminates all of the structural issues with 
>> ARC and makes this problem (among other problems) "trivial", so we have been 
>> focusing our engineering time on implementing that. That being said, we 
>> could use the effects analysis to handle this trivial case. But it would 
>> have to be very conservative. Chris, feel like doing some ARC Optimization? 
>> = p.
>> 
>> # Codegen Aspects
>> 
>> ## Background Info: Local Retain/Release Pairs
>> 
>> If one does not know about local vs non-local retain/release pairs, see the 
>> appendix for an explanation.
>> 
>> ## +0 Self
>> 
>> In Swift today all parameters except for self are passed at +1. Self is 
>> passed at +0. Even though this is true, often times SILGen will be 
>> conservative around self and will emit a copy/destroy around the call site. 
>> This is actually better since it eliminates a non-local retain/release pair 
>> in the callee/caller favor of a local retain/release pair in the caller that 
>> can be eliminated by the optimizer  without any form of inter-procedural 
>> optimization. That is why there is a retain/release at the count call. I 
>> will go into why we are not optimizing it below in the optimization section.
>> 
>> ## Pattern Matching at +1
>> 
>> All pattern matching in Swift today is done at +1. That means that in the 
>> following Swift:
>> 
>> ----
>> enum Expr: CustomStringConvertible {
>>      case Int(n: Int)
>>      indirect case Var(x: String)
>>      indirect case Add(f: Expr, g: Expr)
>>      indirect case Mul(f: Expr, g: Expr)
>>      indirect case Pow(f: Expr, g: Expr)
>>      indirect case Ln(f: Expr)
>> 
>>      ...
>> 
>>      var count: Int {
>>              switch self {
>>              case .Int, .Var: return 1
>>              case let .Ln(f): return f.count
>>              case let .Add(f, g), let .Mul(f, g), let .Pow(f, g):
>>                      return f.count + g.count
>>              }
>>      }
>> }
>> ----
>> 
>> even though self is passed at +0 to count, SILGen will emit a retain before 
>> the switch. This is unfortunate and adds unnecessary ARC traffic in read 
>> only methods on enums. There is no reason not to emit switches that way, so 
>> as part of the work to make SILGenPattern able to pass the ownership 
>> verifier, I plan on changing all pattern emission to be done at +0. As an 
>> added benefit once that work is complete, refactorings in SILGenPattern will 
>> be exposed that will enable us to significantly simplify SILGenPattern's 
>> handling of cleanups. This complex code has lead to obscure bugs in the past 
>> and is IMO overly complex.
>> 
>> # Optimizer Aspects
>> 
>> Lets start by looking at count.getter. Here is the SIL in CFG form:
>> 
>> <count.getter.pdf>
>> 
>> Lets analyze bb4 (I think bb5 suffers from similar issues).
>> 
>> The retain, release pair on %0 here suffers from the turning off of the 
>> guaranteed optimization. The guaranteed optimization says that any 
>> retain/release pairs on a guaranteed value can be eliminated if they are 
>> "semantic pairings". We had to turn off this optimization since without 
>> semantic-sil we were guessing what retain/release pairings were "semantic 
>> pairings". In certain cases this would result in mispairings so we had to 
>> turn it off in the general case. Specifically, consider the following straw 
>> man:
>> 
>> ----
>> strong_retain %x
>> ...
>> strong_release obfuscated(%x)
>> ...
>> strong_retain obfuscated'(%x)
>> ...
>> strong_release %x
>> ----
>> 
>> in this case we would pair/remove the outer retain, release pair yielding:
>> 
>> ----
>> strong_release obfuscated(%x)
>> ...
>> strong_retain obfuscated'(%x)
>> ----
>> 
>> While the reference count on %x is still balanced, since the retain count 
>> balancing is in inverse order this can result in pre-mature releases and 
>> use-after-frees. Once semantic-sil is complete, all pairings are explicit in 
>> the IR so such a bug can not happen. To work around that problem, today we 
>> are conservative and require any retain/release pair around a guaranteed 
>> parameter to be +1 before and have a use afterwards.
>> 
>> The second retain, release pair (i.e. %13) is slightly different since this 
>> suffers from the optimizer needing to make a connection in between the box 
>> in the enum and the value loaded from the box. Today we have enough 
>> knowledge in the optimizer to make the first connection without issue via 
>> the RCIdentity analysis. We do not make the connection though in between the 
>> box being guaranteed and the value loaded from the box being effectively 
>> guaranteed as a result. Even if we made that connection today, I am not sure 
>> if in the general case it would be safe. With semantic-sil all of those 
>> problems go away.
>> 
>> For both of these, I think that by using appropriate inter procedural 
>> effects analysis (which we have), we could teach the ARC optimizer to be 
>> more aggressive around guaranteed. The specific properties would be that:
>> 
>> 1. No side-effects (ignoring retain/releases).
>> 2. No retains/releases that are unbalanced.
>> 3. No escapes.
>> 
>> I would have to think in more detail in order to be sure though.
>> 
>> # Appendix
>> 
>> ## Local Retain/Release Pairs
>> 
>> There are two types of retain/release pairs in Swift, function local and 
>> non-local. An example of a local retain/release is when one creates a new 
>> binding, i.e.:
>> 
>> ----
>> func f(x: Klass) {
>> let xhat = x
>> ...
>> }
>> ----
>> 
>> When the assignment to xhat occurs we retain x and at the end of the 
>> function (assuming that xhat is not used), we release xhat. Since the 
>> retain/release pair are both in f, we are able to potentially pair and 
>> eliminate them without needing to modify the call graph. In contrast, a 
>> function non-local pairing is created when a parameter is passed at +1, e.g.:
>> 
>> ----
>> func g(x: Klass) { ... x is used but not consumed ... }
>> func f(x: Klass) {
>> g(x)
>> }
>> ----
>> 
>> In this case when we call g (ignoring any peepholes) we retain x first in f 
>> (the callee) and then release x in g (the caller). This is bad for 
>> optimization purposes since it means that we can not eliminate the 
>> retain/release pair without modifying the call graph or inlining. For 
>> non-resilient functions, we of course can/do inline and modify the callgraph 
>> via function signature optimization. In the case of resilient functions this 
>> is impossible by the nature of resilience.
>> 
>>> 
>>> -Chris
>>> 
>>> 
>>> 
>>>> On Dec 27, 2017, at 9:19 PM, Félix Cloutier via swift-evolution 
>>>> <swift-evolut...@swift.org> wrote:
>>>> 
>>>> Running this on my MBP with 10 as the parameter: 
>>>> https://gist.github.com/zneak/ae33bb970a08632cfb2925e2049f9e7a 
>>>> 
>>>> I get a runtime of about 10 seconds, 45% of which is spent in 
>>>> retain/release calls according to Instruments (!!), and at least half of 
>>>> that from Expr.count. Looking at the IR, Swift generously sprinkles 
>>>> retain/release calls through the outlined copy method:
>>>> 
>>>>    • `self` is retained at the beginning of `count`
>>>>    • The values that you get out of destructuring are retained
>>>>            • Of course, when you get `count` on these, they are retained 
>>>> again
>>>> 
>>>> Of course, Expr.count cannot modify itself or its subexpressions because 
>>>> the functions are not mutating; in fact, no function in that program can 
>>>> mutate an enum case. Why, then, is Swift retaining/releasing `self` and 
>>>> the values obtained from destructured patterns? They can't go away. 
>>>> Shouldn't we be getting guaranteed references instead of owning references?
>>>> 
>>>> That seems to hit pattern-matching-heavy programs with indirect cases 
>>>> pretty hard, and it's pretty frustrating because that seems to be about 
>>>> the nicest way to write this program, and there's no workaround from the 
>>>> developer's perspective. I don't think that this is a fatal flaw of 
>>>> refcounting, but unless I'm missing something, that's sub-par behavior.
>>>> 
>>>> Félix
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolut...@swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> 
>>> _______________________________________________
>>> swift-dev mailing list
>>> swift-dev@swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-dev
>> 
> 

_______________________________________________
swift-dev mailing list
swift-dev@swift.org
https://lists.swift.org/mailman/listinfo/swift-dev

Reply via email to