[swift-evolution] [Pitch] Retiring `where` from for-in loops

Charlie Monroe charlie at charliemonroe.net
Thu Jun 9 12:09:59 CDT 2016


Didn't originally do it, but added it and here are the results (in seconds):

-O none
guard: 27.8599720001221 (+0.08%)
if-continue: 29.0756570100784 (+4.00%)
where: 27.836905002594 (+0.00%)
filter: 46.8083620071411 (+68.15%)
lazy.filter: 33.3811990022659 (+19.91%)

-O fast

guard: 0.123715996742249 (+4.00%)
if-continue: 0.118164002895355 (+0.00%)
where: 0.118863999843597 (+0.59%)
filter: 0.520934045314789 (+40.85%)
lazy.filter: 0.132100999355316 (+11.79%)

Note that in order to prevent some compiler magic for ranges (not sure if there is any), I've done the following:

let arr = Array(range)

Also, I invoke the block() within timetest 100 times to get larger values, since there can be some minor changes in calling NSDate() - the first time it's called, +initialize may get called as well as the IMP might not be cached and with 0.04 seconds, it can make the result a bit off due to a lock during initialization of NSDate.

Nevertheless my argument against removing where from the for loops is that:

1) Without boilterplate guard/if, the performance is poor.
2) With the boilerplate, you add 3 lines of code. I do not like the one-liner if condition { continue }
3) I'd vote for keeping where next to the variable name as suggested by Brent.


> On Jun 9, 2016, at 4:22 PM, Erica Sadun <erica at ericasadun.com> wrote:
> 
> So how did the guard versions perform?
> 
> Sent from my iPad
> 
>> On Jun 9, 2016, at 4:27 AM, Charlie Monroe <charlie at charliemonroe.net> wrote:
>> 
>> 
>>>> On Jun 9, 2016, at 10:29 AM, Brent Royal-Gordon <brent at architechies.com> wrote:
>>>> 
>>>> I've taken the time to run a test, going through milion numbers (several times) using:
>>>> 
>>>> for i in arr { if i % 2 == 0 { continue } }
>>>> for i in arr where i % 2 == 0 { }
>>>> for i in arr.filter({ $0 % 2 == 0 }) { }
>>>> for i in arr.lazy.filter({ $0 % 2 == 0 }) { }
>>>> 
>>>> Results:
>>>> 
>>>> - plain for loop with if-continue: 27.19 seconds (+1.76%)
>>>> - with where: 26.72 seconds (+0.00%)
>>>> - .filter: 44.73 seconds (+67.40%)
>>>> - .lazy.filter: 31.66 seconds (+18.48%)
>>> 
>>> This is great data. I have a hard time imagining a little compiler work couldn't make if-continue as fast as for-where, but lazy.filter might be a taller order for it, and optimizing plain filter could actually change behavior.
>>> 
>>> A month or two ago, I actually fell into the "just use the higher-order functions" camp on this question, but I've been rethinking that more and more lately. Between the trailing closure incompatibility, the need to remember to use `lazy` to get decent performance, and now the noticeable speed difference even *with* lazy, I'm no longer convinced that answer is good enough.
>> 
>> There will IMHO always be noticeable overhead since you're calling a function which is then invoking a closure. When you look at what that means:
>> 
>> - thunks generated around the invocation, which are a few instructions
>> - new stack frame for each call (correct me if I'm wrong). 
>> 
>> So instead of a single `i % 2 == 0` (which is just 2-3 instructions, depending on the architecture and optimization settings), it will invoke the closure milion times, if the array contains a milion members.
>> 
>> Maybe I'm over-optimizing, but 18% seemed like a lot to me.
>> 
>> 
>>> 
>>> (Though I do think `while` is probably too niche to bother with as a first-class feature, and I am open to if-continue on the `where` clause.)
>>> 
>>> -- 
>>> Brent Royal-Gordon
>>> Architechies
>> 
> 



More information about the swift-evolution mailing list