Skip to content

Commit

Permalink
Add basic memory allocation benchmark (#976)
Browse files Browse the repository at this point in the history
## Summary
This PR adds a binary called `memtest` to `qsc`, and a CI stage to
interpret its output and post feedback on PRs. `memtest` will output the
number of bytes used to compile some Q# code. Right now, it just
compiles the standard library, but it is easily extensible if we want to
use the `qsc` API to evaluate arbitrary Q# inputs (maybe all of the
samples?).

## Some Notes
I first used Jemalloc for this. After some iteration with Jemalloc, I
realized it is overkill for what we need. Additionally, using Jemalloc
breaks on Windows and uses C FFI under the hood. I looked for other
options, and found that [you can implement your own
allocator](https://doc.rust-lang.org/stable/std/alloc/trait.GlobalAlloc.html#example).
So I did that, instead. This new approach adds zero dependencies and is
easier to reason about.

Part 2 of this feature is here: #981
  • Loading branch information
sezna authored Jan 8, 2024
1 parent 6859be2 commit 332886a
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 0 deletions.
4 changes: 4 additions & 0 deletions compiler/qsc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ bench = false
name = "qsi"
bench = false

[[bin]]
name = "memtest"
bench = false

[[bench]]
name = "large"
harness = false
Expand Down
59 changes: 59 additions & 0 deletions compiler/qsc/src/bin/memtest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Records the memory usage of the compiler.
use qsc::{compile, CompileUnit};
use qsc_frontend::compile::{PackageStore, RuntimeCapabilityFlags};
use std::{
alloc::{GlobalAlloc, Layout, System},
sync::atomic::{AtomicU64, Ordering},
};

/// A wrapper around a memory allocator that tracks allocation amounts.
pub struct AllocationCounter<A: GlobalAlloc> {
pub allocator: A,
pub counter: AtomicU64,
}

unsafe impl<A: GlobalAlloc> GlobalAlloc for AllocationCounter<A> {
unsafe fn alloc(&self, l: Layout) -> *mut u8 {
self.counter.fetch_add(l.size() as u64, Ordering::SeqCst);
self.allocator.alloc(l)
}
unsafe fn dealloc(&self, ptr: *mut u8, l: Layout) {
self.allocator.dealloc(ptr, l);
self.counter.fetch_sub(l.size() as u64, Ordering::SeqCst);
}
}

impl<A: GlobalAlloc> AllocationCounter<A> {
pub const fn new(allocator: A) -> Self {
AllocationCounter {
allocator,
counter: AtomicU64::new(0),
}
}
pub fn reset(&self) {
self.counter.store(0, Ordering::SeqCst);
}
pub fn read(&self) -> u64 {
self.counter.load(Ordering::SeqCst)
}
}

#[global_allocator]
static ALLOCATOR: AllocationCounter<System> = AllocationCounter::new(System);

pub fn compile_stdlib() -> CompileUnit {
let store = PackageStore::new(compile::core());
compile::std(&store, RuntimeCapabilityFlags::all())
}

fn main() {
let _stdlib = compile_stdlib();
let std = ALLOCATOR.read();

ALLOCATOR.reset();
println!("{std}");
}

0 comments on commit 332886a

Please sign in to comment.