Compare commits

..

22 Commits

Author SHA1 Message Date
c20ce7c2c3 add solution of lab 5 2025-02-26 12:16:53 -05:00
Jidong Xiao
05c92a01ae rename 2025-02-26 12:16:53 -05:00
Jidong Xiao
b724fb5a78 adding stack implementation 2025-02-26 12:16:53 -05:00
Jidong Xiao
b26d858b72 chrono is not used anymore 2025-02-26 12:16:53 -05:00
Jidong Xiao
ad71d0519f re-organize 2025-02-26 12:16:53 -05:00
Jidong Xiao
165034d3da adding flat profile result 2025-02-26 12:16:53 -05:00
Jidong Xiao
e275aa3ad7 adding test.cpp 2025-02-26 12:16:53 -05:00
Jidong Xiao
33c771a3a5 adding explaination of volatile 2025-02-26 12:16:53 -05:00
Jidong Xiao
f7f69ecd2f adding gprof page 2025-02-26 12:16:53 -05:00
Jidong Xiao
4d040bbec5 adding gprof page 2025-02-26 12:16:53 -05:00
Jidong Xiao
0dae55a634 adding gprof page 2025-02-26 12:16:53 -05:00
Jidong Xiao
bcbf1ac700 no functors for now 2025-02-26 12:16:53 -05:00
Jidong Xiao
0772468c29 test info moved 2025-02-26 12:16:53 -05:00
Jidong Xiao
b55c526c18 adding benchmark results 2025-02-26 12:16:53 -05:00
Jidong Xiao
5915d27df3 renaming 2025-02-26 12:16:53 -05:00
Jidong Xiao
2382c65914 adding the readme 2025-02-26 12:16:53 -05:00
Jidong Xiao
77259c6f33 adding the emplace_back and push_back example programs 2025-02-26 12:16:53 -05:00
Jidong Xiao
d7f19cb35d adding 13 2025-02-26 12:16:53 -05:00
Jidong Xiao
dddb293e04 adding order notation 2025-02-26 12:16:53 -05:00
Jidong Xiao
710830ec55 renaming 2025-02-26 12:16:53 -05:00
Jidong Xiao
3da5d38338 renaming 2025-02-26 12:16:53 -05:00
Jidong Xiao
f95296f4d1 no new line at the end 2025-02-26 12:16:53 -05:00
30 changed files with 941 additions and 97 deletions

2
.vscode/launch.json vendored
View File

