ECE 522 Lab 3: Benchmarking and Databases solution




5/5 - (5 votes)

Part 1: Benchmarking in Rust Consider the following x86 machine code: Details of x86 machine code can be found at: and also, at: .LCPI0_0: .long 10 .long 0 .long 12 .long 32 example::main: sub rsp, 16 movaps xmm0, xmmword ptr [rip + .LCPI0_0] movups xmmword ptr [rsp], xmm0 xor eax, eax cmp dword ptr [rsp + 4], 32 setg al mov dword ptr [rsp + 8*rax + 4], 10 add rsp, 16 ret Question 1: Examine the machine code and explain what it does? (Hint: What can you do to a list with two for loops and a single conditional?) Question 2: Translate the machine code to Rust. Question 3: Use the compiler explorer ( to generate the machine code for your answer in Question 2. Question 4: Does the optimization flag -O make a difference? ● DEMO this deliverable to the lab instructor. Criterion ( is a statistics-driven micro benchmarking crate in Rust. It helps you write fast code by detecting and measuring performance improvements or regressions, even small ones, quickly and accurately. The crate helps us optimize with confidence, knowing how each change affects the performance of our code. 1 ECE 522 | Software Construction, Verification and Evolution Question 5: Follow the instructions in the quickstart section ( ) to add the Criterion crate to your project (Question 2). Question 6: Bench your function from Question 2 using the following code: let mut rng = rand::thread_rng(); let mut l: Vec = (0..10000).map(|_| {rng.gen_range(1, 10000)}).collect(); c.bench_function(“your function: “, |b| b.iter(|| your_function::(black_box(&mut l)))); Feel free to replace “your_code” with the name of the function you wrote in Question 2 and examine the output of the benchmark. ● DEMO this deliverable to the lab instructor. Part 2: Code Optimization Based on the results obtained in Question 6, Do you see any room for optimization? (Hint: consider using iterators ( Question 7: Optimize your code from Question 2. Question 8: Bench your function from Question 7 using the following code: let mut rng = rand::thread_rng(); let mut l: Vec = (0..10000).map(|_| {rng.gen_range(1, 10000)}).collect(); c.bench_function(“your function: “, |b| b.iter(|| your_function::(black_box(&mut l)))); Question 9: Compare between the two performance results obtained from Criterion crate from Question 6 and Question 8. Question 10: What are “zero-cost abstractions” in Rust? Explain how this term applies to Question 9. ● DEMO (and Explain) this deliverable to the lab instructor. Part 3: Managing Databases Modern applications are rapidly evolving to become data-heavy systems. For example, Training machines with data is starting to become a mainstream way to solve complex problems. As a result, the data should be easy to retrieve and must have guarantees regarding consistency and durability. Databases are the go-to solution for providing a robust foundation for building applications that are data-backed and that support the expectations of their users. A database is a collection of tables. Tables are units of data organization. Data that’s been organized into tables is only applicable to relational databases. Other databases, such as NoSQL and graph-based databases, use a more flexible document model to store and 2 ECE 522 | Software Construction, Verification and Evolution organize data. Tables are usually representations of entities from the real world. These entities can have various attributes that take the place of columns in these tables. In this part, we will use SQLite to design a convenient database system to provide data storage for a simple Bank application written in Rust. 1- SQLite Integration First, we need need to install SQLite in order to be able to create our database. ● For windows: 1- Install SQLite for their website ( 2- Download the sqlite-shell-win32-*.zip and sqlite-dll-win32-*.zip zipped files. 3- Create a folder on your machine (e.g., C:\sqlite) and unzip these two zipped files in this folder. 4- Add the path for this folder (e.g., C:\sqlite) to your PATH envrionment variable. 5- To test the installation, go to the terminal and write sqlite3 comand, the following message should be displayed. ● For Linux: SQLite should be installed by default on almost all the flavours of Linux OS. You can test the installation by issuing the sqlite3 command in the terminal. ● For Mac OS: 1- Although the lates version of Mac should have SQLite installted by default, you can insall it from their website ( 2- Download sqlite-autoconf-*.tar.gz from source code section. 3- Run the following command − $tar xvfz sqlite-autoconf-3071502.tar.gz $cd sqlite-autoconf-3071502 $./configure –prefix=/usr/local $make $make install 4- To test the intallation, run the following command on your machnie: $sqlite3 3 ECE 522 | Software Construction, Verification and Evolution Second, once you have SQLite installed, you can create a new Rust project, then create a new folder called data. Your project’s folders should have the following folders/files: ● Cargo has generated a new directory hello-rust with the following files: DBProject ├── Cargo.toml └── data └── src └── 1 directory, 2 files Navigate to the data folder and run the following command to create your database sqlite3 users.db Your database will have two tables: 1- One for username and password 2- Another one to store a list of transactions. ● To create the user table that stores usernames and passwords for bank clients, we can run the following command: sqlite> create table users(u_name text PRIMARY KEY, p_word text); To display a list of tables in your database, you can use: sqlite> .tables This should return: users ● the transactions table should contain the following fields: • u_from: the user from which the transaction is sent. • u_to: the user to which the transaction is going. • t_date: the transaction date • t_amount: the transaction amount • The primary key is using multiple names combining u_from and t_date. ● To create the table: 4 ECE 522 | Software Construction, Verification and Evolution sqlite> create table transactions(u_from text, u_to text, t_date integer, t_amount text, PRIMARY KEY(u_from,t_date), FOREIGN KEY (u_from) REFERENCES users(u_name), FOREIGN KEY (u_to) REFERENCES users(u_name)); • The table has two foreign keys that reference the u_name from the users table. Now running the .tables command: sqlite> .tables should return: transactions users Third, to insert data into the users database, we can use the insert statemetn as follows: sqlite> insert into users (u_name, p_word) values (“Matt”, “matt_pw”), (“Dave”, “dave_pw”); sqlite> insert into transactions (u_from, u_to, t_date, t_amount) values (“Dave”,”Matt”,datetime(“now”),50); We can use the phrase datetime(“now”), which is a SQL function to return the current time. To view the data, we can use the select statement: sqlite> select * from users; Matt|matt_pw Dave|dave_pw sqlite> select * from transactions; Dave|Matt|2019-10-15 01:54:09|50 To delete from your tables, you can use: sqlite> delete from transactions; sqlite> delete from users; 2- Data Management using Rust First, we should not save passwords as text. To make secure passwords, we will use the bcrypt library: From that bcrypt, we will need the methods: DEFAULT_COST, hash, verify; and the error type BcryptError. 5 ECE 522 | Software Construction, Verification and Evolution Also, to manage our SQLite database from our code, we need SQLite crate: Second, we will need to create a struct UserBase to handle access to the database. So, we will add the following to our file pub struct UserBase{ fname:String, } We are going to combine two different error types: bcrypt errors and the SQLite errors. We can use another OR structure by implementing the erros as enum: use bcrypt::{DEFAULT_COST, hash, verify, BcryptError}; use sqlite::Error as SqErr; #[derive(Debug )] pub enum UBaseErr{ DbErr(SqErr), HashError(BcryptError) } To handle both types of error as UBaseErr, we can use the From trait allows for a type to define how to create itself from another type, hence providing a very simple mechanism for converting between several types. impl From for UBaseErr{ fn from(s:SqErr)->Self{ UBaseErr::DbErr(s) } } impl From for UBaseErr{ fn from(b:BcryptError)->Self{ UBaseErr::HashError(b) } } 6 ECE 522 | Software Construction, Verification and Evolution Third, let’s implement some functions for the UserBase struct. We will start with the add_user method for adding a user to the database: impl UserBase{ pub fn add_user(&self, u_name:&str, p_word:&str)->Result<(),UBaseErr>{ let conn=sqlite::open(&self.fname)?; let hpass=bcrypt::hash(p_word,DEFAULT_COST)?; let mut st= conn.prepare(“insert into users(u_name, p_word) values (?,?);”)?; st.bind(1,u_name)?; st.bind(2,&hpass as &str)?;; Ok(()) } } Now, can you write the pay function that inserts into the transaction table: pub fn pay(&self, u_from:&str, u_to:&str, amount:i64)->Result<(),UBaseErr>{ let conn=sqlite::open(&self.fname)?; let mut st= conn.prepare(“insert into transactions (u_from, u_to, t_date, t_amount) values(?,?,datetime(\”now\”),?);”)?; st.bind(1,u_from)?; st.bind(2,u_to)?; st.bind(3,amount)?;; Ok(()) } 3- Writing Tests Question 11: Write test cases to check if the users table gets updated when the add_user function is run and the transactions table gets updated when the pay function is run. Your tests should ensure that your code runs properly. ● DEMO this deliverable to the lab instructor. 7