Where Scala Native Fits

@RichardWhaling

Scala Love 2020

What's In This Talk:

How Scala Native's dual personalities

relate to two different ecosystems:

  • JVM Interop
    • GraalVM
    • Scala.js
    • "Classic Scala"
  • C Interop
    • Rust
    • Go
    • OCaml

And what this tells us about

where Scala Native is effective

Who I Am:

  • Data Engineer at M1 Finance
  • Scala Native contributor
  • Author of SN libraries
  • Author of this:

Caveat:

  • I don't speak for SN as a project
  • I don't speak for my employer
  • There are a lot of spicy takes in this talk
  • Nothing in this is a "versus"
  • if I talk about a language or technology, I like it
  • If I get something wrong or you disagree, let's talk afterwards in the Q&A breakout

Scala Native Is:

  • a plugin for scalac
  • provides AOT compilation for idiomatic Scala
  • an ergonomic `unsafe` DSL with low-level capabilities
  • we don't get the Java Standard Library for free

But what does Scala without Java mean?

three different aspects:

  • Scala features that are implemented by Java capabilities
  • Scala code that uses Java library code
  • Scala code within a larger Java application

Javalib in SN

  • like SJS, re-implement it in Scala
  • unlike SJS, native platform is less portable,
    • fewer built-in capabilities, also fewer limits
  • SJS re-implements Javalib on top of JS
  • SN re-implements Javalib on top of POSIX 

So how much coverage do we need?

 

  • Support as much of Java Standard Library as possible
  • Supporting core Scala language features is mandatory
  • Reflection, classloading, Netty, etc. are challenging
  • Obstacles for Akka, Spark

Graal

GraalVM's JIT is state-of-the-art for supporting dynamic and functional languages on the JVM

Graal-native-image (SubstrateVM) provides AOT compilation and a lightweight runtime for JVM bytecode

Both are awesome!

But SubstrateVM does not have GraalVM's JIT

Both work on Java bytecode, not Scala code

Graal

SN has advantages because it's optimizer has access to Scala code, whereas Graal only gets the JVM output of scalac

SN's optimizer and runtime has comparable performance to Graal, while outputting compact binaries

Graal is better at JVM platform coverage and features

SN is stronger at unsafe memory usage and idiomatic C interop

Next Up: C Interop

Scala + C

SN provides C interop + unsafe/low-level capabilties

But what do we use them for?

 

  • Many modern Scala libraries do not have JVM dependencies
  • Leverage POSIX and C libraries to provide high-level Scala API's
  • Examples:
    • ZIO
    • STTP
    • Haoyi-ecosystem
  • As the community moves away from Javaism, SN wins

scalanative.unsafe

the unsafe API provides the following capabilities:

  • Primitive Values
  • Pointers
  • Arrays
  • Structs
  • Functions

These are all traditionally taught in C, and not often directly exposed in higher-level languages.

My hot take - these are fundamental information structures for low-level programming, and we can do a better job than C at using them

Ptr[T]

val jPtr:Ptr[Int] = stackalloc[Int]
println(s"jPtr has value ${jPtr} and size ${sizeof[Ptr[Int]]} bytes")

val j:Int = !jPtr
println(s"j has value ${j} and size ${sizeof[Int]}")

!jPtr = 5
println(s"jPtr has value ${jPtr} and size ${sizeof[Ptr[Int]]} bytes")

val j2:Int = !jPtr
println(s"j2 has value ${j2} and size ${sizeof[Int]}, j has value ${j}")
  • A pointer denotes the address of a value in memory
  • Generally a 64-bit unsigned integer under the hood
  • Pointer values are created by explicit allocation
  • Pointers are read and updated with the dereference operator "!"
  • No addressOf/& operator
  • Better safety - can't break the seal on GC managed objects
  • Semantics related to reference and pointer types in Haskell/SML

Arrays via Ptr[T]

val arraySize = 16 * sizeof[Int]
val allocation:Ptr[Byte] = stdlib.malloc(arraySize)
val intArray = allocation.asInstanceOf[Ptr[Int]]
for (i <- 0 to 16) {
    intArray(i) = i * 2
}
for (i <- 0 to 16) {
    val address = intArray + i
    val item = intArray(i)
    val check = !(intArray + i) == intArray(i)
    println(s"item $i at address ${intArray + i} has value $item, check: $check")
}
// just to be safe
stdlib.free(allocation)
  • Arrays are really just pointers with arithmetic operators
  • Access by index is equivalent to addition and dereference
  • Address is incremented by offset times element size
  • Seeks are constant time because layout is uniform

