Run Swiftly: precompiled Swift actions

The OpenWhisk serverless platform allows developers to run functions coded in different languages in the “cloud” without deploying or managing servers. The platform today offers first class support for Node.js, Python, Swift and Java.

An example of a Swift action in OpenWhisk is the following main function which returns a greeting:

func main(args: [String:Any]) -> [String:Any] {
if let name = args["name"] as? String {
return [ "greeting" : "Hello \(name)!" ]
} else {
return [ "greeting" : "Hello stranger!" ]
}
}

This code may be deployed to run in OpenWhisk with the command line interface wsk. Save the code above to a file hello.swift and run the following two commands to create the action and invoke it:

$ wsk action create helloSwift hello.swift
$ wsk action invoke hellowSwift --blocking | grep duration
"duration": 2985,

Swift actions, unlike other actions, have an expensive startup latency (called the “cold start” latency) which is incurred by the swiftc compiler. Using the CLI for a blocking invoke which returns the full activation record, the time spent executing the action is stored in the duration property. The activation above for helloSwift took nearly 3 seconds. Invoking the action again, we can see the action benefits from a “warm start” and the activation is much faster (28 milliseconds):

$ wsk action invoke hellowSwift --blocking | grep duration
"duration": 28,

I previously described a way to run precompiled binaries as OpenWhisk actions in this article but I will reprise it here for Swift actions. The methodology is mostly the same but I adapt it to allow for dynamic linking against the Swift runtime and libraries so that actions are smaller and leaner.

I’ll use the same hello.swift action — the goal is to precompile the binary and use it to run a Swift action. This will eschew the expensive compilation step and get the execution time of the action to be closer to the warm start instead.

To do this, we will need Docker to run the Swift action container locally. This will allow us to build the binary in the same environment that it will eventually run within, when using OpenWhisk.

# run an interactive Swift action container
docker run -it -v "$HOME:/owexec" openwhisk/swift3action bash
# now inside the docker shell
# install zip for convenience, to package the binary
apt-get install -y zip
# copy the source code and prepare to build it
cp /owexec/hello.swift /swift3Action/spm-build/main.swift
cat /swift3Action/epilogue.swift >> /swift3Action/spm-build/main.swift
echo '_run_main(mainFunction:main)' >> /swift3Action/spm-build/main.swift

# build and link (the expensive step)
/swift3Action/spm-build/swiftbuildandlink.sh
# create the zip archive
cd /swift3Action/spm-build
zip /owexec/hello.zip .build/release/Action
# exit the docker shell
exit

The steps above provide a recipe for compiling and linking the action and creating a Zip file from the resultant executable called Action. The build script swiftbuildandlink.sh expects the source code in a file called main.swift. You will need to tweak the script if you have more than one source file. The executable must be located at .build/release/Action inside the archive.

With the Zip file, you can now create a new Swift action from it and invoke it as before:

$ wsk action update helloSwiftly hello.zip --kind swift:3
$ wsk action invoke helloSwiftly --blocking | grep duration
"duration": 160,

The action cold-start is now significantly reduced (160 milliseconds).


If you’ve enjoyed reading this and want to learn more, you can check out the project on GitHub or join us on Slack. This new feature will be available to try in IBM Bluemix soon.