-
Notifications
You must be signed in to change notification settings - Fork 99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding a test that will test the correctness of snmalloc #569
Conversation
60c1279
to
0dc4a25
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know how Rust vecs interact with the underlying heap allocator, but as a future improvement I think it probably makes sense to exercise resize()
on existing vecs to grow and shrink them, in addition to allocating and freeing vecs.
It might also be nice to have command-line arguments or environment variables or some other mechanism for controlling the number of threads and number of iterations without having to recompile the code.
[package.metadata.fortanix-sgx] | ||
# heap size (in bytes), the default heap size is 0x2000000. | ||
heap-size=0x800000000 | ||
debug=false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we're setting debug=false
here?
const TO_GB: usize = TO_MB * 1024; | ||
|
||
const NUM_OPERATION_CHOICES: usize = 4; | ||
const MAGIC_NUM_TO_CHECK: u32 = 0xdeadbeef; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a slight preference for not using 0xdeadbeef
as a magic number. This is a popular choice for magic number, so if we're using 3rd party libraries, there's a slightly higher chance of this particular magic number being used by something else, compared to picking some different magic number. Also, 32 bits is pretty small if it's important that we don't encounter the magic number on accident. 64 bits would be better, if that's possible.
Both of these comments are very minor.
* can check for the same index multiple times. We could have checked | ||
* for all the indices but that would be too time consuming | ||
*/ | ||
if (buf[get_random_num(0, buf.len() - 1)] != MAGIC_NUM_TO_CHECK) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe as a future improvement, it may be a good idea to fill the buffer with pseudo-random data that can be easily checked, rather than by repeating the same bytes over and over. I've worked on memory allocators in the past that had bugs that manifested as data being copied to the wrong location in memory. These bugs were not caught by a test similar to this one that repeated the same magic number repeatedly, because all of the magic numbers were the same. When we changed the test to use pseudo-random (but calculatable) sequences, the test was able to catch the bug.
} | ||
|
||
fn select_and_check_random_buffer_contents(array_of_vectors: &Vec<Vec<u32>>) { | ||
/* This buffer selects a random number of buffers and then calls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the comment say "This function selects a random number..." instead of "This buffer selects..."?
let num_active_vectors = array_of_vectors.len(); | ||
if (num_active_vectors > 0) { | ||
let random_buffer_check_count = | ||
get_random_num(1, 100) % MAX_BUFFER_CHECKS_PER_THREAD_ITERATION + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to do this calculation this way instead of just calling get_random_num(1, MAX_BUFFER_CHECKS_PER_THREAD_ITERATION)
?
let random_buffer_check_count = | ||
get_random_num(1, 100) % MAX_BUFFER_CHECKS_PER_THREAD_ITERATION + 1; | ||
for i in 1..=random_buffer_check_count { | ||
let random_buffer_index_to_check = get_random_num(1, 100) % array_of_vectors.len(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use get_random_num(0, array_of_vectors.len() - 1)
?
let random_index_to_delete = get_random_num(1, 100) % array_of_vectors.len(); | ||
let len = &array_of_vectors[random_index_to_delete].len() * mem::size_of::<i32>(); | ||
drop(&array_of_vectors[random_index_to_delete]); | ||
array_of_vectors.remove(random_index_to_delete); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does removing the vec from array_of_vectors
not cause the vec to be dropped?
const MAX_BUFF_NUM_PER_THREAD: usize = (32); | ||
const MAX_BUFFER_CHECKS_PER_THREAD_ITERATION: usize = 8; | ||
const MAX_INDEX_CHECKS_PER_BUFFER: usize = 1 << 16; | ||
const MAX_ALLOWED_FREE_HEAP_PERCENTAGE: f64 = 80.0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: Maybe this should be named MAX_ALLOWED_USED_HEAP_PERCENTAGE
?
const TO_MB: usize = TO_KB * 1024; | ||
const TO_GB: usize = TO_MB * 1024; | ||
|
||
const NUM_OPERATION_CHOICES: usize = 4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why don't you create an array of tester functions: fn traverse_and_check_buffer(buf: &Vec)
and in the worker thread we just go by the number of elements in the array.
this way we can:
- expand the test variety by adding more operations
- change the test type weight/ratio by adding more functions of the same type
- remove test types, for debug purposes or when they become obsolete, or for whatever other reason
- other fun stuff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea (as a future enhancement), but I would add that the usual way to have different weights for different functions is not to duplicate their entries in the table but rather to have an explicit weight associated with each table entry, and then randomly choose an activity taking the weights into account.
daa696a
to
1588dc0
Compare
/* Set of configurable parameters. These will adjusted as necessary while | ||
* recording the performance numbers | ||
*/ | ||
const NUM_CPUS: usize = 64; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice to have an explanation or a one line comment about each of these parameters.
1588dc0
to
16b1c28
Compare
drop(started); | ||
} | ||
|
||
fn traverse_and_check_buffer(buf: &Vec<usize>, MAGIC_NUM_TO_CHECK: usize) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change MAGIC_NUM_TO_CHECK to snake case as it is a dynamic value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can compile this program and address the warnings generated by the rust compiler, I see a lot of unused variables or improvements that can be easily addressed:
warning: unused import: `num_cpus`
--> src/main.rs:20:5
|
20 | use num_cpus;
| ^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: unused import: `std::time::Instant`
--> src/main.rs:26:5
|
26 | use std::time::Instant;
| ^^^^^^^^^^^^^^^^^^
warning: unnecessary parentheses around assigned value
--> src/main.rs:45:47
|
45 | const PER_THREAD_PER_BUFFER_MAX_SIZE: usize = (4 * TO_KB);
| ^ ^
|
= note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
|
45 - const PER_THREAD_PER_BUFFER_MAX_SIZE: usize = (4 * TO_KB);
45 + const PER_THREAD_PER_BUFFER_MAX_SIZE: usize = 4 * TO_KB;
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also turn on a setting that makes warning fail the build.
16b1c28
to
bf4419d
Compare
* then n threads, then n/2 and so on. | ||
*/ | ||
fn start_tests() { | ||
let MAGIC_NUM_TO_CHECK = get_random_num(1, usize::MAX); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My intention with better randomization of the fill data was that every element of every vec should get a distinct value. I think that's fine as a future enhancement.
bf4419d
to
62b040c
Compare
62b040c
to
ecad7f8
Compare
|
||
fn get_free_heap_percentage() -> f64 { | ||
(get_free_heap_size_in_bytes() as f64 / heap_size() as f64) * 100.0 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the functions above could be logically grouped in Heap
struct. That'd also simplify their names. You could do the same for allocating, freeing and checking allocated chunks.
drop(started); | ||
} | ||
|
||
fn wakeup_all_child_threads(pair_clone: Arc<(Mutex<bool>, Condvar)>) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use a Barrier
here
} | ||
|
||
fn traverse_and_check_buffer(buf: &Vec<usize>, magic_num_to_check: usize) { | ||
let num_indices_checks = get_random_num(1, MAX_INDEX_CHECKS_PER_BUFFER); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you have the same issue here as in #566: You allocate a Vec
assuming each element is a byte, which is not the case
* can check for the same index multiple times. We could have checked | ||
* for all the indices but that would be too time consuming | ||
*/ | ||
if buf[get_random_num(0, buf.len() - 1)] != magic_num_to_check { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I liked Dan's comment on magic numbers. You could even calculate a checksum instead
} | ||
} | ||
|
||
fn get_num_processors() -> usize { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is confusing as get_num_processors
doesn't return the number of processors on the machine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As an optimization I will fill up this function with so that it returns number of processors. Currently it returns a hardcoded value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but if it's hardcoded, there's no need to have this function
* than MIN_ALLOWED_FREE_HEAP_PERCENTAGE | ||
*/ | ||
|
||
let random_size = get_random_num(mem::size_of::<usize>() * 2, PER_THREAD_PER_BUFFER_MAX_SIZE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the minimum be 1 byte?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially tried with 1 byte but since the vector is of of u64, I kept the minimum size to be 16 bytes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that type probably comes from how you compare it with a magic number. You could use a checksum instead
let random_size = get_random_num(mem::size_of::<usize>() * 2, PER_THREAD_PER_BUFFER_MAX_SIZE) | ||
/ mem::size_of::<usize>(); | ||
let mut tmp: Vec<usize> = Vec::with_capacity(random_size); | ||
tmp.resize(random_size, magic_num_to_check); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you interact with the memory allocator directly? Vec
may internally do some optimizations. For example, allocating more than requested. That'd mess up the stress test for the allocator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't tried interacting with the allocator directly. Do you want me to try this in this PR? @raoulstrackx
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please
extern "C" { | ||
static HEAP_BASE: u64; | ||
static HEAP_SIZE: usize; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty much all this code could also be executed on almost all targets the Rust compiler supports, except some statements/functions like these. You could restrict these to only the SGX target with:
#[cfg(target_env = "sgx")]
extern "C" {
static HEAP_BASE: u64;
static HEAP_SIZE: usize;
}
That way you can develop and test under Linux, and if that works, try it within an enclave.
7a8e2d4
to
7cad3fc
Compare
7cad3fc
to
a28a97a
Compare
This is a test that tests the correctness of our new allocator snmalloc. This test should run continuosly and should not crash.
The pseudocode is taken from https://fortanix.atlassian.net/wiki/spaces/PLAT/pages/3155820642/Memory+Allocators
We should modify the configurable parameters https://github.com/fortanix/rust-sgx/blob/memory-correctness-test/examples/mem-correctness-test/src/main.rs#L43 and also modify the
heap-size
parameter inrust-sgx/examples/mem-correctness-test/Cargo.toml
file then simply do the following