What sn.unsafe gives us:

C-level performance for working with contiguous data
Clean model for interfacing with safe code

Syntax that is easier to write correctly than C

Explicitly model mutability as a type

Access to any C library!

And we still get:
state-of-the-art GC
full Scala FP capabilities
pattern matching
errors & exceptions

immutable data structures, etc

Questions so far?

Low-level Ecosystems

Repeating my caveat - this isn't a fight
Comparing to highlight different capabilities
Understanding where tools complement each other

Rust

  • Performance on par with C
  • Memory safety
  • No GC
  • Explicit ownership of values 
  • No HKT
  • Very nice type system & macros
  • C interop is good but increasingly discouraged

hot take - the Rust community is challenging the C model, but has yet to demonstrate generality

 

SN has advantages at very low-level code (pointers, arrays, bit-flipping) as well as high level (FP, GC, immutability)

Go

  • Very fast, garbage collected, compiled language
  • State-of-the-art concurrent runtime (goroutines)
  • Powerful built-in slice/map/channel types
  • No generics, no subclassing built-in collections
  • Very strong in distributed systems
  • C Interop available but problematic
  • Not meant for FP

Go is amazing as long as your domain is well aligned with its provided abstractions

Good at "systems" but at a higher level than C

OCaml

  • Typed Strict Functional Programming Language
  • Hindley-Milner type inference
  • An excellent object system
  • Excellent performance and GC
  • Compiles to native binaries
  • Great unikernel support (MirageOS)
  • JS as a target (Bucklescript)
  • Strong C interop and FFI
  • ReasonML provides friendlier syntax

I could keep going, OCaml is great!

 

SN has similar strengths but Scala community is larger

Subjectively - Scala is more productive & approachable

Where Scala Native Fits

Starting with Haoyi's list:

 

  • CLI
  • Sidecars/proxies
  • Mobile
  • Desktop
  • C Interop

 

http://www.lihaoyi.com/post/TheDeathofHypeWhatsNextforScala.html

 Where Scala Native Fits 

My additions:

 

  • Serverless
  • embedded
  • WebAssembly
  • future hardware

     

These are all more experimental and speculative

(But potentially very high impact)

Serverless

Creating a new process or VM to serve a request is very sensitive to startup time, binary size, memory usage etc.

Typically billed by seconds of execution x memory usage

SN's low footprint is very hard to beat here

 

https://github.com/rwhaling/native-lambda

Embedded

  • Embedded processors have gotten fast recently!
  • STM32H747XI - dual core ARM, 480Mhz with DSP
  • Runs Lua, Python, Javascript (and Tensorflow)
  • Strong DSP capabilities as well

 

Hardware support is always a challenge but doable

 

Shadaj Laddad has a 32-bit branch of SN that targets ARM

 

SN could be really great here:

flipping bits is the essence of embedded programming

WebAssembly

LLVM already includes WebAssembly as a target

 

Surprisingly analogous to embedded - 32 bit, resource constrained, also runs on Shadaj's fork

 

Hannes Rutz has run SN in a Web Audio oscillator unit

 

Open question: how does this relate to SJS?

 

Hot take: to the extent that wasm conquers the world, it demonstrates that the C/linear memory model is general!

Future Hardware

Two new classes of hardware peripherals:

1.  Non-volatile memory

  • byte-addressable durable storage
  • 5x-10x faster (lower latency) than flash
  • a few competing programming models:
    • file-like
    • mmap-like
    • KV-like
  • durable objects in-memory break GC semantics
  • probably break Rust's model also?

     

Future Hardware

Two new classes of hardware peripherals:

2.  High-speed (10Gbps) NIC

  • Exceeds the linux kernels network IO capability by 10x
  • Can fully exploit hardware with kernel bypass techniques:
    • NIC writes into host RAM directly
    • ringbuffer/queue-style IO
  • Potentially very low-latency (<5us)
    • Enables RDMA networking -
    • remote direct memory access
    • read/write from remote host's main memory
  • potential gamechanger for distributed systems

Future Hardware

Two new classes of hardware peripherals:

1.  Non-volatile memory

2.  High-speed (10Gbps) NIC

  • Both challenge the JVM object model
  • Both challenge Rust's ownership model
  • We are still discovering abstractions for these devices

 

A language and platform that can build novel abstractions on top of plain linear memory is ideally positioned for these changes

I feel like we could build something genuinely groundbreaking with SN and this class of hardware.

Thanks!

Questions?

Made with Slides.com