Compare commits
22 Commits
7a6c6b2090
...
c20ce7c2c3
| Author | SHA1 | Date | |
|---|---|---|---|
| c20ce7c2c3 | |||
|
|
05c92a01ae | ||
|
|
b724fb5a78 | ||
|
|
b26d858b72 | ||
|
|
ad71d0519f | ||
|
|
165034d3da | ||
|
|
e275aa3ad7 | ||
|
|
33c771a3a5 | ||
|
|
f7f69ecd2f | ||
|
|
4d040bbec5 | ||
|
|
0dae55a634 | ||
|
|
bcbf1ac700 | ||
|
|
0772468c29 | ||
|
|
b55c526c18 | ||
|
|
5915d27df3 | ||
|
|
2382c65914 | ||
|
|
77259c6f33 | ||
|
|
d7f19cb35d | ||
|
|
dddb293e04 | ||
|
|
710830ec55 | ||
|
|
3da5d38338 | ||
|
|
f95296f4d1 |
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -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
26
.vscode/tasks.json
vendored
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -28,12 +28,8 @@ int main() {
|
||||
// clear out the list
|
||||
a.clear();
|
||||
|
||||
/*
|
||||
assert (a.size() == 0);
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
BIN
labs/list_implementation/checkpoint2
Executable file
BIN
labs/list_implementation/checkpoint2
Executable file
Binary file not shown.
@@ -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.
|
||||
|
||||
BIN
labs/list_implementation/checkpoint3
Executable file
BIN
labs/list_implementation/checkpoint3
Executable file
Binary file not shown.
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<vector<char>> grid; Each vector<char> 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)
|
||||
|
||||
63
lectures/13_advanced_recursion_II/README.md
Normal file
63
lectures/13_advanced_recursion_II/README.md
Normal 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 haven’t 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, that’s 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 processor’s -> 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<vector<char>> grid; Each vector<char> 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.
|
||||
@@ -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.
|
||||
- Student’s assigned test room, row, and seat assignments will be re-randomized. If you don’t 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, that’s 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.
|
||||
|
||||
## Today’s 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
|
||||
|
||||
<!--We’ve 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).
|
||||
|
||||
43
lectures/14_stacks_queues/stack.h
Normal file
43
lectures/14_stacks_queues/stack.h
Normal 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();
|
||||
}
|
||||
};
|
||||
26
lectures/14_stacks_queues/stack_test.cpp
Normal file
26
lectures/14_stacks_queues/stack_test.cpp
Normal 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;
|
||||
}
|
||||
171
lectures/optimization/emplace_back/README.md
Normal file
171
lectures/optimization/emplace_back/README.md
Normal 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: [](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
|
||||
```
|
||||
56
lectures/optimization/emplace_back/emplace_back_faster.cpp
Normal file
56
lectures/optimization/emplace_back/emplace_back_faster.cpp
Normal 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;
|
||||
}
|
||||
54
lectures/optimization/emplace_back/emplace_back_fastest.cpp
Normal file
54
lectures/optimization/emplace_back/emplace_back_fastest.cpp
Normal 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;
|
||||
}
|
||||
46
lectures/optimization/emplace_back/emplace_back_slower.cpp
Normal file
46
lectures/optimization/emplace_back/emplace_back_slower.cpp
Normal 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;
|
||||
}
|
||||
44
lectures/optimization/emplace_back/push_back.cpp
Normal file
44
lectures/optimization/emplace_back/push_back.cpp
Normal 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;
|
||||
}
|
||||
54
lectures/optimization/emplace_back/push_back_move.cpp
Normal file
54
lectures/optimization/emplace_back/push_back_move.cpp
Normal 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;
|
||||
}
|
||||
55
lectures/optimization/emplace_back/push_back_reserve.cpp
Normal file
55
lectures/optimization/emplace_back/push_back_reserve.cpp
Normal 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;
|
||||
}
|
||||
136
lectures/optimization/gprof/README.md
Normal file
136
lectures/optimization/gprof/README.md
Normal 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.
|
||||
25
lectures/optimization/gprof/test.cpp
Normal file
25
lectures/optimization/gprof/test.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user