6 private links
L1 cache reference ......................... 0.5 ns
Branch mispredict ............................ 5 ns
L2 cache reference ........................... 7 ns
Mutex lock/unlock ........................... 25 ns
Main memory reference ...................... 100 ns
Compress 1K bytes with Zippy ............. 3,000 ns = 3 µs
Send 2K bytes over 1 Gbps network ....... 20,000 ns = 20 µs
SSD random read ........................ 150,000 ns = 150 µs
Read 1 MB sequentially from memory ..... 250,000 ns = 250 µs
Round trip within same datacenter ...... 500,000 ns = 0.5 ms
Read 1 MB sequentially from SSD* ..... 1,000,000 ns = 1 ms
Disk seek ........................... 10,000,000 ns = 10 ms
Read 1 MB sequentially from disk .... 20,000,000 ns = 20 ms
Send packet CA->Netherlands->CA .... 150,000,000 ns = 150 ms
Lets multiply all these durations by a billion:
Magnitudes:
Minute:
L1 cache reference 0.5 s One heart beat (0.5 s)
Branch mispredict 5 s Yawn
L2 cache reference 7 s Long yawn
Mutex lock/unlock 25 s Making a coffee
Hour:
Main memory reference 100 s Brushing your teeth
Compress 1K bytes with Zippy 50 min One episode of a TV show (including ad breaks)
Day:
Send 2K bytes over 1 Gbps network 5.5 hr From lunch to end of work day
Week
SSD random read 1.7 days A normal weekend
Read 1 MB sequentially from memory 2.9 days A long weekend
Round trip within same datacenter 5.8 days A medium vacation
Read 1 MB sequentially from SSD 11.6 days Waiting for almost 2 weeks for a delivery
Year
Disk seek 16.5 weeks A semester in university
Read 1 MB sequentially from disk 7.8 months Almost producing a new human being
The above 2 together 1 year
Decade
Send packet CA->Netherlands->CA 4.8 years Average time it takes to complete a bachelor's degree
Reformat function call on one line:
sed -e :a -e '$!N;s/\n / /;ta' -e 'P;D'
From slides http://mitsuhiko.pocoo.org/RustAPI.pdf :
APIs are Important
• A library's author's true success metrics are:
• how successful all users are in using the API
• the quality of the output that users achieve by using the API
• the percentage of users making the correct choices
Your User Matters
• When you build a library you should treat it like any other thing
• Define success metrics
• Measure yourself
• Concise: easy to get started
• Good Defaults: easy to get started, trivial to stay on the golden path as it
changes
• Small Surface Area: enable room to breath and innovate, without breaking
users
• Backwards compatible: avoid unnecessary churn to keep users on the golden
path
The Golden Path
• An opinionated path for how to build
• That path might change over time
• Change requires adjustment by users
• Fast change means users being left behind
• Measuring success: users on the golden path (not churning, not staying on
old versions, not hating the upgrade experience, not using old patterns)
Use Defaults to Fight Cargo Cult
• Defaults are hard and of two types:
• Absolute defaults that cannot be changed (i32::default() -> 0)
• Defaults that allow a level of flexibility (Default Hasher: SipHash)
• For defaults to allow flexibility, care has to be taken:
• Set rules and expectations about stability
• Aim for some level of change
Good Defaults
• Default Hasher:
• Hasher is documented to be non portable
• Hasher is documented to change
• No expectation around cross-version/process stability
• A better hasher can be picked, all code ever written benefits at once
More API = More Problems
• The larger the surface, the more of it ends up used
• Less commonly used APIs have the most leaky abstractions
• Inhibits future change: "does someone even use this?"
Hide API Behind Common Abstractions
• Developers are used to these patterns, they are worth exploring:
• Into<T>
• AsRef<T>
• Careful: surface area stays large, but large bound to common and simple
patterns
Into
• Common pairs:
• Into<String>
• Into<Cow<'_, T>>
• Into<YourRuntimeType>
• ToString can be sometimes an interesting alternative to Into<String
Into
• Common pairs:
• Into<String>
• Into<Cow<'_, T>>
• Into<YourRuntimeType>
• ToString can be sometimes an interesting alternative to Into<String
Monomorphization & Compile Times
• Rust loves to inline
• All those different types create
duplicated generated code
• Example: isolate conversions and
call into shared functions to
reduce the total amount of copied
code.
Hide the Onion but create the Onion
• Good APIs are Layered Like Onions
• Only provide the outermost layer first
• Keeps the inner layers flexibility to change
• Over time, consider exposing internal layers under separate stability
guarantees
Explicit Exports
• Hide your internal structure, re-export sensibly
• Your folder structure does not matter to your users
Explicit Fake Modules
• Consider creating modules on the spot for utilities
• For instance "insta" has utility
functions and types that are rarely
useful. The ones I subscribe stability
to are re-exported under a specific
module.
Public but Hidden
• Sometimes stuff needs to be public,
but you don't want anyone to use it.
• Common example: utility functionality
for macros.
• Here both __context and
__context_pair! are public but hidd
Traits are Tricky
• Traits are super useful, but they are tricky
• Fall into two categories:
• Sealed (user should not implement)
• Open (user should implemen
Sealed Traits
• Not really supported, doc hidden
and hackery
• Example in MiniJinja: want to
abstract over types, but I don't
really want to let the user do that
Full Seal
• Uses a private zero sized marker type somewhere
• User cannot implement or invoke as the type is private
Traits are Hard to Discover
• I avoid traits unless I know abstraction over implementations is necessary
• Did you notice that BTreeMap and HashMap are not expressed via traits?
• The usefulness of abstraction even for interchangeable types is sometimes
unclear
• You can always add traits later
Debug
• Put it on all public types
• Consider it on your internal types behind a feature flag
• Super valuable for dbg!() and co
Display
• Makes the type have a representation in format!()
• It also gives it the `.to_string()` method
• Certain types need it in the contract (eg: all errors)
• Recommendation: avoid in most cases unless you implement a custom
integer, string etc
Copy and Clone
• Once granted, impossible to take away
• Neither can be universally provided
• Clone: really useful, consider adding
• If you ever feel you need to take it away, consider Arc<T> internally
• Copy: might inhibit future change, but really useful
• Some types regrettably do not have Copy (eg: Range) and people hate it
Sync and Send
• I cannot give recommendations
• The only one I have: non Send/Sync types are not that bad
• Consider them seriously
Lifetimes and Libraries
• Try to avoid too clever setups
• Consider "Session" abstractions where people only need to temporarily hold
on to data.
Borrowing to Self
• Rust is really bad at this, sometimes you build yourself into a corner
• Best tool I found to date for this is the self_cell crate
• Buffer can be held into itself
Panic vs Error
• Try to avoid panics
• If you do need to panic, consider #[track_caller]
Errors Matter
• Spend some time designing your errors
• Errors deserve attention just as much as your other types
• A talk all by itself, so here the basics:
• Implement std::error::Error on your errors
• Implement source() if you think someone might want to peak into
Easy netboot pxe with one binary
TL DR
Step 1) Add
-object memory-backend-file,id=mem,size=512M,mem-path=/dev/shm/qemu-ram,share=on -machine memory-backend=mem
To your QEMU command
Note : Choose the correct size, the same as specified by -m, here -m 512m, therefore size=512M.
Step 2) Open /dev/shm/qemu-ram from your external process
Step 3) Profit !
Release Engineering
#![no_main]
#[link_section=".text"]
#[no_mangle]
pub static main: [u32; 9] = [
3237986353,
3355442993,
120950088,
822083584,
252621522,
1699267333,
745499756,
1919899424,
169960556,
];