Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift: Compile Time versus Run Time

Swift: Compile Time versus Run Time

airspeedswift

May 22, 2015
Tweet

Other Decks in Programming

Transcript

  1. Compile(Time(vs(Run(Time:(Fight!

    View Slide

  2. This%is%a%needlessly%trollbai0ng%0tle.%The%two%things%are%not%
    mutually%exclusive%–%they%are%two%different%tools%for%different%jobs.

    View Slide

  3. Common%Themes%on%Stack%Overflow
    1. “My&God,&it’s&full&of&vars”
    2. Also&!&and&as&!"#$%&
    3. Obj;C&interop&can&be&quite&surprising
    4. People&love&doing&stuff&dynamically&at&runGme

    View Slide

  4. As#@OldManKris#pointed#out#to#me,#F##is#nice#in#making#their#var#
    "let mutable",#since#it#means#lazy#people#do#the#right#thing#by#
    default.
    Swi$%1.2,%of%course,%makes%as%a%lot%safer%by%changing%the%
    explodina>ng%version%to%as!%
    When%I%say%the%interop%can%be%surprising,%I%don‘t%think%this%is%a%
    problem,%just%the%inevitable%result%of%the%tradeoff%between%seamless%
    interop%and%magical%type>coersion.%Unfortunately%it%has%the%effect%
    of%seAng%up%expectaCons%for%beginners%that%you%can%as%between%
    types.%I%definitely%like%how%1.2%requires%more%of%these%casts%to%be%
    explicit.

    View Slide

  5. Second'Most'Popular'SO'Swi4'Ques6on
    How$do$I$make$this$work?
    func anymorph(a: Any, type: T.Type, f: T->()) {
    if let t = a as? T {
    f(t)
    }
    }

    View Slide

  6. “Second(most,popular”,based,on,my,non(scien6fic,observa6ons.1,
    The$goal$here$is$to$build$an$array$of$types$and$func5ons,$and$then$
    throw$a$value$and$that$array$at$this$func5on$and$have$it$figure$it$all$
    out.$Unfortunately,$this$technique$doesn‘t$work$quite$as$hoped.$The$
    type$of$T$is$determined$at$compile$5me$(that's$how$generics$work),$
    but$the$ques5oner$is$looking$for$a$runC5me$solu5on$that$takes$a$T$
    argument.
    1"The"most"popular"ques0on"being"“Why"doesn’t"this"[AnyObject]?"type"have"a"subscript"operator?”

    View Slide

  7. Screw&you,&Swi,!
    // I wish this was JavaScript
    var foo: AnyObject!
    —"@nicklockwood

    View Slide

  8. Eventually*the*ques.oner*gets*disheartened*when*they*discover*
    this.*They*some.mes*decide*Swi:*generics*"don‘t*work",*and*that*
    s.cking*with*AnyObject*is*the*way*forward.

    View Slide

  9. Is#this#code#“Swi.”?
    @objc class C {
    func doThing() { println("something") }
    }
    @objc class D {
    func doThing() { println("something else") }
    }
    func takeThing(o: AnyObject) {
    o.doThing()
    }
    takeThing(C())
    takeThing(D())

    View Slide

  10. Classes&C&and&D&have&nothing&in&common&but&the&name&of&a&
    method,&yet&you&can&pass&either&into&a&func8on&and&have&it&work.&It&
    does&this&through&the&magic&of&objc_MsgSend,&the&same&as&with&
    Objec8ve?C&code.&

    View Slide

  11. Dynamic(Typing
    ...like&other&built-in&language&pa2erns&like&inheritance,&is&useful&
    when&you&need&it.
    If#I#do#more#at#compile#/me,#not#run/me,#what's#in#it#for#me?

    View Slide

  12. The$inheritance$analogy$refers$to$the$long1standing$tradi3on$of$
    people$using$inheritance$(is1a)$when$they$want$aggrega3on$(is1
    implemented1in1terms1of).$“I$want$a$library$class$where$you$can$
    look$up$books$by$3tle,$so$I’ll$inherit$from$Dic3onary”.$For$as$long$as$
    inheritance$has$been$a$thing,$people$have$been$doing$this,$and$
    people$have$been$wri3ng$about$how$you$shouldn't$do$this.$No1one$
    is$saying$don’t$use$inheritance$and$classes2,$just$to$only$use$it$when$
    the$problem$you$need$to$solve$calls$for$it.$The$same$goes$for$
    dynamic$types$–$break$them$out$when$the$problem$will$benefit$
    from$them.
    2"This"is"a"lie,"plenty"of"people"(smart"people)"are"saying"this."See"Rob"Rix’s"great"talk"on"this"from"NSScotland.

    View Slide

  13. View Slide

  14. There‘s'now'a'long'list'of'great'talks'about'how'using'immutable'
    value'types'instead'can'help'reduce'the'complexity'of'your'code'
    and'make'it'easier'to'reason'about.'
    The$downside$of$these$talks$is$they're$a$li3le$bit...$“Programming,$
    You're$Doing$It$Wrong”$which,$while$it$might$be$true,$can$be$a$hard$
    pill$to$swallow.$So$how$about$a$more$sugarDcoated$one$–$staFc$
    value$types$and$compileDFme$opFmizaFon$have$the$potenFal$to$
    make$your$code$faster!
    In#case#you#don’t#recognize#him,#that's#John#McCarthy,#inventor#of#
    Lisp.

    View Slide

  15. How$many$Haskell$programmers$
    does$it$take$to$change$a$lightbulb?
    None,$you$need$a$new$house$with$a$
    different$lightbulb.

    View Slide

  16. This%is%a%cheap%shot,%of%course.%Haskell%can%be%fast.
    The$joke$comes$from$this$ar0cle$on$the$F##for#Fun#and#Profit$blog,$
    which$is$well$worth$a$read.

    View Slide

  17. Four%character+Codes
    Pack%four%character%bytes%into%a%324bit%integer:
    "utxt"
    = 'u'<<24 | 't'<<16 | 'x'<<8 | 't'
    = 1,970,567,284

    View Slide

  18. Credit'to'@UINT_MIN'for'the'FourCharacterCode'example,'
    which'is'a'much'more'useful'case'than'example'I'had'originally,'
    which'was'10'applicaAons'of'the'XorshiC'generator,'which'also'
    constantDfolds'at'compile'Ame:
    struct Seed {
    let x, y, z, w: UInt32
    }
    func xrand(seed: Seed) -> Seed {
    let t = seed.x ^ (seed.x << 11)
    return Seed(x: seed.y, y: seed.z, z: seed.w,
    w: seed.w ^ (seed.w >> 19) ^ (t ^ (t >> 8)))
    }
    var r = Seed(x: 2014, y: 12, z: 29, w: 2015)
    let x = reduce(0..<10, r) { r, _ in xrand(r) }
    println(String(x.w, radix: 16))

    View Slide

  19. The$Xorshi*$example$is$interes2ng$as$it$comes$from$a$
    benchmarking$ques2on$on$Stack$Overflow.$It’s$an$easy$trap$to$fall$
    into$when$you’re$wri2ng$a$benchmarking$test$that$your$code$isn’t$
    actually$running$because$your$inputs$are$constant$and$the$func2on$
    you$want$to$test$isn't$running$at$all!$If$the$results$are$too$good$to$be$
    true,$try$randomizing$your$input$and$running$them$again.
    In#the#original#ques0on’s#code,#they#were#using#a#closure#to#capture#
    variables#that#held#the#state#between#itera0ons.#Closures#like#this#
    can#also#be#a#barrier#to#compiler#op0miza0on.#Once#that#was#
    removed,#SwiA#handily#beat#the#Delphi#version.

    View Slide

  20. Four%character+Codes
    extension FourCharCode {
    init(fromFourCharString str: StaticString) {
    precondition(str.byteSize == 4)
    self = str.withUTF8Buffer { buf in
    var result: FourCharCode = 0
    for byte in buf {
    result <<= 8
    result |= FourCharCode(byte)
    }
    return result
    }
    }
    }

    View Slide

  21. Four%character+Codes
    let utxt = FourCharCode(fromFourCharString: "utxt")
    println("0x" + String(utxt, radix: 16))
    // prints 0x75747874

    View Slide

  22. The$radix$version$of$String.init$is$very$convenient$if$you're$
    debugging$bitshi4ing$code$(use$radix: 2$to$see$the$binary$
    equivalent).
    That%screenshot%is%from%Hopper%which%is%a%very%useful%tool%for%
    examining%assembly%output.%Alterna;vely%you%can%run%swiftc%with%
    -emit-assembly,%but%hopper%adds%a%lot%of%features%that%make%it%
    easier%to%view.

    View Slide

  23. Using&reduce
    extension FourCharCode {
    init(fromFourCharString str: StaticString) {
    precondition(str.byteSize == 4)
    self = str.withUTF8Buffer { buf in
    reduce(buf, 0) { code, byte in
    (code << 8) | FourCharCode(byte)
    }
    }
    }
    }

    View Slide

  24. It#could#be#considered#a#bit#iffy#to#extend#FourCharCode#with#an#
    init#like#this,#as#it’s#just#a#typealias#for#UInt32#–#so#UInt32#now#
    has#this#method.#But#that's#not#a#huge#deal#since#you#invoke#it#with#
    an#explicit#argument#name.#But#it's#definitely#a#reason#not#to#use#
    IntegerLiteralConvertible.#The#other#reason#being,#that#
    seems#to#defeat#the#constant#folding#too#(even#if#you#typealias#
    StringLiteralType#to#be#StaticString)

    View Slide

  25. Using&String
    extension FourCharCode {
    init(fromFourCharString str: String) {
    precondition(str.byteSize == 4)
    self = reduce(str.utf8, 0) { code, byte in
    (code << 8) | UInt32(byte)
    }
    }
    }

    View Slide

  26. Nibble&Sort&Challenge
    Write&a&func,on&that&sorts&all&the&nibbles&in&a&644bit&word.
    nibble_sort_word(0xbadbeef)
    // returns 0xfeedbba000000000

    View Slide

  27. I"wrote"an"ar*cle"covering"this"challenge"and"the"Swi4"
    implemen*on"if"you"want"more"details.

    View Slide

  28. func read_nibble(w: UInt64, i: UInt64) -> UInt64 {
    let res = w >> (i * 4)
    return res & 0xf
    }
    func write_nibble(inout w: UInt64, i: UInt64, v: UInt64) {
    var mask: UInt64 = 0xf
    mask <<= (i * 4);
    w &= ~mask
    var prom = v;
    prom <<= (i * 4)
    w |= prom
    }

    View Slide

  29. func nibble_sort_word(var arg: UInt64) -> UInt64 {
    for var i: UInt64 = 0; i < 16; ++i {
    var min = i;
    for var j = i+1; j < 16; ++j {
    if read_nibble(arg, j) < read_nibble(arg, min) {
    min = j
    }
    }
    if (min != i) {
    var tmp = read_nibble(arg, i)
    write_nibble(&arg, i, read_nibble(arg, min))
    write_nibble(&arg, min, tmp)
    }
    }
    return arg;
    }

    View Slide

  30. struct Nibbles: MutableCollectionType {
    var val: UInt64
    var startIndex: UInt64 { return 0 }
    var endIndex: UInt64 { return 16 }
    subscript(idx: UInt64) -> UInt64 {
    get {
    return (val >> (idx*4)) & 0xf
    }
    set(n) {
    let mask = 0xf << (idx * 4)
    val &= ~mask
    val |= n << (idx * 4)
    }
    }
    } // + generator impl.

    View Slide

  31. Now$use$the$standard$library’s$sort
    var col = Nibbles(val: 0xbadbeef)
    sort(&col)
    col.val // is 0xfeedbba000000000
    sizeof(UInt64) == sizeof(Nibbles)
    write/read/sort!version:!400µs
    sort(Nibbles)!version:!270µs

    View Slide

  32. The$Swi($standard$library’s$sort$appears$to$be$an$implementa6on$
    of$an$introsort$(which$is$itself$a$hybrid$of$quicksort$and$mergesort),$
    but$that$drops$to$an$inser6on$sort$if$the$collec6on$to$be$sorted$is$
    sufficiently$small$for$that$to$be$faster.$The$LLVM$C++$std$lib$
    implementa6on$does$something$similar.

    View Slide

  33. Zero%cost)Abstrac-ons
    struct Int {
    var value: Builtin.Int64
    }
    func +(lhs: Int, rhs: Int) -> Int {
    precondition(no overflow)
    }

    View Slide

  34. Zero%cost)Abstrac-ons
    func f(x: P) { }
    versus
    func f(x: T) { }

    View Slide

  35. For$more$on$the$differences$between$generics$and$func5ons$that$
    take$protocols$as$arguments,$see$my$post;talk$ar5cle.

    View Slide

  36. Zero%cost)Abstrac-ons
    func f(x: P) { }
    versus
    func f(x: Int) { }

    View Slide

  37. You$can$write$your$own$fast$generic$func1ons!
    func insertionsort
    where C.Index: RandomAccessIndexType, C.Generator.Element: Comparable>
    (inout source: C) {
    for i in indices(source) {
    for var j=i; j!=source.startIndex && source[j]swap(&source[j-1], &source[j])
    }
    }
    }
    This%version:%420µs%!

    View Slide

  38. Don’t&uninten*onally&rewrite&std&lib&func*ons!
    func insertionsortwhere C.Index: RandomAccessIndexType, C.Generator.Element: Comparable>
    (inout source: C) {
    if isEmpty(source) { return }
    for i in source.startIndex.successor()..let x = source[i]
    var j = i
    while j != source.startIndex {
    let k = j.predecessor()
    let cmp = source[k]
    if x < cmp {
    source[j] = cmp
    j = k
    }
    else { break }
    }
    source[j] = x
    }
    }

    View Slide

  39. Stay%Familiar%With%the%Swi/%Standard%Library
    developer.apple.com/swi0/blog
    Xcode&and&Swi,&Release&Notes
    swi$doc.org
    airspeedvelocity.net

    View Slide