Optimization experiments
This commit is contained in:
parent
9bd88483c4
commit
95c811f3b5
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -196,6 +196,7 @@ dependencies = [
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -486,6 +487,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.8"
|
||||
|
@ -121,8 +121,9 @@ assigning each argument a register and associating the register with the local.
|
||||
|
||||
When generating instructions for a register-based virtual machine, there are opportunities to
|
||||
optimize the generated code by using fewer instructions or fewer registers. While it is best to
|
||||
output optimal code in the first place, it is not always possible. Dust's compiler uses simple
|
||||
functions that modify isolated sections of the instruction list through a mutable reference.
|
||||
output optimal code in the first place, it is not always possible. Dust's compiler modifies the
|
||||
instruction list during parsing to apply optimizations before the chunk is completed. There is no
|
||||
separate optimization pass, and the compiler cannot be run in a mode that disables optimizations.
|
||||
|
||||
#### Type Checking
|
||||
|
||||
|
5
bench/assets/count_to_one_billion.ds
Normal file
5
bench/assets/count_to_one_billion.ds
Normal file
@ -0,0 +1,5 @@
|
||||
let mut i = 0
|
||||
|
||||
while i < 100_000_000 {
|
||||
i += 1
|
||||
}
|
5
bench/assets/count_to_one_billion.js
Normal file
5
bench/assets/count_to_one_billion.js
Normal file
@ -0,0 +1,5 @@
|
||||
var i = 0;
|
||||
|
||||
while (i < 1_000_000_000) {
|
||||
i++;
|
||||
}
|
@ -18,6 +18,7 @@ serde_json = "1.0.117"
|
||||
getrandom = { version = "0.2", features = [
|
||||
"js",
|
||||
] } # Indirect dependency, for wasm builds
|
||||
smallvec = { version = "1.13.2", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11.5"
|
||||
|
@ -7,6 +7,7 @@
|
||||
use std::fmt::{self, Debug, Display, Write};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{ConcreteValue, Disassembler, FunctionType, Instruction, Scope, Span, Type};
|
||||
|
||||
@ -18,18 +19,18 @@ pub struct Chunk {
|
||||
name: Option<String>,
|
||||
r#type: FunctionType,
|
||||
|
||||
instructions: Vec<(Instruction, Span)>,
|
||||
constants: Vec<ConcreteValue>,
|
||||
locals: Vec<Local>,
|
||||
instructions: SmallVec<[(Instruction, Span); 32]>,
|
||||
constants: SmallVec<[ConcreteValue; 16]>,
|
||||
locals: SmallVec<[Local; 8]>,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn new(name: Option<String>) -> Self {
|
||||
Self {
|
||||
name,
|
||||
instructions: Vec::new(),
|
||||
constants: Vec::new(),
|
||||
locals: Vec::new(),
|
||||
instructions: SmallVec::new(),
|
||||
constants: SmallVec::new(),
|
||||
locals: SmallVec::new(),
|
||||
r#type: FunctionType {
|
||||
type_parameters: None,
|
||||
value_parameters: None,
|
||||
@ -48,9 +49,9 @@ impl Chunk {
|
||||
Self {
|
||||
name,
|
||||
r#type,
|
||||
instructions,
|
||||
constants,
|
||||
locals,
|
||||
instructions: instructions.into(),
|
||||
constants: constants.into(),
|
||||
locals: locals.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,18 +71,32 @@ impl Chunk {
|
||||
self.instructions.is_empty()
|
||||
}
|
||||
|
||||
pub fn constants(&self) -> &Vec<ConcreteValue> {
|
||||
pub fn constants(&self) -> &SmallVec<[ConcreteValue; 16]> {
|
||||
&self.constants
|
||||
}
|
||||
|
||||
pub fn instructions(&self) -> &Vec<(Instruction, Span)> {
|
||||
pub fn instructions(&self) -> &SmallVec<[(Instruction, Span); 32]> {
|
||||
&self.instructions
|
||||
}
|
||||
|
||||
pub fn locals(&self) -> &Vec<Local> {
|
||||
pub fn locals(&self) -> &SmallVec<[Local; 8]> {
|
||||
&self.locals
|
||||
}
|
||||
|
||||
pub fn stack_size(&self) -> usize {
|
||||
self.instructions()
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|(instruction, _)| {
|
||||
if instruction.yields_value() {
|
||||
Some(instruction.a() as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn disassembler(&self) -> Disassembler {
|
||||
Disassembler::new(self)
|
||||
}
|
||||
|
@ -505,13 +505,19 @@ impl<'src> Compiler<'src> {
|
||||
if let Token::Integer(text) = self.current_token {
|
||||
self.advance()?;
|
||||
|
||||
let integer = text
|
||||
.parse::<i64>()
|
||||
.map_err(|error| CompileError::ParseIntError {
|
||||
error,
|
||||
position: self.previous_position,
|
||||
})?;
|
||||
let value = ConcreteValue::Integer(integer);
|
||||
let mut integer_value = 0_i64;
|
||||
|
||||
for digit in text.chars() {
|
||||
let digit = if let Some(digit) = digit.to_digit(10) {
|
||||
digit as i64
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
integer_value = integer_value * 10 + digit;
|
||||
}
|
||||
|
||||
let value = ConcreteValue::Integer(integer_value);
|
||||
|
||||
self.emit_constant(value, position)?;
|
||||
|
||||
@ -1519,7 +1525,7 @@ impl<'src> Compiler<'src> {
|
||||
self.current_position = function_compiler.current_position;
|
||||
|
||||
let function =
|
||||
ConcreteValue::Function(function_compiler.finish(None, value_parameters.clone()));
|
||||
ConcreteValue::function(function_compiler.finish(None, value_parameters.clone()));
|
||||
let constant_index = self.push_or_get_constant(function);
|
||||
let register = self.next_register();
|
||||
let function_type = FunctionType {
|
||||
|
@ -239,7 +239,7 @@ impl<'src> Lexer<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
if c.is_ascii_digit() {
|
||||
if c.is_ascii_digit() || c == '_' {
|
||||
self.next_char();
|
||||
} else {
|
||||
break;
|
||||
|
@ -19,7 +19,7 @@ impl AbstractValue {
|
||||
|
||||
pub fn to_concrete_owned(&self, vm: &Vm) -> Result<ConcreteValue, VmError> {
|
||||
match self {
|
||||
AbstractValue::FunctionSelf => Ok(ConcreteValue::Function(vm.chunk().clone())),
|
||||
AbstractValue::FunctionSelf => Ok(ConcreteValue::function(vm.chunk().clone())),
|
||||
AbstractValue::List { items, .. } => {
|
||||
let mut resolved_items = Vec::with_capacity(items.len());
|
||||
|
||||
|
@ -12,7 +12,7 @@ pub enum ConcreteValue {
|
||||
Byte(u8),
|
||||
Character(char),
|
||||
Float(f64),
|
||||
Function(Chunk),
|
||||
Function(Box<Chunk>),
|
||||
Integer(i64),
|
||||
List(Vec<ConcreteValue>),
|
||||
Range(RangeValue),
|
||||
@ -28,6 +28,10 @@ impl ConcreteValue {
|
||||
ValueRef::Concrete(self)
|
||||
}
|
||||
|
||||
pub fn function(chunk: Chunk) -> Self {
|
||||
ConcreteValue::Function(Box::new(chunk))
|
||||
}
|
||||
|
||||
pub fn list<T: Into<Vec<ConcreteValue>>>(into_list: T) -> Self {
|
||||
ConcreteValue::List(into_list.into())
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ impl ValueRef<'_> {
|
||||
pub fn add(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.add(right).map(|result| result.to_value())
|
||||
left.add(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotAdd(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
@ -89,7 +89,7 @@ impl ValueRef<'_> {
|
||||
pub fn subtract(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.subtract(right).map(|result| result.to_value())
|
||||
left.subtract(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotSubtract(
|
||||
self.to_owned(),
|
||||
@ -101,7 +101,7 @@ impl ValueRef<'_> {
|
||||
pub fn multiply(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.multiply(right).map(|result| result.to_value())
|
||||
left.multiply(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotMultiply(
|
||||
self.to_owned(),
|
||||
@ -113,7 +113,7 @@ impl ValueRef<'_> {
|
||||
pub fn divide(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.divide(right).map(|result| result.to_value())
|
||||
left.divide(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotDivide(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
@ -122,7 +122,7 @@ impl ValueRef<'_> {
|
||||
pub fn modulo(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.modulo(right).map(|result| result.to_value())
|
||||
left.modulo(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotModulo(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
@ -130,18 +130,14 @@ impl ValueRef<'_> {
|
||||
|
||||
pub fn negate(&self) -> Result<Value, ValueError> {
|
||||
match self {
|
||||
ValueRef::Concrete(concrete_value) => {
|
||||
concrete_value.negate().map(|result| result.to_value())
|
||||
}
|
||||
ValueRef::Concrete(concrete_value) => concrete_value.negate().map(Value::Concrete),
|
||||
_ => Err(ValueError::CannotNegate(self.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn not(&self) -> Result<Value, ValueError> {
|
||||
match self {
|
||||
ValueRef::Concrete(concrete_value) => {
|
||||
concrete_value.not().map(|result| result.to_value())
|
||||
}
|
||||
ValueRef::Concrete(concrete_value) => concrete_value.not().map(Value::Concrete),
|
||||
_ => Err(ValueError::CannotNot(self.to_owned())),
|
||||
}
|
||||
}
|
||||
@ -149,7 +145,7 @@ impl ValueRef<'_> {
|
||||
pub fn equal(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.equal(right).map(|result| result.to_value())
|
||||
left.equal(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
@ -158,7 +154,7 @@ impl ValueRef<'_> {
|
||||
pub fn less_than(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.less_than(right).map(|result| result.to_value())
|
||||
left.less_than(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
@ -166,9 +162,9 @@ impl ValueRef<'_> {
|
||||
|
||||
pub fn less_than_or_equal(&self, other: ValueRef) -> Result<Value, ValueError> {
|
||||
match (self, other) {
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => left
|
||||
.less_than_or_equal(right)
|
||||
.map(|result| result.to_value()),
|
||||
(ValueRef::Concrete(left), ValueRef::Concrete(right)) => {
|
||||
left.less_than_or_equal(right).map(Value::Concrete)
|
||||
}
|
||||
_ => Err(ValueError::CannotCompare(self.to_owned(), other.to_owned())),
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ use std::{
|
||||
io,
|
||||
};
|
||||
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use crate::{
|
||||
compile, instruction::*, AbstractValue, AnnotatedError, Argument, Chunk, ConcreteValue,
|
||||
Destination, DustError, Instruction, NativeFunctionError, Operation, Span, Value, ValueError,
|
||||
@ -25,9 +27,9 @@ pub fn run(source: &str) -> Result<Option<ConcreteValue>, DustError> {
|
||||
#[derive(Debug)]
|
||||
pub struct Vm<'a> {
|
||||
chunk: &'a Chunk,
|
||||
stack: Vec<Register>,
|
||||
stack: SmallVec<[Register; 64]>,
|
||||
parent: Option<&'a Vm<'a>>,
|
||||
local_definitions: Vec<Option<u16>>,
|
||||
local_definitions: SmallVec<[Option<u16>; 16]>,
|
||||
|
||||
ip: usize,
|
||||
last_assigned_register: Option<u16>,
|
||||
@ -35,14 +37,14 @@ pub struct Vm<'a> {
|
||||
}
|
||||
|
||||
impl<'a> Vm<'a> {
|
||||
const STACK_LIMIT: usize = u16::MAX as usize;
|
||||
const STACK_LIMIT: u16 = u16::MAX;
|
||||
|
||||
pub fn new(chunk: &'a Chunk, parent: Option<&'a Vm<'a>>) -> Self {
|
||||
Self {
|
||||
chunk,
|
||||
stack: Vec::new(),
|
||||
stack: smallvec![Register::Empty; chunk.stack_size()],
|
||||
parent,
|
||||
local_definitions: vec![None; chunk.locals().len()],
|
||||
local_definitions: smallvec![None; chunk.locals().len()],
|
||||
ip: 0,
|
||||
last_assigned_register: None,
|
||||
current_position: Span(0, 0),
|
||||
@ -643,11 +645,9 @@ impl<'a> Vm<'a> {
|
||||
Ok(value_ref)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn set_register(&mut self, to_register: u16, register: Register) -> Result<(), VmError> {
|
||||
self.last_assigned_register = Some(to_register);
|
||||
|
||||
let length = self.stack.len();
|
||||
let to_register = to_register as usize;
|
||||
let length = self.stack.len() as u16;
|
||||
|
||||
if length == Self::STACK_LIMIT {
|
||||
return Err(VmError::StackOverflow {
|
||||
@ -659,16 +659,12 @@ impl<'a> Vm<'a> {
|
||||
Ordering::Less => {
|
||||
log::trace!("Change R{to_register} to {register}");
|
||||
|
||||
self.stack[to_register] = register;
|
||||
|
||||
Ok(())
|
||||
self.stack[to_register as usize] = register;
|
||||
}
|
||||
Ordering::Equal => {
|
||||
log::trace!("Set R{to_register} to {register}");
|
||||
|
||||
self.stack.push(register);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let difference = to_register - length;
|
||||
@ -682,10 +678,12 @@ impl<'a> Vm<'a> {
|
||||
log::trace!("Set R{to_register} to {register}");
|
||||
|
||||
self.stack.push(register);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
self.last_assigned_register = Some(to_register);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_constant(&self, constant_index: u16) -> Result<&ConcreteValue, VmError> {
|
||||
|
@ -6,7 +6,7 @@ fn function() {
|
||||
|
||||
assert_eq!(
|
||||
run(source),
|
||||
Ok(Some(ConcreteValue::Function(Chunk::with_data(
|
||||
Ok(Some(ConcreteValue::function(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
@ -70,7 +70,7 @@ fn function_call() {
|
||||
(Instruction::r#return(true), Span(41, 41)),
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Function(Chunk::with_data(
|
||||
ConcreteValue::function(Chunk::with_data(
|
||||
None,
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
@ -126,7 +126,7 @@ fn function_declaration() {
|
||||
(Instruction::r#return(false), Span(40, 40))
|
||||
],
|
||||
vec![
|
||||
ConcreteValue::Function(Chunk::with_data(
|
||||
ConcreteValue::function(Chunk::with_data(
|
||||
Some("add".to_string()),
|
||||
FunctionType {
|
||||
type_parameters: None,
|
||||
|
@ -1,5 +0,0 @@
|
||||
var i = 0;
|
||||
|
||||
while (i < 10000) {
|
||||
i++;
|
||||
}
|
491
flamegraph.svg
Normal file
491
flamegraph.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 23 KiB |
22
perf.data.old
Normal file
22
perf.data.old
Normal file
@ -0,0 +1,22 @@
|
||||
# started on Tue Dec 3 23:57:01 2024
|
||||
|
||||
|
||||
Performance counter stats for 'target/release/dust bench/assets/count_to_one_billion.ds':
|
||||
|
||||
3,821.33 msec task-clock:u # 1.000 CPUs utilized
|
||||
0 context-switches:u # 0.000 /sec
|
||||
0 cpu-migrations:u # 0.000 /sec
|
||||
128 page-faults:u # 33.496 /sec
|
||||
19,568,365,731 cycles:u # 5.121 GHz
|
||||
22,811,191 stalled-cycles-frontend:u # 0.12% frontend cycles idle
|
||||
70,300,518,284 instructions:u # 3.59 insn per cycle
|
||||
# 0.00 stalled cycles per insn
|
||||
12,500,111,636 branches:u # 3.271 G/sec
|
||||
19,795 branch-misses:u # 0.00% of all branches
|
||||
|
||||
3.822391878 seconds time elapsed
|
||||
|
||||
3.811230000 seconds user
|
||||
0.000999000 seconds sys
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user