[swift-evolution] [Review] SE-0007 Remove C-style for-loops with conditions and incrementers

Marc Knaup marc at knaup.koeln
Fri Dec 11 11:17:58 CST 2015


This does not take continue into account which would then require a
duplication of expr3.
It would also change the scope of variables defined in expr1
potentially causing
collisions or unexpected shadowing.

On Fri, Dec 11, 2015 at 5:27 PM, Erica Sadun via swift-evolution <
swift-evolution at swift.org> wrote:

> For many of these number-crunching performance-stretching scenarios, many
> I suggest once again, that if you're doing serious number crunching that
> Accelerate or similar approaches is to be preferred? As for c-style-for vs
> while, the two are mechanically convertible.
>
>
> Where heavy performance is not required, for-in is more readable,
> maintainable, and optimizable to a sufficient extent that I do not see it
> as a bar to conversion.
>
> -- E
>
>
> On Dec 11, 2015, at 8:44 AM, Paul Cantrell via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> Your revised results are now right in line with what I get in my test
> harness, so that’s reassuring!
>
> I’d quibble with this:
>
>
>    1. The optimized builds are still slower than the for-in “equivalent”
>    functionality.
>
>
> That’s not an accurate summary. Depending on precisely what’s in the loop,
> the for-in flavor is clocking in anywhere from 80% slower to 20% *faster*.
>
> None of this performance testing undercuts your entirely valid concerns
> about syntax. We have, I think, widespread agreement on the list that the
> C-style for is very rarely used in most Swift code in the wild — but if
> your usage patterns are unusual and you use it a lot, I can see why you’d
> be reluctant to part with it!
>
> It’s a question, then, of whether it’s worth having a leaner language at
> the expense of making some less-common code more verbose when optimized.
> I’m not sure that any of the C-style audits people have done on the list
> have been games. Are there other game developers on the list using Swift
> who could do the audit on their code?
>
> Cheers,
>
> Paul
>
>
> On Dec 11, 2015, at 2:33 AM, David Owens II <david at owensd.io> wrote:
>
> I don’t know what you did, your gist 404s.
>
> Here’s an update with the while-loop:
> https://gist.github.com/owensd/b438bea90075907fa6ec and using both i and
> j within the loops. This is a simple OS X framework project with unit tests.
>
> *Debug Build:*
>
>    - testZipStride - 2.496s
>    - testCStyleFor - 0.210s
>    - testWhileLoop - 0.220s
>
>
> *Release Build:*
>
>    - testZipStride - 0.029s
>    - testCStyleFor - 0.018s
>    - testWhileLoop - 0.019s
>
>
> I ran these tests from my MacBook Pro, the previous tests were from my
> iMac.
>
> When you use the sum += (i - j) construct, I think all you are ending up
> with a hot-path that the optimizer can end up optimizing better (my guess
> is that the i-j turns into a constant expression - after all, the
> difference is always 1, but I don’t know enough about the SIL
> representation to confirm that). If you use a code path where that
> expression is not constant time (again, assuming my suspicion is correct),
> the zip+stride is against slower.
>
> I would argue the following:
>
>
>    1. The code is not objectively easier to read or understand with the
>    zip+stride construct (arguably, they are not even semantically equivalent).
>    2. The debug builds are prohibitively slower, especially in the
>    context of high-performance requirement code (I’m doing a lot of
>    prototyping Swift in the context of games, so yes, performance matters
>    considerably).
>    3. The optimized builds are still slower than the for-in “equivalent"
>    functionality.
>    4. The optimizer is inconsistent, like all optimizers are (this is a
>    simple truth, not a value judgement - optimizers are not magic, they are
>    code that is run like any other code and can only do as well as they are
>    coded under the conditions they are coded against), at actually producing
>    similar results with code that ends up with slightly different shapes.
>    5. There is not functionally equivalent version of the code that I can
>    write that is not more verbose, while requiring artificial scoping
>    constructs, to achieve the same behavior.
>
>
> So no, there is no evidence that I’ve seen to reconsider my opinion that
> this proposal should not be implemented. If there is evidence to show that
> my findings are incorrect or a poor summary of the general problem I am
> seeing, then of course I would reconsider my opinion.
>
> -David
>
> On Dec 10, 2015, at 9:12 PM, Paul Cantrell <cantrell at pobox.com> wrote:
>
> Hold the presses.
>
> David, I found the radical differences in our results troubling, so I did
> some digging. It turns out that the zip+stride code:
>
>     var sum = 0
>     for (i, j) in zip(first.stride(to: 0, by: -1), second.stride(to: 0,
> by: -2)) {
>         if i % 2 == 0 { continue }
>         sum += 1
>     }
>
> …runs *much* faster if you actually use both i and j inside the loop:
>
>     var sum = 0
>     for (i, j) in zip(first.stride(to: 0, by: -1), second.stride(to: 0,
> by: -2)) {
>         if i % 2 == 0 { continue }
>         sum += *i-j*
>     }
>
> Weird, right? This is with optimization on (default “production” build).
> It smells like a compiler quirk.
>
> With that tweak, the zip+stride approach actually clocks in faster than
> the C-style for. Yes, you read that right: *faster*. Also smells like a
> quirk. Am I doing something fantastically stupid in my code? Or maybe it’s
> just my idiosyncratic taste in indentation? :P
>
> Here’s my test case, which was a command-line app with manual timing,
> followed by David’s dropped into the same harness, followed by David’s but
> with sum += i-j instead of sum += 1:
>
>     https://gist.github.com/pcantrell/6bbe80e630d227ed0262
>
> Point is: *no big performance difference here; even a performance
> advantage* (that is probably a compiler artifact).
>
> David and Thorsten, you might want to reconsider your reviews?
>
> Results:
>
> —————— Paul’s comparison ——————
>
> zip+stride
>
>   Iter 0: 0.519110977649689
>   Iter 1: 0.503385007381439
>   Iter 2: 0.503321051597595
>   Iter 3: 0.485216021537781
>   Iter 4: 0.524757027626038
>   Iter 5: 0.478078007698059
>   Iter 6: 0.503880977630615
>   Iter 7: 0.498068988323212
>   Iter 8: 0.485781013965607
>          ——————————————
>   Median: 0.524757027626038
>
> C-style
>
>   Iter 0: 0.85480797290802
>   Iter 1: 0.879491031169891
>   Iter 2: 0.851797997951508
>   Iter 3: 0.836017966270447
>   Iter 4: 0.863684952259064
>   Iter 5: 0.837742984294891
>   Iter 6: 0.839070022106171
>   Iter 7: 0.849772989749908
>   Iter 8: 0.819278955459595
>          ——————————————
>   Median: 0.863684952259064
>
> Zip+stride takes 0.607579217692143x the time of C-style for
>
> —————— David’s comparison ——————
>
> zip+stride
>
>   Iter 0: 1.15285503864288
>   Iter 1: 1.1244450211525
>   Iter 2: 1.24192994832993
>   Iter 3: 1.02782195806503
>   Iter 4: 1.13640999794006
>   Iter 5: 1.15879601240158
>   Iter 6: 1.12114900350571
>   Iter 7: 1.21364599466324
>   Iter 8: 1.10698300600052
>          ——————————————
>   Median: 1.13640999794006
>
> C-style
>
>   Iter 0: 0.375869989395142
>   Iter 1: 0.371365010738373
>   Iter 2: 0.356527984142303
>   Iter 3: 0.384984970092773
>   Iter 4: 0.367590010166168
>   Iter 5: 0.365644037723541
>   Iter 6: 0.384257972240448
>   Iter 7: 0.379297018051147
>   Iter 8: 0.363133013248444
>          ——————————————
>   Median: 0.367590010166168
>
> Zip+stride takes 3.09151491202482x the time of C-style for
>
> —————— David’s comparison, actually using indices in the loop ——————
>
> zip+stride
>
>   Iter 0: 0.328687965869904
>   Iter 1: 0.332105994224548
>   Iter 2: 0.336817979812622
>   Iter 3: 0.321089029312134
>   Iter 4: 0.338591992855072
>   Iter 5: 0.348567008972168
>   Iter 6: 0.34687602519989
>   Iter 7: 0.34755402803421
>   Iter 8: 0.341500997543335
>          ——————————————
>   Median: 0.338591992855072
>
> C-style
>
>   Iter 0: 0.422354996204376
>   Iter 1: 0.427953958511353
>   Iter 2: 0.403640985488892
>   Iter 3: 0.415378987789154
>   Iter 4: 0.403639018535614
>   Iter 5: 0.416707038879395
>   Iter 6: 0.415345013141632
>   Iter 7: 0.417587995529175
>   Iter 8: 0.415713012218475
>          ——————————————
>   Median: 0.403639018535614
>
> Zip+stride takes 0.838848518865867x the time of C-style for
>
> Program ended with exit code: 0
>
> Cheers,
>
> Paul
>
> On Dec 10, 2015, at 5:36 PM, David Owens II <david at owensd.io> wrote:
>
> Here’s my basic test case:
>
> let first = 10000000
> let second = 20000000
>
> class LoopPerfTests: XCTestCase {
>
>     func testZipStride() {
>         self.measureBlock {
>             var sum = 0
>             for (i, j) in zip(first.stride(to: 0, by:
> -1), second.stride(to: 0, by: -2)) {
>                 if i % 2 == 0 { continue }
>                 sum += 1
>             }
>             print(sum)
>         }
>     }
>
>     func testCStyleFor() {
>         self.measureBlock {
>             var sum = 0
>             for var i = first, j = second; i > 0 && j > 0; i -= 1, j -= 2 {
>                 if i % 2 == 0 { continue }
>                 sum += 1
>             }
>             print(sum)
>         }
>
>     }
>
> }
>
>
> Non-optimized timings:
>
>    - testCStyleFor - 0.126s
>    - testZipStride - 2.189s
>
>
> Optimized timings:
>
>    - testCStyleFor - 0.008s
>    - testZipStride - 0.015s
>
>
> That’s a lot worse than 34%; even in optimized builds, that’s 2x slower
> and in debug builds, that’s 17x slower. I think it’s unreasonable to force
> people to write a more verbose while-loop construct to simply get the
> performance they need.
>
> Also, the readability argument is very subjective; for example, I don’t
> find the zip version more readability. In fact, I think it obscures what
> the logic of the loop is doing. But again, that’s subjective.
>
> -David
>
> On Dec 10, 2015, at 2:41 PM, Paul Cantrell <cantrell at pobox.com> wrote:
>
> Is there any guarantee that these two loops have the exact same runtime
> performance?
>
> for (i, j) in zip(10.stride(to: 0, by: -1), 20.stride(to: 0, by: -2)) {
>    if i % 2 == 0 { continue }
>    print(i, j)
> }
>
> for var i = 10, j = 20; i > 0 && j > 0; i -= 1, j -= 2 {
>    if i % 2 == 0 { continue }
>    print(i, j)
> }
>
>
> In a quick and dirty test, the second is approximately 34% slower.
>
> I’d say that’s more than acceptable for the readability gain. If you’re in
> that rare stretch of critical code where the extra 34% actually matters,
> write it using a while loop instead.
>
> P
>
>
>
> On Dec 10, 2015, at 4:07 PM, David Owens II via swift-evolution <
> swift-evolution at swift.org> wrote:
>
>
> On Dec 10, 2015, at 1:57 PM, thorsten--- via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> Yes, performance is one thing neglected by the discussions and the
> proposal.
>
>
> This is my primary objection to to this proposal; it assumes (or
> neglects?) that all of the types used can magically be inlined to nothing
> but the imperative code. This isn’t magical, someone has to implement the
> optimizations to do this.
>
> Is there any guarantee that these two loops have the exact same runtime
> performance?
>
> for (i, j) in zip(10.stride(to: 0, by: -1), 20.stride(to: 0, by: -2)) {
>    if i % 2 == 0 { continue }
>    print(i, j)
> }
>
> for var i = 10, j = 20; i > 0 && j > 0; i -= 1, j -= 2 {
>    if i % 2 == 0 { continue }
>    print(i, j)
> }
>
>
> And can you guarantee that performance is relatively the same across debug
> and release builds? Because historically, Swift has suffered greatly in
> this regard with respects to the performance of optimized versus
> non-optimized builds.
>
> These types of optimizer issues are real-world things I’ve had to deal
> with (and have written up many blog posts about). I get the desire to
> simplify the constructs, but we need an escape hatch to write performant
> code when the optimizer isn’t up to the job.
>
> -David
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
>
>
>  _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151211/b0acb653/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Screen Shot 2015-12-08 at 3.54.37 PM.png
Type: image/png
Size: 93414 bytes
Desc: not available
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151211/b0acb653/attachment.png>


More information about the swift-evolution mailing list