Merge pull request #5985 from roc-lang/update-copy

Update /fast
This commit is contained in:
Richard Feldman 2023-11-15 12:00:06 -05:00 committed by GitHub
commit 96757bcbc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 61 additions and 27 deletions

View File

@ -6,7 +6,7 @@ Roc code is designed to build fast and run fast...but what does "fast" mean here
What "fast" means in embedded systems is different from what it means in games, which in turn is different from what it means on the Web. To better understand Rocs performance capabilities, let's look at the upper bound of how fast optimized Roc programs are capable of running, and the lower bound of what languages Roc should generally outperform.
**Limiting factors: memory management and async I/O.** Part of Roc's design is that it is a [memory-safe](https://en.wikipedia.org/wiki/Memory_safety) language with [automatic memory management](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)#Reference_counting). Automatic memory management has some unavoidable runtime overhead, and memory safety rules out certain performance optimizations—which is why [unsafe Rust](https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html) can outperform safe Rust. This gives Roc a lower performance ceiling than languages which support memory unsafety and manual memory management, such as C, C++, Zig, and Rust. Another part of Roc's design is that I/O operations are done using a lightweight state machine so that they can be asynchronous. This has potential performance benefits compared to synchronous I/O, but it also has some unavoidable overhead.
**Limiting factors: automatic memory management and always-async I/O.** Part of Roc's design is that it is a [memory-safe](https://en.wikipedia.org/wiki/Memory_safety) language with [automatic memory management](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)#Reference_counting). Automatic memory management has some unavoidable runtime overhead, and memory safety based on static analysis rules out certain performance optimizations—which is why [unsafe Rust](https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html) can outperform safe Rust. This gives Roc a lower performance ceiling than languages which support memory unsafety and manual memory management, such as C, C++, Zig, and Rust. Another part of Roc's design is that I/O operations are done using a lightweight state machine so that they can be asynchronous. This has potential performance benefits compared to synchronous I/O, but it also has some unavoidable overhead.
**Faster than dynamic or gradual languages.** As a general rule, Roc programs should have almost strictly less runtime overhead than equivalent programs written in languages with dynamic types and automatic memory management. This doesn't mean all Roc programs will outperform all programs in these languages, just that Roc should have a higher ceiling on what performance is achievable. This is because dynamic typing (and gradual typing) requires tracking types at runtime, which has unavoidable overhead. Roc tracks types only at compile time, and tends to have [minimal (often zero) runtime overhead](https://guide.handmade-seattle.com/c/2021/roc-lang-qa/) for language constructs compared to the top performers in industry. For example, Roc's generics, records, functions, numbers, and tag unions have no more runtime overhead than they would in their Rust or C++ equivalents.
@ -14,15 +14,15 @@ When [benchmarking compiled Roc programs](https://www.youtube.com/watch?v=vzfy4E
### Current Progress
Most of Roc's data structures are already close to their theoretical limit in terms of performance, at least without changing their behavior or introducing memory unsafety. However, there is plenty of room for further compiler optimizations; current optimizations include only [LLVM](https://llvm.org/), [Morphic](https://www.youtube.com/watch?v=F3z39M0gdJU&t=3547s), and [Perceus](https://www.microsoft.com/en-us/research/uploads/prod/2020/11/perceus-tr-v1.pdf). Promising examples of potential future optimizations include closure-aware [inlining](https://en.wikipedia.org/wiki/Inline_expansion), [automatic deforestation](https://www.cs.tufts.edu/~nr/cs257/archive/duncan-coutts/stream-fusion.pdf), and full [compile-time evaluation](https://en.wikipedia.org/wiki/Constant_folding) of top-level declarations.
Most of Roc's data structures are already close to their theoretical limit in terms of performance, at least without changing their behavior or introducing memory unsafety.
An open design question is how atomic reference counting should work in the context of platforms. Roc uses automatic reference counting (with some compile-time optimizations) to manage memory, and updating reference counts runs a lot faster if you don't need to guard against [data races](https://en.wikipedia.org/wiki/Race_condition#Data_race). If Roc data structures are shared across threads (which might or might not come up, depending on the platform), then in some cases [atomic](https://en.wikipedia.org/wiki/Linearizability#Primitive_atomic_instructions) reference counting might be necessary, which is slower.
However, there is plenty of room for further compiler optimizations. For example, we have [an implementation](https://ayazhafiz.com/articles/23/a-lambda-calculus-with-coroutines-and-heapless-closures) of [defunctionalization](https://blog.sigplan.org/2019/12/30/defunctionalization-everybody-does-it-nobody-talks-about-it/) (based on [this paper](https://dl.acm.org/doi/pdf/10.1145/3591260)) which gives us stack-allocated closures (among other things). However, the current implementation has run into enough problems that we're planning to switch to the more traditional (but slower) heap-based closures until we can find a way to properly implement guaranteed defunctionalization. (If you're interested in getting involved in finding an implementation of the faster approach that works, [we'd love to hear from you!](https://github.com/roc-lang/roc/issues/5969))
A design of "always use atomic reference counts" would be unnecessarily slow for platforms where atomicity isn't needed. A design of "never use atomic reference counts" would mean sharing Roc data structures across threads would always require deeply copying the entire data structure (which has worked well for Erlang, but which might not work as well for Roc). It seems likely that the best design is for the compiler to sometimes choose one and sometimes choose the other, but the exact design to go with is currently an open question.
Other current optimizations include [LLVM](https://llvm.org/), [Morphic](https://www.youtube.com/watch?v=F3z39M0gdJU&t=3547s), [Perceus](https://www.microsoft.com/en-us/research/uploads/prod/2020/11/perceus-tr-v1.pdf), and tail recursion optimization (including [modulo cons](https://en.wikipedia.org/wiki/Tail_call#Tail_recursion_modulo_cons)). Promising examples of potential future optimizations include closure-aware [inlining](https://en.wikipedia.org/wiki/Inline_expansion), [automatic deforestation](https://www.cs.tufts.edu/~nr/cs257/archive/duncan-coutts/stream-fusion.pdf), and full [compile-time evaluation](https://en.wikipedia.org/wiki/Constant_folding) of top-level declarations.
## Fast Feedback Loops
The goal is for Roc to provide fast feedback loops by making builds normally feel "instant" except on truly massive projects. It's a concrete goal to have them almost always complete in under 1 second on the median computer being used to write Roc (assuming that system is not bogged down with other programs using up its resources), and ideally under the threshold at which humans typically find latency perceptible (around 100 milliseconds). Hot code loading can make the feedback loop even faster, by letting you see changes without having to restart your program.
One of Roc's goals is to provide fast feedback loops by making builds normally feel "instant" except on truly enormous projects. It's a concrete goal to have them almost always complete in under 1 second on the median computer being used to write Roc (assuming that system is not bogged down with other programs using up its resources), and ideally under the threshold at which humans typically find latency perceptible (around 100 milliseconds). Hot code loading can make the feedback loop even faster, by letting you see changes without having to restart your program.
Note that although having fast "clean" builds (without the benefit of caching) is a goal, the "normally feels instant" goal refers to builds where caching was involved. After all, the main downside of build latency is that it comes up over and over in a feedback loop; the initial "clean" build comes up rarely by comparison.
@ -31,21 +31,27 @@ Note that although having fast "clean" builds (without the benefit of caching) i
`roc check` type-checks your code and reports problems it finds. `roc build` also does this, but it additionally
builds a runnable binary of your program. You may notice that `roc build` takes much longer to complete! This is because
of two projects that are underway but not completed yet:
- *Development backend* refers to generating machine code directly instead of asking [LLVM](https://llvm.org/) to generate it. LLVM is great at generating optimized machine code, but it takes a long time to generate it—even if you turn off all the optimizations (and `roc` only has LLVM perform optimizations when the `--optimize` flag is set). The dev backend is currently implemented for WebAssembly, which you can see in the [Web REPL](https://www.roc-lang.org/repl), and in `roc repl` on x64 Linux. Work is underway to implement it for `roc build` and `roc run`, as well as macOS, Windows, and the ARM versions of all of these.
- *Surgical linking* refers to a fast way of combining the platform and application into one binary. Today, this works on Linux, Windows, and WebAssembly. `roc build` on macOS is noticeably slower because it falls back on non-surgical linking.
- *Development backend* refers to generating machine code directly instead of asking [LLVM](https://llvm.org/) to generate it. LLVM is great at generating optimized machine code, but it takes a long time to generate it—even if you turn off all the optimizations (and `roc` only has LLVM perform optimizations when the `--optimize` flag is set). The dev backend is currently implemented for WebAssembly, which you can see in the [Web REPL](https://www.roc-lang.org/repl), and in `roc repl` except on Windows. Work is underway to implement it for `roc build` and `roc run`, as well as macOS, Windows, and the ARM versions of all of these.
- *Surgical linking* refers to a fast way of combining the platform and application into one binary. Today, this works on x64 Linux, x64 Windows, and WebAssembly. `roc build` on macOS is noticeably slower because it falls back on non-surgical linking.
Here's a table summarizing the current progress:
Target | Dev backend | Surgical linking |
---------------------------|-------------|-------------------|
WebAssembly | yes | yes |
macOS ARM (Apple Silicon) | repl only | |
macOS x64 (Intel) | repl only | |
Linux ARM | repl only | |
Linux x64 | repl only | yes |
Windows x64 | | yes |
macOS Intel (x64) | | |
Linux ARM | | |
Windows ARM | | |
macOS Apple Silicon (ARM) | | |
Once we have full coverage, `roc build` (and `roc run` and `roc test`, which also perform builds) should take only a bit longer than `roc check`.
The next major performance improvement will be caching. Currently, `roc` always builds everything from scratch. Most of the time, it could benefit from caching some of the work it had done in a previous build, but today it doesn't do that. There's a design for the caching system, but essentially none of the implementation has started yet. Hot code loading will be the next major improvement after caching, but it requires full dev backend coverage, and does not have a concrete design yet.
## Friendly
In addition to being fast, Roc also aims to be a friendly programming language.
[What does _friendly_ mean here?](/wip/friendly)

View File

@ -2,9 +2,9 @@
Roc prioritizes being a user-friendly language. This impacts the syntax, semantics, and tools Roc ships with.
## Syntax and source code formatter
## Syntax and Formatter
Roc's syntax isn't trivial, but there also isn't much of it to learn. Its design is generally uncluttered and unambiguous. A goal is that you can normally look at a piece of code and quickly get an accurate mental model of what it means, without having to think through several layers of indirection. Here are some examples:
Roc's syntax isn't trivial, but there also isn't much of it to learn. It's designed to be uncluttered and unambiguous. A goal is that you can normally look at a piece of code and quickly get an accurate mental model of what it means, without having to think through several layers of indirection. Here are some examples:
- `x = combine y z` always declares a new constant `x` (Roc has [no mutable variables, reassignment, or shadowing](/functional)) to be whatever the `combine` function returns when passed the arguments `y` and `z`. (Function calls in Roc don't need parentheses or commas.)
- `user.email` always accesses the `email` field of a record named `user`. (Roc has no inheritance, subclassing, or proxying.)
@ -13,7 +13,7 @@ Roc's syntax isn't trivial, but there also isn't much of it to learn. Its design
Roc also ships with a source code formatter that helps you maintain a consistent style with little effort. The `roc format` command neatly formats your source code according to a common style, and it's designed with the time-saving feature of having no configuration options. This feature saves you all the time that would otherwise be spent debating which stylistic tweaks to settle on!
## Helpful compiler
## Helpful Compiler
Roc's compiler is designed to help you out. It does complete type inference across all your code, and the type system is *sound*. This means you'll never get a runtime type mismatch if everything type-checked (including null exceptions; Roc doesn't have the [billion-dollar mistake](https://en.wikipedia.org/wiki/Null_pointer#History)), and you also don't have to write any type annotations for the compiler to be able to infer all the types in your program.
@ -44,7 +44,7 @@ Tip: You can convert between integers and fractions using functions like
If you like, you can run a program that has compile-time errors like this. (If the program reaches the error at runtime, it will crash.) This lets you do things like trying out code that's only partially finished, or running tests for one part of your code base while other parts have compile errors. (Note that this feature is only partially completed, and often errors out; it has a ways to go before it works for all compile errors!)
## Serialization inference
## Serialization Inference
- Type inference is used for schema inference, but you can also spell it out if you like
- Reports errors immediately
@ -64,9 +64,16 @@ In the future, there are plans to add built-in support for [benchmarking](https:
- also note: future plan to cache tests so we only re-run tests whose answers could possibly have changed. also maybe note: tests that don't perform I/O are guaranteed not to flake b/c pure functions.
## Future plans
## Future Plans
- Package manager (Currently just URLs, content-hashed to make them immutable) with searchable index and no installation step, global cache of immutable downloads instead of per-project folders (no need to .gitignore anything)
- Step debugger with replay
- Customizable "linter" (e.g. code mods, project-specific rules to enforce)
- Editor plugin ecosystem that works across editors, where plugins ship with packages
- `roc edit`
## Functional
Besides being designed to be [fast](/wip/fast) and friendly, Roc is also a functional programming language.
[What does _functional_ mean here?](/wip/functional)

View File

@ -120,4 +120,4 @@ Pure functions are notably amenable to compiler optimizations. Roc already takes
## Get started
If this design sounds interesting to you, you can give Roc a try by heading over to the [tutorial](https://www.roc-lang.org/tutorial)!
If this design sounds interesting to you, you can give Roc a try by heading over to the [tutorial](/tutorial)!

View File

@ -83,18 +83,23 @@ Here are some examples of how it can be used today.
<div role="presentation" class="home-examples-container">
<div role="presentation" class="home-examples-column">
<h3 class="home-examples-title">Command-Line Interfaces</h3>
<p>You can use Roc to create command-line interfaces (CLIs) and scripts. For example, the HTML for this website is generated using a simple Roc script. You can see <a href="https://github.com/roc-lang/roc/blob/main/www/wip_new_website/main.roc">the code for it</a> in the main Roc source code repository.</p>
<p>For a more flexible starting point, <a href="https://github.com/roc-lang/basic-cli">roc-lang/basic-cli</a> is a popular platform for building your own CLI tools. It's the most mature Roc platform today.</p>
<h3 class="home-examples-title">Web Servers</h3>
<p>You can use Roc to build web servers. <a href="https://github.com/roc-lang/basic-webserver">basic-webserver</a> is a platform with
a simple interface: you write a function which takes a <code>Request</code>, performs I/O, and returns a <code>Response</code>.</p>
<p>Behind the scenes, the platform uses Rust's high-performance <a href="https://docs.rs/hyper/latest/hyper/">hyper</a> and <a href="https://tokio.rs/">tokio</a> libraries to execute your Roc function on incoming requests.</p>
<p>For database access, <a href="https://github.com/agu-z/roc-pg">roc-pg</a> offers a way to access a <a href="https://www.postgresql.org/">PostgreSQL</a> database with your Roc types checked against the types in the database's schema.</p>
</div>
<div role="presentation" class="home-examples-column">
<h3 class="home-examples-title">Web Servers</h3>
<p>You can also use Roc to build web servers. <a href="https://github.com/roc-lang/basic-webserver">roc-lang/basic-webserver</a> is an upcoming platform for doing this.</>
<h3 class="home-examples-title">Command-Line Interfaces</h3>
<p>You can also use Roc to create command-line interfaces (CLIs) and scripts. Roc compiles to binary executables, so these can run on devices that don't have Roc installed.</p>
<p>For example, the HTML for this website is generated using a simple Roc script. You can see <a href="https://github.com/roc-lang/roc/blob/main/www/wip_new_website/main.roc">the code for it</a> in the main Roc code repository.</p>
<p>For a more flexible starting point, <a href="https://github.com/roc-lang/basic-cli">basic-cli</a> is a popular platform for building your own CLI tools. It's the most mature Roc platform today.</p>
</div>
<div role="presentation" class="home-examples-column">
<h3 class="home-examples-title">Embedding</h3>
<p>You can call Roc functions from other languages. See <a href="https://github.com/roc-lang/roc/tree/main/examples">basic examples</a> of how to call Roc functions from Python, Node.js, Swift, WebAssembly, and JVM languages. Any function that supports C interop can call Roc functions, using similar techniques to the ones these examples use.</p>
<p>Most of those examples are minimal proofs of concept. <a href="https://github.com/vendrinc/roc-esbuild">roc-esbuild</a> is a work in progress thats used at <a href="https://www.vendr.com/careers">Vendr</a> to call Roc functions from Node.js.</p>
<p>You can call Roc functions from other languages. There are several <a href="https://github.com/roc-lang/roc/tree/main/examples">basic examples</a> of how to call Roc functions from Python, Node.js, Swift, WebAssembly, and JVM languages.</p>
<p>Any language that supports C interop can call Roc functions, using similar techniques to the ones found in these examples.</p>
<p>Most of those are minimal proofs of concept, but <a href="https://github.com/vendrinc/roc-esbuild">roc-esbuild</a> is a work in progress thats used at <a href="https://www.vendr.com/careers">Vendr</a> to call Roc functions from Node.js.</p>
</div>
</div>

View File

@ -1,7 +1,7 @@
app "roc-website"
packages { pf: "../../examples/static-site-gen/platform/main.roc" }
imports [
pf.Html.{ Node, html, head, body, header, footer, div, main, text, nav, a, link, meta, script },
pf.Html.{ Node, html, head, body, header, footer, div, span, main, text, nav, a, link, meta, script },
pf.Html.Attributes.{ attribute, content, name, id, href, rel, lang, class, title, charset, color, ariaLabel, type, role },
InteractiveExample,
]
@ -56,6 +56,12 @@ view = \page, htmlContent ->
else
[text htmlContent]
bodyAttrs =
if page == "index.html" then
[id "homepage-main"]
else
[]
html [lang "en", class "no-js"] [
head [] [
meta [charset "utf-8"],
@ -81,7 +87,7 @@ view = \page, htmlContent ->
# Otherwise, this will work locally and then fail in production!
script [] [text "document.documentElement.className = document.documentElement.className.replace('no-js', '');"],
],
body [] [
body bodyAttrs [
viewNavbar page,
main [] mainBody,
footer [] [
@ -101,7 +107,9 @@ viewNavbar = \page ->
if isHomepage then
div [role "presentation"] [] # This is a spacer for the nav bar
else
a [id "nav-home-link", href "/wip/", title "The Roc Programming Language"] [rocLogo]
a
[id "nav-home-link", href "/wip/", title "The Roc Programming Language"]
[rocLogo, span [class "home-link-text"] [text "Roc"]]
header [id "top-bar"] [
nav [ariaLabel "primary"] [

View File

@ -207,12 +207,15 @@ h2 {
font-size: 3rem;
border-bottom: 4px solid var(--dark-cyan);
margin: 0;
margin-top: 72px;
margin-bottom: 36px;
padding: 36px 0;
color: var(--heading-color);
}
#homepage-main h2 {
margin-top: 72px; /* On the homepage, these need to be spaced out more. */
}
h2 a {
color: var(--heading-color);
}
@ -250,6 +253,11 @@ h3 {
padding: 4px;
}
.home-link-text {
padding: 8px;
font-size: 24px;
}
#top-bar-links {
display: flex;
}