@@ -51,7 +51,7 @@
"environment": [],
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb",
"preLaunchTask": "C/C++: g++ build active file"
"preLaunchTask": "C/C++: g++ build single active file"
},
{
"name": "nybusninesses",

26
.vscode/tasks.json vendored
View File

@@ -25,6 +25,32 @@
"isDefault": true
},
"detail": "Task generated by Debugger."
},
{
"type": "cppbuild",
"label": "C/C++: g++ build single active file",
"command": "/usr/bin/g++",
"args": [
"-fdiagnostics-color=always",
"-std=c++17",
"-Wall",
"-Wextra",
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
],
"version": "2.0.0"

View File

@@ -67,7 +67,7 @@ Your program will support these commands:
5. block someone.
<!--6. delete account.-->
More details about each of these five commands are given below. For all five commands, *users.txt* represents the input file, and *output.txt* represents the output file, please do not hardcode the file names into your program, as the actual input/output file may have a different name. You should just use the *argv[]* array to retrieve the name of the input and the output file. Also note that all the expected output files contain an empty line at the end of file, to match with that, you just need to make sure to use *std::endl;* (or just *endl;* if you don't use *std::*) when printing the last line of a file.
More details about each of these five commands are given below. For all five commands, *users.txt* represents the input file, and *output.txt* represents the output file, please do not hardcode the file names into your program, as the actual input/output file may have a different name. You should just use the *argv[]* array to retrieve the name of the input and the output file. <!--Also note that all the expected output files contain an empty line at the end of file, to match with that, you just need to make sure to use *std::endl;* (or just *endl;* if you don't use *std::*) when printing the last line of a file.-->
**Note**: for all the commands, you can assume the phone numbers (used in the commands) are valid and are corresponding to an existing account.

View File

@@ -28,12 +28,8 @@ int main() {
// clear out the list
a.clear();
/*
assert (a.size() == 0);
*/
/*
// simple tests of push_front, pop_front, and pop_back
a.push_front(5);
a.push_back(7);
@@ -66,7 +62,6 @@ int main() {
a.pop_front();
assert (a.size() == 0);
assert (a.begin() == a.end());
*/
return 0;
}

Binary file not shown.

View File

@@ -21,6 +21,36 @@ public:
NodeB* prev;
};
template <typename NodeType>
void PushBack(NodeType*& head, NodeType* newNode) {
// If the list is empty, newNode becomes the head
if (!head) {
head = newNode;
} else {
// Otherwise, find the tail and attach there
NodeType* current = head;
while (current->next != nullptr) {
current = current->next;
}
current->next = newNode;
newNode->prev = current;
}
}
template <typename NodeType>
void PrintList(NodeType* head) {
NodeType* current = head;
while (current != nullptr) {
// For doubles, you might do: std::cout << std::fixed << std::setprecision(5);
std::cout << current->data;
if (current->next != nullptr) {
std::cout << " -> ";
}
current = current->next;
}
std::cout << " -> nullptr" << std::endl;
}
int main() {
// Part 1: test NodeA class.
// Initialize an empty linked list, consisting of NodeA nodes.

Binary file not shown.

View File

@@ -15,6 +15,51 @@ public:
// this function returns a pointer pointing to the head node of the merged list.
template <class T>
Node<T>* mergeLists(Node<T>* head_A, Node<T>* head_B) {
// If one list is empty, return the other
if (!head_A) return head_B;
if (!head_B) return head_A;
// Decide the new head by comparing the first elements
Node<T>* head = nullptr;
Node<T>* tail = nullptr;
// Pick whichever head is smaller as the new head
if (head_A->value < head_B->value) {
head = head_A;
head_A = head_A->next;
} else {
head = head_B;
head_B = head_B->next;
}
// At this point, 'head' is the first node of the merged list
head->prev = nullptr; // new head has no previous
tail = head;
// Continue merging while both lists have nodes
while (head_A && head_B) {
if (head_A->value < head_B->value) {
tail->next = head_A;
head_A->prev = tail;
tail = tail->next;
head_A = head_A->next;
} else {
tail->next = head_B;
head_B->prev = tail;
tail = tail->next;
head_B = head_B->next;
}
}
// Append any remaining nodes from A or B
if (head_A) {
tail->next = head_A;
head_A->prev = tail;
} else if (head_B) {
tail->next = head_B;
head_B->prev = tail;
}
return head;
}
int main() {

View File

@@ -131,19 +131,32 @@ dslist<T>& dslist<T>::operator= (const dslist<T>& old) {
template <class T>
void dslist<T>::push_front(const T& v) {
Node<T>* newp = new Node<T>(v);
// if list is empty
if (!head_) {
head_ = newp;
tail_ = newp;
} else {
newp->next_ = head_;
head_->prev_ = newp;
head_ = newp;
}
++size_;
}
template <class T>
void dslist<T>::pop_front() {
assert(head_ != NULL); // list must not be empty
Node<T>* temp = head_;
head_ = head_->next_;
if (head_) {
head_->prev_ = NULL;
} else {
// list becomes empty
tail_ = NULL;
}
delete temp;
--size_;
}
template <class T>
@@ -163,11 +176,17 @@ void dslist<T>::push_back(const T& v) {
template <class T>
void dslist<T>::pop_back() {
assert(tail_ != NULL); // list must not be empty
Node<T>* temp = tail_;
tail_ = tail_->prev_;
if (tail_) {
tail_->next_ = NULL;
} else {
// list becomes empty
head_ = NULL;
}
delete temp;
--size_;
}
// do these lists look the same (length & contents)?
@@ -186,7 +205,7 @@ bool operator== (dslist<T>& left, dslist<T>& right) {
template <class T>
bool operator!= (dslist<T>& left, dslist<T>& right){ return !(left==right); }
template <class T>
template <class T>
typename dslist<T>::iterator dslist<T>::erase(iterator itr) {
assert (size_ > 0);
--size_;
@@ -256,13 +275,15 @@ void dslist<T>::copy_list(const dslist<T>& old) {
template <class T>
void dslist<T>::destroy_list() {
Node<T>* current = head_;
while (current) {
Node<T>* next = current->next_;
delete current;
current = next;
}
head_ = NULL;
tail_ = NULL;
size_ = 0;
}
#endif

View File

@@ -1,4 +1,4 @@
# Lab 8 — Recursion
# Lab 6 — Recursion
This lab gives you practice in the use of recursion to solve problems. All three checkpoints addressed in
this lab deal with finding and counting the number of paths between points on a rectilinear grid. A starting

View File

@@ -6,7 +6,6 @@ Review Recursion vs. Iteration
- “Rules” for writing recursive functions
- Advanced Recursion — problems that cannot be easily solved using iteration (for or while loops):
Merge sort
Non-linear maze search
## 12.1 Review: Iteration vs. Recursion
@@ -102,44 +101,14 @@ Count the number of pairwise comparisons that are required.
- [Leetcode problem 912: Sort an Array](https://leetcode.com/problems/sort-an-array/). Solution: [p912_sortarray.cpp](../../leetcode/p912_sortarray.cpp)
- [Leetcode problem 148: Sort List](https://leetcode.com/problems/sort-list/). Solution: [p148_sortlist.cpp](../../leetcode/p148_sortlist.cpp)
## 12.7 Example: Word Search
## 12.7 Merge Sort Run Time Complexity
- Take a look at the following grid of characters.
```console
heanfuyaadfj
crarneradfad
chenenssartr
kdfthileerdr
chadufjavcze
dfhoerpadlfc
neicoetrtlkf
paermpuohtrr
diofetaycrhg
daldruetryrt
```
- If you can start from any location of this grid, and go forward, backward, up and down. Can you find the word **computer** in this grid? (**Note**: The same letter cell may not be used more than once.)
- A sketch of the solution is as follows:
The grid of letters is represented as vector&lt;vector&lt;char&gt;&gt; grid; Each vector&lt;char&gt; represents a row. We can treat this as a two-dimensional array.
A word to be sought, such as “computer” is read as a string.
A pair of nested for loops searches the grid for occurrences of the first letter in the string. Call such a location (r, c).
- At each location where the first letter is found, a search of the second letter is initiated in the 4 neighboring locations of location (r, c).
- Make this process recursive: at each location where the *ith* letter is found, a search of the *(i+1)th* letter is initiated in the 4 neighboring locations.
- The search can stop when all letters of the string are found - this is the base case of the recursion.
- Question: how to make sure we do not use the same letter more than once on our success path?
- Each time we divide the vector into two halves, the number of subproblems doubles, leading to a recursion tree structure.
- At each level, the vector size reduces by half. The division continues until we reach base cases where each sub-vector contains only one element.
- The number of levels in the recursion tree corresponds to how many times we can divide n by 2 until we reach 1: log<sub>2</sub> n.
- At each level of recursion, we need to merge two sorted halves into one sorted vector. Merging two halves of size n requires O(n) operations. The same amount of merging work happens at each level of recursion.
- Total Work Done: Since we have O(logn) levels in the recursion tree and each level requires O(n) merging work, the total time complexity is: O(nlogn).
## 12.8 Exercise: Complete the implementation
- [Leetcode problem 79: Word Search](https://leetcode.com/problems/word-search/). Solution: [p79_wordsearch.cpp](../../leetcode/p79_wordsearch.cpp).
- [Leetcode problem 212: Word Search II](https://leetcode.com/problems/word-search-ii/). Solution: To be added. (it won't be added until we learn Trie).
## 12.9 Summary of Nonlinear Word Search Recursion
- Recursion starts at each location where the first letter is found.
- Each recursive call attempts to find the next letter by searching around the current position. When it is found,
a recursive call is made.
- The current path is maintained at all steps of the recursion.
- The “base case” occurs when the path is full or all positions around the current position have been tried.
## 12.10 Leetcode Exercises
## 12.8 Leetcode Exercises
- [Leetcode problem 704: Binary Search](https://leetcode.com/problems/binary-search/). Solution: [p704_binarysearch.v1.cpp](../../leetcode/p704_binarysearch.v1.cpp) or [p704_binarysearch.v2.cpp](../../leetcode/p704_binarysearch.v2.cpp)

View File

@@ -0,0 +1,63 @@
# Test 2 Information
Students will be randomly assigned to a test room and seating zone will be on Submitty soon.
<!-- If you havent filled out the “Left or Right Handed” gradeable by Tuesday night, we will assume you are
right handed. This is used for seating assignments.-->
- Test 2 will be held **Thursday, 02/27/2025 from 6-7:50pm**.
<!-- No make-ups will be given except for pre-approved absence or illness, and a written excuse from the Dean
of Students or the Student Experience office or the RPI Health Center will be required. -->
If you have a letter from Disability Services for Students and you have not already emailed it to
ds_instructors@cs.rpi.edu, please do so ASAP. Meredith Widman will be in contact with you about
your accommodations for the test.
- Coverage: Lectures 1-13, Labs 1-5, and Homeworks 1-5.
- Practice problems from previous exams are available on the course website. The best way to prepare is to completely work through and write out your solution to each problem, before looking at the answers.
- OPTIONAL: you are allowed to bring one physical piece of 8.5x11” paper, thats two “sides”. We will check at the start of the exam that you do not have more than one piece of paper for your notes!
<!-- - OPTIONAL: Prepare a 2 page, black & white, 8.5x11”, portrait orientation .pdf of notes you would like to have during the exam. This may be digitally prepared or handwritten and scanned or photographed. The file may be no bigger than 2MB. You will upload this file to Submitty (“Test 1 Notes Upload”) before Wednesday night @11:59pm. We will print this and attach it to your exam. Make sure you get credit for test case 2 and that you view the details to verify your sheet looks correct. You cannot bring your own cribsheet, you must submit one electronically. IMPORTANT: Using third party websites to make a PDF may generate an invalid PDFs that prints weird. Your word processors -> save as/export to PDF, or Google Docs -> Download -> PDF should be safe. -->
- Bring to the exam room:
Your Rensselaer photo ID card.
Pencil(s) & eraser (pens are ok, but not recommended). The exam will involve handwriting code on paper (and other short answer problem solving). Neat legible handwriting is appreciated.
Computers, cell-phones, smart watches, calculators, music players, etc. are not permitted. Please do not bring your laptop, books, backpack, etc. to the exam room leave everything in your dorm room. Unless you are coming directly from another class or sports/club meeting.
<!-- Do not bring your own scratch paper. We will provide scratch paper.-->
# Lecture 13 --- Advanced Recursion, Part II
- Advanced Recursion — problems that cannot be easily solved using iteration (for or while loops):
Non-linear maze search
## 13.1 Example: Word Search
- Take a look at the following grid of characters.
```console
heanfuyaadfj
crarneradfad
chenenssartr
kdfthileerdr
chadufjavcze
dfhoerpadlfc
neicoetrtlkf
paermpuohtrr
diofetaycrhg
daldruetryrt
```
- If you can start from any location of this grid, and go forward, backward, up and down. Can you find the word **computer** in this grid? (**Note**: The same letter cell may not be used more than once.)
- A sketch of the solution is as follows:
The grid of letters is represented as vector&lt;vector&lt;char&gt;&gt; grid; Each vector&lt;char&gt; represents a row. We can treat this as a two-dimensional array.
A word to be sought, such as “computer” is read as a string.
A pair of nested for loops searches the grid for occurrences of the first letter in the string. Call such a location (r, c).
- At each location where the first letter is found, a search of the second letter is initiated in the 4 neighboring locations of location (r, c).
- Make this process recursive: at each location where the *ith* letter is found, a search of the *(i+1)th* letter is initiated in the 4 neighboring locations.
- The search can stop when all letters of the string are found - this is the base case of the recursion.
- Question: how to make sure we do not use the same letter more than once on our success path?
## 13.2 Exercise: Complete the implementation
- [Leetcode problem 79: Word Search](https://leetcode.com/problems/word-search/). Solution: [p79_wordsearch.cpp](../../leetcode/p79_wordsearch.cpp).
- [Leetcode problem 212: Word Search II](https://leetcode.com/problems/word-search-ii/). Solution: To be added. (it won't be added until we learn Trie).
## 13.3 Summary of Nonlinear Word Search Recursion
- Recursion starts at each location where the first letter is found.
- Each recursive call attempts to find the next letter by searching around the current position. When it is found,
a recursive call is made.
- The current path is maintained at all steps of the recursion.
- The “base case” occurs when the path is full or all positions around the current position have been tried.

View File

@@ -1,30 +1,10 @@
# Lecture 14 --- Stack and Queue
## Test 2 Information
- Test 2 will be held Thursday, February 29th, 2024 from 6-7:50pm.
- No make-ups will be given except for pre-approved absence or illness, and a written excuse from the Dean of Students or the Student Experience office or the RPI Health Center will be required.
- If you have a letter from Disability Services for Students and you have not already emailed it to ds_instructors@cs.rpi.edu, please do so ASAP. Shianne Hulbert will be in contact with you about your accommodations for the test. And you will go to Lally 102 for the test.
- Students assigned test room, row, and seat assignments will be re-randomized. If you dont have a seating assignment when you log onto Submitty, let us know via the ds_instructors list.
- Coverage: Lectures 1-14, Labs 1-7, HW 1-5.
- OPTIONAL: you are allowed to bring two physical pieces of 8.5x11” paper, thats four “sides”. We will check at the start of the exam that you do not have more than two pieces of paper for your notes!
- All students must bring their Rensselaer photo ID card.
- Bring pencil(s) & eraser (pens are ok, but not recommended).
- Computers, cell-phones, smart watches, calculators, music players, etc. are not permitted.
- Practice problems from previous tests are available on the [course materials](https://submitty.cs.rpi.edu/courses/s24/csci1200/course_materials) page on Submitty.
## Other Announcements
- Resources will be dedicated to test grading and thus no office hours on Friday in test weeks. (week of test 1, week of test 2, week of test 3)
- Jidong's new office hours (effective after the spring break): Monday 2-4pm, Wednesday 1-3pm.
- HW6 will be ready on Thursday night 10pm. Only 10 students passed all test cases last semester. Not just because it's hard, it's just that submitty will stop your program if it runs too long. In order to pass some of the tricky test cases, your program must be fast enough.
## Todays Lecture
- Function Objects
- STL Queue and STL Stack
## 14.0 Some Special Syntax
<!-- ## 14.0 Some Special Syntax
The following program demonstrates some special syntax about C++ constructors.
@@ -164,8 +144,9 @@ int main() {
```
- You can compile and run this [example](multiply.cpp).
-->
## 14.4 Additional STL Container Classes: Stacks
## 14.1 Additional STL Container Classes: Stacks
<!--Weve studied STL vectors, lists, maps, and sets. These data structures provide a wide range of flexibility in
terms of operations. One way to obtain computational efficiency is to consider a simplified set of operations or
@@ -178,7 +159,7 @@ functionality.-->
- Stacks may be implemented efficiently in terms of vectors and lists, although vectors are preferable.
- All stack operations are O(1).
### 14.4.1 Member functions of std::stack
### 14.1.1 Member functions of std::stack
- push(const T& value): Adds an element value to the top of the stack.
- pop(): Removes the top element from the stack.
@@ -186,7 +167,7 @@ functionality.-->
- empty(): Checks if the stack is empty. Returns true if the stack is empty, false otherwise.
- size(): Returns the number of elements in the stack.
### 14.4.2 Stack Example Program
### 14.1.2 Stack Example Program
- Following is an example program, remember to include the stack library.
@@ -222,7 +203,11 @@ int main() {
You can compile and run this above [program](stack.cpp).
## 14.5 Additional STL Container Classes: Queues
### 14.1.3 Stack Implementation
We have the stack implementation and test code here: [stack.h](stack.h), [stack_test.cpp](stack_test.cpp).
## 14.2 Additional STL Container Classes: Queues
- A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle.
- Queues allow insertion at one end, called the back and removal from the other end, called the front.
@@ -230,7 +215,7 @@ You can compile and run this above [program](stack.cpp).
- Queues may be implemented efficiently in terms of a list. Using vectors for queues is also possible, but requires more work to get right.
- All queue operations are O(1).
### 14.5.1 Member functions of std::queue
### 14.2.1 Member functions of std::queue
- push(const T& value): Adds an element value to the rear of the queue. This operation is also known as enqueue.
- pop(): Removes the front element from the queue. This operation is also known as dequeue.
@@ -238,7 +223,7 @@ You can compile and run this above [program](stack.cpp).
- empty(): Checks if the queue is empty. Returns true if the queue is empty, false otherwise.
- size(): Returns the number of elements in the queue.
### 14.5.2 Queue Example Program
### 14.2.2 Queue Example Program
- Following is an example program, remember to include the queue library.
@@ -274,7 +259,7 @@ int main() {
You can compile and run this above [program](queue.cpp).
## 14.6 Leetcode Exercises
## 14.3 Leetcode Exercises
- [Leetcode problem 225: Implement Stack using Queues](https://leetcode.com/problems/implement-stack-using-queues/). Solution: [p225_stack_using_queues.cpp](../../leetcode/p225_stack_using_queues.cpp).
- [Leetcode problem 232: Implement Queue using Stacks](https://leetcode.com/problems/implement-queue-using-stacks/). Solution: [p232_queue_using_stacks.cpp](../../leetcode/p232_queue_using_stacks.cpp).

View File

@@ -0,0 +1,43 @@
#include <iostream>
#include <vector>
template <class T>
class Stack {
private:
std::vector<T> data; // Vector to store the stack elements
public:
// Push element onto the stack
void push(const T& value) {
data.push_back(value);
}
// Pop element from the stack
void pop() {
if (!empty()) {
data.pop_back();
} else {
std::cout << "Stack is empty, cannot pop!" << std::endl;
}
}
// Get the top element of the stack
int top() {
if (!empty()) {
return data.back();
} else {
std::cout << "Stack is empty!" << std::endl;
return -1; // Or handle as needed
}
}
// Check if the stack is empty
bool empty() {
return data.empty();
}
// Get the size of the stack
int size() {
return data.size();
}
};

View File

@@ -0,0 +1,26 @@
#include <iostream>
#include "stack.h"
int main() {
Stack<int> myStack;
myStack.push(10);
myStack.push(20);
myStack.push(30);
myStack.push(40);
myStack.push(50);
std::cout << "Size of stack: " << myStack.size() << std::endl;
std::cout << "Top element: " << myStack.top() << std::endl;
if (!myStack.empty()) {
std::cout << "Stack is not empty" << std::endl;
} else {
std::cout << "Stack is empty" << std::endl;
}
myStack.pop();
std::cout << "Top element after pop: " << myStack.top() << std::endl;
return 0;
}

View File

@@ -0,0 +1,171 @@
# `emplace_back` vs. `push_back` in C++
## Overview
Both `push_back` and `emplace_back` are member functions of C++ standard library containers (e.g., `std::vector`, `std::deque`) used to add elements to the end of the container. However, they differ in how they construct and insert these elements.
## `push_back`
The `push_back` function adds an existing object to the end of the container. It requires the object to be constructed before being passed to the function.
**Usage:**
```cpp
std::vector<MyClass> vec;
MyClass obj(args);
vec.push_back(obj); // Adds a copy of 'obj' to the vector
```
If the object is movable, push_back can utilize move semantics:
```cpp
vec.push_back(std::move(obj)); // Moves 'obj' into the vector
```
## `emplace_back`
The emplace_back function constructs a new element in place at the end of the container. It forwards the provided arguments to the constructor of the element, eliminating the need for a temporary object.
**Usage:**
```cpp
std::vector<MyClass> vec;
vec.emplace_back(args); // Constructs 'MyClass' directly in the vector
```
This approach can improve performance by avoiding unnecessary copy or move operations, especially for complex objects.
## Key Differences
- **Object Construction:**
- `push_back`: Requires a fully constructed object.
- `emplace_back`: Constructs the object in place using provided arguments.
- **Performance:**
- `push_back`: May involve copy or move operations, depending on whether the object is passed by value or moved.
- `emplace_back`: Potentially more efficient for complex objects, as it avoids extra copy or move operations.
## When to Use
- **Use `push_back`** when you have an existing object that you want to add to the container.
- **Use `emplace_back`** when you want to construct a new object directly in the container, especially if the object's construction is complex or resource-intensive.
## Example
```cpp
#include <vector>
#include <string>
class MyClass {
public:
MyClass(int id, const std::string& name) : id_(id), name_(name) {}
private:
int id_;
std::string name_;
};
int main() {
std::vector<MyClass> vec;
// Using push_back
MyClass obj(1, "Object1");
vec.push_back(obj); // Adds a copy of 'obj'
// Using emplace_back
vec.emplace_back(2, "Object2"); // Constructs 'MyClass(2, "Object2")' in place
return 0;
}
```
In this example, emplace_back constructs the MyClass object directly within the vector, potentially reducing overhead compared to push_back, which adds a copy of an existing object.
For a visual explanation and further insights, consider watching the following video: [![C++ From Scratch: push_back vs. emplace_back](https://img.youtube.com/vi/BbPWrkgj1I4/0.jpg)](https://www.youtube.com/watch?v=BbPWrkgj1I4)
## Benchmarks
Compile and run these 6 programs to see the performance difference.
[push_back.cpp](push_back.cpp) [push_back_move.cpp](push_back_move.cpp) [push_back_reserve.cpp](push_back_reserve.cpp) [emplace_back_slower.cpp](emplace_back_slower.cpp) [emplace_back_faster.cpp](emplace_back_faster.cpp) [emplace_back_fastest.cpp](emplace_back_fastest.cpp)
```console
$g++ push_back.cpp -o push_back
$g++ push_back_move.cpp -o push_back_move
$g++ push_back_reserve.cpp -o push_back_reserve
$g++ emplace_back_slower.cpp -o emplace_back_slower
$g++ emplace_back_faster.cpp -o emplace_back_faster
$g++ emplace_back_fastest.cpp -o emplace_back_fastest
$time ./push_back
real 0m0.496s
user 0m0.236s
sys 0m0.260s
$time ./push_back
real 0m0.497s
user 0m0.257s
sys 0m0.240s
$time ./push_back_move
real 0m0.353s
user 0m0.072s
sys 0m0.280s
$time ./push_back_move
real 0m0.364s
user 0m0.104s
sys 0m0.260s
$time ./push_back_reserve
real 0m0.340s
user 0m0.088s
sys 0m0.252s
$time ./push_back_reserve
real 0m0.338s
user 0m0.108s
sys 0m0.231s
$time ./emplace_back_slower
real 0m0.487s
user 0m0.248s
sys 0m0.239s
$time ./emplace_back_slower
real 0m0.483s
user 0m0.256s
sys 0m0.228s
$time ./emplace_back_faster
real 0m0.360s
user 0m0.096s
sys 0m0.264s
$time ./emplace_back_faster
real 0m0.351s
user 0m0.112s
sys 0m0.239s
$time ./emplace_back_fastest
real 0m0.336s
user 0m0.104s
sys 0m0.232s
$time ./emplace_back_fastest
real 0m0.336s
user 0m0.101s
sys 0m0.235s
```

View File

@@ -0,0 +1,56 @@
#include <iostream>
#include <vector>
#include <cstring>
class LargeObject {
public:
// constructor allocating a large amount of memory
LargeObject(int size) {
size_ = size;
data_ = new char[size_];
// initialize data with some values
std::memset(data_, 'A', size_);
}
// copy constructor
LargeObject(const LargeObject& other) {
size_ = other.size_;
data_ = new char[size_];
std::memcpy(data_, other.data_, size_);
}
// move constructor
// marking functions as noexcept allows the compiler to make certain optimizations,
// knowing that the function won't emit exceptions. This can result in more efficient code generation.
LargeObject(LargeObject&& other) noexcept {
size_ = other.size_;
data_ = other.data_;
other.data_ = nullptr;
other.size_ = 0;
}
// destructor
~LargeObject() {
delete[] data_;
}
private:
size_t size_;
char* data_;
};
int main() {
int numElements = 1000000; // number of elements
int dataSize = 1024; // size of each LargeObject's data
std::vector<LargeObject> vec;
for (size_t i = 0; i < numElements; ++i) {
vec.emplace_back(dataSize); // move constructor would get called here
// when a std::vector exceeds its current capacity,
// it allocates a larger block of memory and moves existing elements to the new storage location.
// This reallocation process involves calling the move constructor for each existing element.
}
return 0;
}

View File

@@ -0,0 +1,54 @@
#include <iostream>
#include <vector>
#include <cstring>
class LargeObject {
public:
// constructor allocating a large amount of memory
LargeObject(int size) {
size_ = size;
data_ = new char[size_];
// initialize data with some values
std::memset(data_, 'A', size_);
}
// copy constructor
LargeObject(const LargeObject& other) {
size_ = other.size_;
data_ = new char[size_];
std::memcpy(data_, other.data_, size_);
}
// move constructor
// marking functions as noexcept allows the compiler to make certain optimizations,
// knowing that the function won't emit exceptions. This can result in more efficient code generation.
LargeObject(LargeObject&& other) noexcept {
size_ = other.size_;
data_ = other.data_;
other.data_ = nullptr;
other.size_ = 0;
}
// destructor
~LargeObject() {
delete[] data_;
}
private:
size_t size_;
char* data_;
};
int main() {
int numElements = 1000000; // number of elements
int dataSize = 1024; // size of each LargeObject's data
std::vector<LargeObject> vec;
vec.reserve(numElements);
for (size_t i = 0; i < numElements; ++i) {
vec.emplace_back(dataSize);
}
return 0;
}

View File

@@ -0,0 +1,46 @@
#include <iostream>
#include <vector>
#include <cstring>
class LargeObject {
public:
// constructor allocating a large amount of memory
LargeObject(int size) {
size_ = size;
data_ = new char[size_];
// initialize data with some values
std::memset(data_, 'A', size_);
}
// copy constructor
LargeObject(const LargeObject& other) {
size_ = other.size_;
data_ = new char[size_];
std::memcpy(data_, other.data_, size_);
}
// destructor
~LargeObject() {
delete[] data_;
}
private:
size_t size_;
char* data_;
};
int main() {
int numElements = 1000000; // number of elements
int dataSize = 1024; // size of each LargeObject's data
std::vector<LargeObject> vec;
for (size_t i = 0; i < numElements; ++i) {
vec.emplace_back(dataSize); // copy constructor would get called here
// when a std::vector exceeds its current capacity,
// it allocates a larger block of memory and copies existing elements to the new storage location.
// this reallocation process involves calling the copy constructor for each existing element.
}
return 0;
}

View File

@@ -0,0 +1,44 @@
#include <iostream>
#include <vector>
#include <cstring>
class LargeObject {
public:
// constructor allocating a large amount of memory
LargeObject(int size) {
size_ = size;
data_ = new char[size_];
// initialize data with some values
std::memset(data_, 'A', size_);
}
// copy constructor
LargeObject(const LargeObject& other) {
size_ = other.size_;
data_ = new char[size_];
std::memcpy(data_, other.data_, size_);
}
// destructor
~LargeObject() {
delete[] data_;
}
private:
size_t size_;
char* data_;
};
int main() {
int numElements = 1000000; // number of elements
int dataSize = 1024; // size of each LargeObject's data
std::vector<LargeObject> vec;
for (int i = 0; i < numElements; ++i) {
LargeObject obj(dataSize); // calls constructor
vec.push_back(obj); // calls copy constructor
}
return 0;
}

View File

@@ -0,0 +1,54 @@
#include <iostream>
#include <vector>
#include <cstring>
class LargeObject {
public:
// constructor allocating a large amount of memory
LargeObject(int size) {
size_ = size;
data_ = new char[size_];
// initialize data with some values
std::memset(data_, 'A', size_);
}
// copy constructor
LargeObject(const LargeObject& other) {
size_ = other.size_;
data_ = new char[size_];
std::memcpy(data_, other.data_, size_);
}
// move constructor
// marking functions as noexcept allows the compiler to make certain optimizations,
// knowing that the function won't emit exceptions. This can result in more efficient code generation.
LargeObject(LargeObject&& other) noexcept {
size_ = other.size_;
data_ = other.data_;
other.data_ = nullptr;
other.size_ = 0;
}
// destructor
~LargeObject() {
delete[] data_;
}
private:
size_t size_;
char* data_;
};
int main() {
int numElements = 1000000; // number of elements
int dataSize = 1024; // size of each LargeObject's data
std::vector<LargeObject> vec;
for (int i = 0; i < numElements; ++i) {
LargeObject obj(dataSize); // calls constructor
vec.push_back(std::move(obj)); // calls move constructor
}
return 0;
}

View File

@@ -0,0 +1,55 @@
#include <iostream>
#include <vector>
#include <cstring>
class LargeObject {
public:
// constructor allocating a large amount of memory
LargeObject(int size) {
size_ = size;
data_ = new char[size_];
// initialize data with some values
std::memset(data_, 'A', size_);
}
// copy constructor
LargeObject(const LargeObject& other) {
size_ = other.size_;
data_ = new char[size_];
std::memcpy(data_, other.data_, size_);
}
// move constructor
// marking functions as noexcept allows the compiler to make certain optimizations,
// knowing that the function won't emit exceptions. This can result in more efficient code generation.
LargeObject(LargeObject&& other) noexcept {
size_ = other.size_;
data_ = other.data_;
other.data_ = nullptr;
other.size_ = 0;
}
// destructor
~LargeObject() {
delete[] data_;
}
private:
size_t size_;
char* data_;
};
int main() {
int numElements = 1000000; // number of elements
int dataSize = 1024; // size of each LargeObject's data
std::vector<LargeObject> vec;
vec.reserve(numElements);
for (int i = 0; i < numElements; ++i) {
LargeObject obj(dataSize); // calls constructor
vec.push_back(std::move(obj)); // calls move constructor
}
return 0;
}

View File

@@ -0,0 +1,136 @@
# C++ Profiling with `gprof`
## What is `gprof`?
`gprof` is a GNU profiler that helps analyze where a program spends most of its execution time. It provides function call counts and execution time details.
## Installing `gprof`
```sh
$ sudo apt-get install binutils
```
## Compiling a C++ Program for Profiling
To use `gprof`, compile your program with the `-pg` flag:
```sh
$ g++ -pg -o test test.cpp
```
## Example C++ Program
Create a file [test.cpp](test.cpp) with the following code:
```cpp
#include <iostream>
void heavyComputation() {
volatile long long sum = 0;
for (long long i = 0; i < 500000000; ++i) {
sum += i; // Simple but expensive loop
}
}
void lightComputation() {
volatile int sum = 0;
for (int i = 0; i < 100000; ++i) {
sum += i; // Lighter loop
}
}
int main() {
heavyComputation(); // Call heavy function once
for (int i = 0; i < 1000; ++i) {
lightComputation(); // Call light function many times
}
return 0;
}
```
## Running and Profiling the Program
1. Compile the program:
```sh
$ g++ -pg -o test test.cpp
```
2. Execute the program to generate `gmon.out`:
```sh
$ ./test
```
3. Analyze the profiling data:
```sh
$ gprof test gmon.out > profile.txt
$ cat profile.txt
```
## Understanding the Output (in the profile.txt file)
- **Flat Profile**: Shows execution time spent in each function.
- **Call Graph**: Displays function call relationships and their execution time.
The flat profile from gprof also indicates the percentage of time spent in each function, helping to identify bottlenecks. For the above test program, the flat profile is like this:
```plaintext
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
85.71 0.24 0.24 1 240.00 240.00 heavyComputation()
14.29 0.28 0.04 1000 0.04 0.04 lightComputation()
0.00 0.28 0.00 1 0.00 0.00 __static_initialization_and_destruction_0(int, int)
```
As can be seen, the profiling results show that heavyComputation() takes significantly more execution time than lightComputation(), even though lightComputation() is called 1000 times.
Note: The __static_initialization_and_destruction_0(int, int) function is generated by the compiler during the static initialization and destruction phases of a program, particularly for global or static variables.
## Best Practices for Using `gprof`
- Use `-O2` optimizations but **avoid `-O3`**, which may inline functions and reduce profiling accuracy.
- Profile with realistic input data to get meaningful results.
- Optimize the slowest functions first based on the profiling report.
## Conclusion
`gprof` is a powerful tool for detecting performance bottlenecks in C++ programs. By identifying expensive functions, developers can make targeted optimizations.
# Why Use `volatile` in the above program?
## Compiler May Remove the Loops
When compiling a program, the compiler applies optimizations to make the code run faster. One such optimization is **dead code elimination**, where the compiler removes code that does **not affect the program's observable behavior**.
For example, consider this function:
```cpp
void heavyComputation() {
long long sum = 0;
for (long long i = 0; i < 500000000; ++i) {
sum += i;
}
}
```
- The compiler notices that `sum` is **never used outside the function**.
- Since the result is discarded, the compiler **may completely remove the loop**.
- This means `heavyComputation()` might do **nothing** at runtime, which ruins our profiling experiment.
---
## How Does `volatile` Help?
Declaring a variable as `volatile` tells the compiler:
"This variable might change in ways you cannot predict, so do not optimize it away."
For example:
```cpp
void heavyComputation() {
volatile long long sum = 0; // Mark sum as volatile
for (long long i = 0; i < 500000000; ++i) {
sum += i;
}
}
```
- Now, **even if `sum` is never used**, the compiler **must** perform the loop.
- The `volatile` keyword prevents the compiler from assuming that `sum` is unimportant.
- This ensures that the loop actually runs during profiling.
---
## **Does `volatile` Affect Performance?**
Yes, but only slightly.
- **Without `volatile`**, the compiler can optimize the loop aggressively.
- **With `volatile`**, every read and write to `sum` is guaranteed to happen exactly as written, preventing some optimizations.
However, this small cost is **worth it for benchmarking**, because it ensures that the loops are not removed.

View File

@@ -0,0 +1,25 @@
#include <iostream>
void heavyComputation() {
// The use of volatile prevents compiler optimizations that could remove the loops.
volatile long long sum = 0;
for (long long i = 0; i < 500000000; ++i) {
sum += i; // Simple but expensive loop
}
}
void lightComputation() {
// The use of volatile prevents compiler optimizations that could remove the loops.
volatile int sum = 0;
for (int i = 0; i < 100000; ++i) {
sum += i; // Lighter loop
}
}
int main() {
heavyComputation(); // Call heavy function once
for (int i = 0; i < 1000; ++i) {
lightComputation(); // Call light function many times
}
return 0;
}