In our previous lesson, we established a toolkit of Linux commands for inspecting files, monitoring system resources, and managing processes. These skills are essential for observing what your code is doing at any given moment. Now, we'll address a related, equally crucial question: how do you manage the evolution of your code over time? As you conduct performance experiments, you'll constantly be making changes, and you need a robust way to track them, try new ideas safely, and revert to previous versions if an experiment fails.
This lesson introduces Git, the industry-standard version control system. Mastering Git is non-negotiable for any software engineering role, and it's particularly vital in performance work where you'll run many experiments to find the optimal solution. We will cover how to use Git's core features—commits, branches, and merges—to manage a simple performance experiment from start to finish.
By the end of this lesson, you will be able to:
- Track a project's history with snapshots called commits.
- Create isolated branches to experiment with new code.
- Merge successful experiments back into your main codebase.
- Understand and resolve simple merge conflicts.
What is Version Control?
Before diving into commands, it's important to understand what a version control system (VCS) does. At its heart, a VCS is a system that records changes to a file or set of files over time so that you can recall specific versions later. This allows you to revert files back to a previous state, compare changes over time, and see who last modified something that might be causing a problem.
For a formal introduction to these concepts, the documentation from GitHub provides a concise overview.
This reading will introduce you to the fundamental ideas behind version control.
Read the first two sections, About version control and Git and About repositories. Focus on understanding the purpose of a VCS and the definitions of a repository, a commit, and a branch.
Getting Started: Your First Repository
Let's apply these concepts to the benchmark_project you've been working on. We'll turn it into a Git repository.
First, you need to ensure Git is installed and configured. The following video by Corey Schafer will guide you through the one-time setup.
Git Tutorial for Beginners: Command-Line Fundamentals
This video covers the initial installation and configuration of Git.
Watch from the beginning to the configuration steps. You need to set your name and email. These are attached to every commit you make, which is essential for tracking contributions.
Once configured, navigate to your benchmark_project directory in your terminal. To initialize it as a Git repository, you just need one command:
git init
You'll see a message like Initialized empty Git repository in .../.git/. This command creates a hidden .git subdirectory where Git stores all the history and metadata for your project.
The Core Workflow: Modify, Stage, Commit
Working with Git involves a three-step cycle that moves your changes from your working files into the repository's history.
- Working Directory: This is your project folder with all your files. Any changes you make to files happen here.
- Staging Area (or Index): This is an intermediate area where you group changes together before committing them. It lets you craft a precise snapshot, even if you've made many unrelated changes in your working directory.
- Repository (.git directory): This is where Git permanently stores the snapshots (commits) of your project's history.
Let's walk through this cycle. The command git status is your best friend; it tells you the current state of these three areas. Run it now in your project directory. You'll see all your existing project files listed under "Untracked files."
To start tracking files, you use git add. This moves them from the working directory to the staging area.
# To add a specific file
git add benchmark/data_processing.py
# To add all new and modified files
git add .
Let's add everything. Run git add . and then run git status again. You'll see that your files are now under "Changes to be committed."
Finally, to save this snapshot to your history, you use git commit. The -m flag lets you provide a descriptive message.
git commit -m "Initial commit of benchmark project"
You've just made your first commit! To see the project's history, use git log. This command shows a chronological list of commits.
Branching: The Key to Safe Experimentation
So far, all our work has been on the default branch, which is usually named main or master. Committing directly to this branch is fine for simple additions, but what if you want to try a risky performance optimization? You don't want to break your working code.
This is where branching comes in. A branch is an independent line of development. You can create a new branch, make experimental changes on it, and it won't affect your stable main branch.
This video from SuperSimpleDev provides an excellent conceptual introduction to branching and merging.
Git Branching and Merging - Detailed Tutorial
This video explains the motivation for branching and walks through the basic commands.
Watch the first three sections: The introduction explains the problem that branching solves. Creating a branch shows how to create a new branch and switch to it. Committing to a branch demonstrates how changes on one branch are isolated from another.
Let's apply this to our project. Suppose we want to experiment with a new data processing technique. We can create a new branch for this experiment.
The command git checkout -b <branch-name> creates a new branch and immediately switches to it.
git checkout -b perf-experiment-numpy
Run git branch to see all your local branches. The one with an asterisk (*) is the one you're currently on. Now, any changes you make will be on the perf-experiment-numpy branch.
Open your benchmark/data_processing.py file. Let's make a simple change. Find a list comprehension or a simple loop that performs a numeric calculation and replace it with a NumPy equivalent (even if you haven't covered NumPy yet, this illustrates a plausible performance optimization). For example, if you had:
# original code
squared_latencies = [l**2 for l in latencies]
Change it to:
import numpy as np
# new code
squared_latencies = np.square(np.array(latencies))
Now, commit this change to your experiment branch:
git add benchmark/data_processing.py
git commit -m "Feat: Use NumPy for squaring latencies"
(Note: "Feat:" is a common convention for commit messages indicating a new feature or change.)
Now, switch back to the main branch: git checkout main. Open data_processing.py again. You'll see that the NumPy change is gone! Your main branch is untouched and remains in its last known-good state. This isolation is the power of branching.
Merging: Integrating Your Successes
Your experiment on the perf-experiment-numpy branch was a success, and you now want to incorporate it into your main project. This process is called merging.
First, make sure you are on the branch you want to merge into. In our case, that's main.
git checkout main
Then, use the git merge command, specifying the branch you want to merge from.
git merge perf-experiment-numpy
Git will perform a "fast-forward" merge, applying the commits from your experiment branch on top of main. If you run git log, you will now see the commit from the perf-experiment-numpy branch in the history of main. The optimization is now part of your main codebase.
Since the experiment is complete, you can delete the branch to keep your repository clean.
git branch -d perf-experiment-numpy
This entire cycle—creating a feature branch, committing changes, and merging it back—is known as the Feature Branch Workflow. It's the standard way of working in professional software development.
This article from Atlassian provides a detailed professional overview of the workflow you just practiced.
Read the sections from How it works down to the end of the "Merge your pull request" section. Don't worry about the "pull request" parts for now; that relates to remote repositories, which we'll cover later. Focus on the local command sequence for creating a branch, making commits, and merging it back to main. The "Example" section provides a great narrative walkthrough.
Handling Merge Conflicts
Sometimes, Git cannot automatically merge branches. This happens if you've changed the exact same lines in the exact same file on both branches. This is called a merge conflict. Git doesn't know which version of the line to keep, so it stops and asks you to resolve it.
The video you watched earlier also has an excellent, clear explanation of how to handle this.
Git Branching and Merging - Detailed Tutorial
This part of the video explains what merge conflicts are and how to resolve them.
Watch the sections on merge conflicts and the practical demonstration of resolving a conflict.
When a conflict occurs, Git will mark the problematic area in the file like this:
<<<<<<< HEAD
# Code from your current branch (e.g., main)
=======
# Code from the branch you are trying to merge
>>>>>>> perf-experiment-numpy
To resolve it, you simply:
- Open the file in your editor.
- Delete the conflict markers (
<<<<<<<,=======,>>>>>>>). - Edit the code to be the correct final version (you might keep one version, the other, or a combination of both).
- Save the file.
- Use
git addto stage the resolved file. - Use
git committo complete the merge.
Don't be afraid of merge conflicts. They are a normal part of working with Git, especially in a team. The process of resolving them is straightforward once you understand what Git is showing you.
Conclusion
In this lesson, you've learned the fundamentals of Git, the version control system that underpins modern software development. You now have a repeatable, safe process for managing your performance experiments.
Key takeaways include:
- Git tracks your project's history through snapshots called commits.
- The basic workflow is to modify files, add them to the staging area, and commit them to the repository.
- Branches provide isolated environments for developing new features or running experiments without destabilizing your main codebase.
- Merging is used to integrate changes from one branch into another.
- Merge conflicts occur when Git can't automatically combine changes, but they can be resolved manually.
You have now completed the foundational module on Python and Linux. You can write simple programs, organize them into a project, inspect the system they run on, and track your code's evolution with Git.
In the next module, we will move from Python to C++. While Python is excellent for scripting and high-level logic, high-performance inference kernels are written in lower-level languages like C++ and CUDA to get maximum performance from the hardware. Our first lesson in the next module will guide you through writing, compiling, and running your first C++ program.