Where Scala Native Fits

What's In This Talk:

  • Java Interop
    • SN Javallib
    • Scala.js
    • GraalVM
    • SubstrateVM
  • C Interop
    • SN Unsafe API
    • Rust
    • Go
    • OCaml
    • WebAssembly
  • Etc
    • Embedded
    • Serverless

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
  • There are a lot of spicy takes in this talk
  • Nothing in this is a "versus" - if I bother to 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 C-like capabilities
  • because of IP restrictions, we don't get the Java Standard Lib for free

But what does Scala without Java mean?

three models:

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

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

The Graal branding covers a few different projects:

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 run performance comparisons against Graal but not super recently

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

SN's optimizer 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

Where SN Fits

Haoyi's list:

  • CLI
  • Sidecars/proxies
  • Mobile
  • Desktop
  • C Interop
    • ZIO via libuv
    • TF
    • LMDB

 

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

Scala.js

Scala.js is a slam dunk for the browser

Most use cases call for JVM server, scala.js client

Scala.js gets a lot of great capabilities from the browser or from node.js

Eases implementation of Javalib because more is provided by libuv

JS toolchains are convoluted but much less fragmented than native compilation toolchains 

JVM Scala

Probably a win over SN for everyday CRUD backend apps

Akka and Spark unlikely to have SN support soon

As the community moves toward FP, legacy Java API's are less of a draw.

JVM is a poor fit for:
- CLI's
- embedded
- sidecars
- C interop

Scala + C

  • Many modern Scala libraries do not have JVM dependencies
  • SN provides C interop + unsafe/low-level capabilties
  • Leverage POSIX and C libraries to provide high-level Scala API's without Java deps
  • Examples:
    • ZIO
    • STTP
    • Haoyi-ecosystem
  • The more the community moves away from Javaism and toward open-source, community-driven, pure Scala, SN wins

scalanative.unsafe

  • 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?

That's what Scala Native is
- but what are the best use cases for it?

- how does it relate to Graal, to Rust, to Go?
- what does it need to fulfill its potential?

Ecosystems

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

 

Rust
Go
OCaml/Reason
Graal
Scala.js
JVM Scala

Rust

C-level performance and memory safety without GC overhead

 a discipline of explicit ownership of values, which can be intrusive

 not an FP language (no monads) but it's type system has a Hindley-Milner flavor

 

has unsafe features for C interop etc but increasingly discouraged

Rust

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

 

Multiple threads working on large shared data structures is an important domain but not universal


can you build a successful OS or a dynamic language in Rust?  can it provide a platform to build on or is it a walled garden?

SN's safe code is higher-level than safe Rust

FP and immutable collections are the heart of Scala

Go

high performance, garbage collected, state of the art concurrent runtime

quirky but powerful ​built-in slice, map, and channel types

 

no generics, no subclassing of built-in collections

 

lingua franca for kubernetes and distributed systems

C interop strongly discouraged

Go

Scala Native and Go have similar profiles - compiled, compact binary, high performance, GC

I think the ergonomics of Scala are better - monad-ish collection operations, chainable error handling

The semantics of channels and goroutines seem like a step back from the actor model, and Go does not empower the community to build abstractions

 

Go is not for FP

SN is much stronger at C interop than Go

OCaml

A strict FP language with HM type inference and a great object system as well

Excellent performance, native binaries

JS as a target via bucklescript

Reason provides a more conventional syntax

C interop very similar to SN

Applications in finance and HFT

OCaml

I *heart* OCaml and Reason (and Standard ML/NJ!)


Block/statement syntax is very helpful for systems programming, because POSIX is imperative

Reason seems to be targeting JS/React/Mobile more than server-side/CLI/etc

 

Risk of Reason/OCaml fragmentation


The Scala ecosystem is larger than the OCaml world

Where SN Fits

My additions:

 

  • serverless
  • embedded

  • WebAssembly
  • future hardware

Embedded

Embedded processors have gotten fast recently!

STM32H747XI - dual core ARM, 480Mhz with DSP
Can run Lua or Python or Tensorflow
Can do DSP on general-purpose devices in idiomatic C

Hardware support is always a challenge but doable
Shadaj Laddad has a 32-bit branch of SN that has run on ARM
A lot of work focusing on embedded would be a strategic decision for the dev team and community
SN could be really great here - flipping bits is the essence of embedded programming, we can beat Rust, Go, OCaml here on ergonomics and performance

WebAssembly

WebAssembly is surprisingly analogous to embedded - 32 bit, resource constrained
SN uses LLVM already so not a ton more work - Shadaj has demonstrated running SN code on his branch
Hannes Rutz has run SN in a Web Audio oscillator unit
SN has an advantage here because it includes its own GC
Watch Seb's talk on wasm as a SJS target - worth thinking through how SJS and SN complement each other here
SN's ergonomics are ideal for wasm's linear memory

 

To the extent that wasm conquers the world, it demonstrates that the C/linear memory model is general!

Serverless

As cloud/distributed computing technology matures, it is increasingly effective to run stateless computations in a "serverless" modality, scheduled ad hoc on free resources, as pioneered by AWS Lambda

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

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
  • CAP theorem applies in asynchronous networks
  • low-microsecond latency might be a game-changer

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.

Copy of Where Scala Native Fits

By Richard Whaling

Copy of Where Scala Native Fits

  • 392