This script runs benchmarks written using Google Benchmark repeatedly,
and collects the results from JSON to render them nicely and provide
statistical information across the runs.
Because this runs the binaries repeatedly, this can help account for
run-to-run variations that are pervasive in many of Carbon's benchmarks,
such as ASLR and other process-specific differences.
It's most basic mode runs a benchmark multiple times and shows both
median and confidence intervals.
It also supports two comparison modes:
1) Regular expressions can be provided that describe collections of
related benchmarks where one is the "main" benchmark and the others
are comparable. For example, Carbon's data structure vs. data
structures from LLVM or Abseil. These will be rendered with the main
benchmark first, followed by a comparison relative to a "baseline" of
each comparable benchmark.
2) A baseline benchmark binary, and potentially different command line
flags, can be provided to run two benchmark binaries and compute
a comparison for each benchmark within them.
Across all of these, the script works to present the best text UI it can
in the console. I may have gotten a bit obsessed with rendering the
benchmark results in a way that is really pretty. There are lots of
fancy color coding and progress bars, etc., when run in in the terminal.
For the basic mode without any comparisons, the results look like:
```
Computing statistically significant deltas only wherethe P-value < 𝛂 of 0.05
Metric key:
BenchmarkName... <median> ± <% at 95th conf>
Benchmark ┃ CPU Time ┃ bytes_per_second
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━
BM_LatencyHash<RandValues<uint8_t>, CarbonHashBench>. │ 3.051 ns ± 2.721% │ 327.8 M ± 2.765%
BM_LatencyHash<RandValues<uint8_t>, AbseilHashBench>. │ 3.395 ns ± 4.377% │ 294.6 M ± 4.572%
BM_LatencyHash<RandValues<uint8_t>, LLVMHashBench>... │ 6.125 ns ± 2.662% │ 163.3 M ± 2.726%
BM_LatencyHash<RandValues<uint16_t>, CarbonHashBench> │ 3.105 ns ± 3.947% │ 644.1 M ± 4.109%
BM_LatencyHash<RandValues<uint16_t>, AbseilHashBench> │ 3.433 ns ± 4.308% │ 582.6 M ± 4.502%
BM_LatencyHash<RandValues<uint16_t>, LLVMHashBench>.. │ 6.127 ns ± 2.540% │ 326.5 M ± 2.587%
BM_LatencyHash<RandValues<uint32_t>, CarbonHashBench> │ 3.082 ns ± 2.846% │ 1.298 G ± 2.923%
BM_LatencyHash<RandValues<uint32_t>, AbseilHashBench> │ 3.401 ns ± 3.611% │ 1.176 G ± 3.739%
BM_LatencyHash<RandValues<uint32_t>, LLVMHashBench>.. │ 6.209 ns ± 4.064% │ 644.3 M ± 4.236%
BM_LatencyHash<RandValues<uint64_t>, CarbonHashBench> │ 3.122 ns ± 2.871% │ 2.563 G ± 2.956%
BM_LatencyHash<RandValues<uint64_t>, AbseilHashBench> │ 3.426 ns ± 2.811% │ 2.335 G ± 2.892%
BM_LatencyHash<RandValues<uint64_t>, LLVMHashBench>.. │ 6.497 ns ± 3.081% │ 1.231 G ± 3.179%
```
For the first comparison mode on one of Carbon's benchmarks, the results
look like:
```
Computing statistically significant deltas only wherethe P-value < 𝛂 of 0.05
Metric key:
BenchmarkName... <median> ± <% at 95th conf>
vs Comparable: 👍 <delta> p=<U-test P-value>
<median> ± <% at 95th conf>
Benchmark ┃ CPU Time ┃ bytes_per_second
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━
BM_LatencyHash<RandValues<uint8_t>, CarbonHashBench>. │ 3.037 ns ± 1.781% │ 329.2 M ± 1.813%
vs Abseil: │ 👍 -8.200% p=0.000183 │ 👍 8.933% p=0.000183
│ 3.309 ns ± 2.064% │ 302.2 M ± 2.022%
vs LLVM: │ 👍 -49.401% p=0.000183 │ 👍 97.632% p=0.000183
│ 6.003 ns ± 1.502% │ 166.6 M ± 1.480%
│ │
BM_LatencyHash<RandValues<uint16_t>, CarbonHashBench> │ 3.026 ns ± 1.816% │ 661 M ± 1.784%
vs Abseil: │ 👍 -8.599% p=0.000183 │ 👍 9.408% p=0.000183
│ 3.311 ns ± 1.873% │ 604.1 M ± 1.839%
vs LLVM: │ 👍 -49.829% p=0.000183 │ 👍 99.319% p=0.000183
│ 6.031 ns ± 2.806% │ 331.6 M ± 2.730%
│ │
BM_LatencyHash<RandValues<uint32_t>, CarbonHashBench> │ 3.017 ns ± 2.696% │ 1.326 G ± 2.625%
vs Abseil: │ 👍 -9.754% p=0.000183 │ 👍 10.808% p=0.000183
│ 3.344 ns ± 1.537% │ 1.196 G ± 1.514%
vs LLVM: │ 👍 -49.857% p=0.000183 │ 👍 99.427% p=0.000183
│ 6.018 ns ± 3.269% │ 664.7 M ± 3.167%
│ │
BM_LatencyHash<RandValues<uint64_t>, CarbonHashBench> │ 3.025 ns ± 3.395% │ 2.644 G ± 3.284%
vs Abseil: │ 👍 -9.812% p=0.000183 │ 👍 10.879% p=0.000183
│ 3.354 ns ± 2.640% │ 2.385 G ± 2.572%
vs LLVM: │ 👍 0.476x p=0.000183 │ 👍 2.101x p=0.000183
│ 6.357 ns ± 2.477% │ 1.258 G ± 2.418%
│ │
```
For the second mode, in this case comparing a baseline build with `-Oz`
vs an experiment with `-Os`, the results look like:
```
Computing statistically significant deltas only wherethe P-value < 𝛂 of 0.05
Metric key:
BenchmarkName... 👍 <delta> p=<U-test P-value>
baseline: <median> ± <% at 95th conf>
experiment: <median> ± <% at 95th conf>
Benchmark ┃ CPU Time ┃ bytes_per_second
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━
BM_LatencyHash<RandValues<std::pair<uint32_t, uint32_t>>, CarbonHashBench> │ 👍 -35.870% p=0.000557 │ 👍 55.930% p=0.000557
baseline: │ 5.704 ns ± 1.877% │ 1.403 G ± 1.911%
experiment: │ 3.658 ns ± 4.209% │ 2.187 G ± 4.039%
│ │
BM_LatencyHash<RandValues<std::pair<uint32_t, uint64_t>>, CarbonHashBench> │ 👍 -19.475% p=0.00119 │ 👍 24.186% p=0.00119
baseline: │ 4.974 ns ± 3.029% │ 3.217 G ± 3.124%
experiment: │ 4.005 ns ± 4.297% │ 3.995 G ± 4.120%
│ │
BM_LatencyHash<RandValues<std::pair<uint32_t, int*>>, CarbonHashBench>.... │ 👍 -11.740% p=0.00153 │ 👍 13.302% p=0.00153
baseline: │ 4.634 ns ± 3.433% │ 3.453 G ± 3.555%
experiment: │ 4.09 ns ± 2.999% │ 3.912 G ± 2.911%
│ │
```
The script itself uses a new tool for managing dependencies called `uv`:
https://docs.astral.sh/uv/ This tool allows for the script to contain an
inline set of dependencies that will be installed and cached for
subsequent runs. This seemed particularly important as dependencies like
SciPy and NumPy can be particularly difficult to manager or keep
installed in other ways, but are essential to this scripts statistical
analysis. So far, the `uv` system has been working remarkably well for
me and been a relatively pleasant experience on the whole.
I have included as much of the Python dependencies as have good type
information into the MyPy configuration to get good type checking in
pre-commit however.
Last but not least, this has been a pet project of mine for a quite a
while and so may be a bit rough around the edges as I added and tweaked
functionality based on specific benchmarks I was looking at. It feels
like its gotten useful enough to contribute somewhere, but totally open
to any refactoring or improvements needed. I tried to take a few passes
over it to organize and document the code before sending it, but I'm
sure there are still some things that could use improvement.
---------
Co-authored-by: Dana Jansens <danakj@orodu.net>
16 KiB
Contribution tools
The Carbon language project has a number of tools used to assist in preparing contributions.
Table of contents
Setup commands
These commands should help set up a development environment on your machine.
Debian or Ubuntu
# Update apt.
sudo apt update
# Check that the `clang` version is at least 19, our minimum version. That needs
# the number of the `:` in the output to be over 19. For example, `1:19.0-1`.
apt-cache show clang | grep 'Version:'
# Install tools.
sudo apt install \
clang \
gh \
libc++-dev \
libc++abi-dev \
lld \
lldb \
python3 \
pipx
# Install pre-commit.
pipx install pre-commit
# Set up git.
# If you don't already have a fork:
gh repo fork --clone carbon-language/carbon-lang
cd carbon-lang
pre-commit install
# Run tests.
./scripts/run_bazelisk.py test //...:all
Installing Bazelisk
Although the run_bazelisk script can make it easy to get started, if you're
frequently building Carbon, it can be a bit much to type. Consider either
aliasing bazel to the run_bazelisk.py script, or
downloading a bazelisk release, adding
it to your $PATH, and aliasing bazel to it.
Old clang versions
If the version of clang is earlier than 19, you may still have version 19
available. You can use the following install instead:
# Install explicitly versioned Clang tools.
sudo apt install \
clang-19 \
libc++-19-dev \
libc++abi-19-dev \
lld-19 \
lldb-19
# In your Carbon checkout, tell Bazel where to find `clang`. You can also
# export this path as the `CC` environment variable, or add it directly to
# your `PATH`.
echo "build --repo_env=CC=$(readlink -f $(which clang-19))" >> user.bazelrc
And if it's not available directly from the distribution, you can install Clang tools on Debian/Ubuntu from https://apt.llvm.org.
NOTE: Most LLVM 19+ installs should build Carbon. If you're having issues, see troubleshooting build issues.
macOS
# Install Homebrew.
/bin/bash -c "$(curl -fsSL \
https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# IMPORTANT: Make sure `brew` is added to the PATH!
# Install Homebrew tools.
brew install \
bazelisk \
gh \
llvm \
python@3.10 \
pre-commit
# IMPORTANT: Make sure `llvm` is added to the PATH! It's separate from `brew`.
# Set up git.
gh repo fork --clone carbon-language/carbon-lang
cd carbon-lang
pre-commit install
# Run tests. Note homebrew makes `bazel` an alias to `bazelisk`.
bazel test //...:all
NOTE: On macOS, you should end up adding rc file lines similar to:
# For `brew`, `gh`, and other tools: export PATH="${HOME}/.brew/bin:${PATH}" # For `llvm`: export PATH="$(brew --prefix llvm)/bin:${PATH}"
Tools
Main tools
These tools are essential for work on Carbon.
- Package managers
apt(for Debian or Ubuntu)- To upgrade versions of
aptpackages, it will be necessary to periodically runsudo apt update && sudo apt upgrade.
- To upgrade versions of
- Homebrew (for macOS)
- To upgrade versions of
brewpackages, it will be necessary to periodically runbrew upgrade.
- To upgrade versions of
- Python
- Carbon requires Python 3.9 or newer.
- To upgrade versions of pip-installed packages, it will be necessary
to periodically run
pipx list --outdated, thenpipx install -U <package>to upgrade desired packages. - When upgrading, version dependencies may mean packages should be outdated, and not be upgraded.
- Main tools
- Bazel
- Bazelisk: Downloads and runs the configured Bazel version.
- Clang and LLVM
- NOTE: Most LLVM 14+ installs should build Carbon. If you're having issues, see troubleshooting build issues.
- gh CLI: Helps with GitHub.
- pre-commit: Validates and cleans up git commits.
- Bazel
Running pre-commit
pre-commit is typically set up using
pre-commit install. When set up in this mode, it will check for issues when
git commit is run. A typical commit workflow looks like:
git committo try committing files. This automatically executespre-commit run, which may fail and leave files modified for cleanup.git add .to add the automatically modifications done bypre-commit.git commitagain.
You can also use pre-commit run to check pending changes without git commit,
or pre-commit run -a to run on all files in the repository.
NOTE: Some developers prefer to run
pre-commitongit pushinstead ofgit commitbecause they want to commit files as originally authored instead of with pre-commit modifications. To switch, runpre-commit uninstall && pre-commit install -t pre-push.
Optional tools
These tools aren't necessary to contribute to Carbon, but can be worth considering if they fit your workflow.
- GitHub Desktop: A UI for managing GitHub repositories.
rs-git-fsmonitorand Watchman: Helps makegitrun faster on large repositories.- WARNING: Bugs in
rs-git-fsmonitorand/or Watchman can result inpre-commitdeleting files. If you see files being deleted, disablers-git-fsmonitorwithgit config --unset core.fsmonitor.
- WARNING: Bugs in
- vim-prettier: A vim integration for Prettier, which we use for formatting.
- Visual Studio Code: A code editor.
- We provide recommended extensions to assist
Carbon development. Some settings changes must be made separately:
- Python › Formatting: Provider:
black
- Python › Formatting: Provider:
- WARNING: Visual Studio Code modifies the
PATHenvironment variable, particularly in the terminals it creates. ThePATHdifference can causebazelto detect different startup options, discarding its build cache. As a consequence, it's recommended to use either normal terminals or Visual Studio Code to runbazel, not both in combination. Visual Studio Code can still be used for other purposes, such as editing files, without interfering withbazel.
- We provide recommended extensions to assist
Carbon development. Some settings changes must be made separately:
- clangd: An LSP server implementation
for C/C++.
- To ensure that
clangdreports accurate diagnostics. It needs a generated file calledcompile_commands.json. This can be generated by invoking the command below:./scripts/create_compdb.py- NOTE: This assumes you have
python3 installed on your system.
- NOTE: This assumes you have
- To ensure that
uv: A fast Python package manager.- Notably,
uvsupports automatic management of even complex Python dependencies for scripts: https://docs.astral.sh/uv/guides/scripts/ - Installation: https://docs.astral.sh/uv/getting-started/installation/
- Notably,
Using LLDB with VS Code
The required setup for LLDB is:
- In the
.vscodesubdirectory, symlinklldb_launch.jsontolaunch.json. For example:ln -s lldb_launch.json .vscode/launch.json - Install the
llvm-vs-code-extensions.lldb-dapextension. - In VS Code settings, it may be necessary to set
lldb-dap.executable-pathto the path oflldb-dap.
A typical debug session looks like:
bazel build -c dbg //toolchain/testing:file_test- Open a
.carbontestdata file to debug. This must be the active file in VS Code. - Go to the "Run and debug" panel in VS Code.
- Select and run the
file_test (lldb)configuration.
Using GDB with VS Code
The required setup for GDB is:
- In the
.vscodesubdirectory, symlinkgdb_launch.jsontolaunch.json. For example:ln -s gdb_launch.json .vscode/launch.json - Install the
coolchyni.beyond-debugextension.
A typical debug session looks like:
bazel build -c dbg --features=-lldb_flags --features=gdb_flags //toolchain/testing:file_test- Open a
.carbontestdata file to debug. This must be the active file in VS Code. - Go to the "Run and debug" panel in VS Code.
- Select and run the
file_test (gdb)configuration.
Manually building Clang and LLVM (not recommended)
We primarily test against apt.llvm.org and Homebrew installations. However, you can build and install LLVM yourself if you feel more comfortable with it. The essential CMake options to pass in order for this to work reliably include:
-DLLVM_ENABLE_PROJECTS=clang;clang-tools-extra;lld;lldb
-DLLVM_ENABLE_RUNTIMES=compiler-rt;libcxx;libcxxabi;libunwind
-DRUNTIMES_CMAKE_ARGS=-DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF;-DCMAKE_POSITION_INDEPENDENT_CODE=ON;-DLIBCXX_ENABLE_STATIC_ABI_LIBRARY=ON;-DLIBCXX_STATICALLY_LINK_ABI_IN_SHARED_LIBRARY=OFF;-DLIBCXX_STATICALLY_LINK_ABI_IN_STATIC_LIBRARY=ON;-DLIBCXX_USE_COMPILER_RT=ON;-DLIBCXXABI_USE_COMPILER_RT=ON;-DLIBCXXABI_USE_LLVM_UNWINDER=ON
-DLLDB_ENABLE_PYTHON=ON
Troubleshooting build issues
bazel clean
Changes to packages installed on your system may not be noticed by bazel. This
includes things such as changing LLVM versions, or installing libc++. Running
bazel clean should force cached state to be rebuilt.
Old LLVM versions
Many build issues result from the particular options clang and llvm have
been built with, particularly when it comes to system-installed versions. If you
run clang --version, you should see at least version 19. If you see an older
version, please update, or use the special clang-19 instructions above.
System installs of macOS typically won't work, for example being an old LLVM version or missing llvm-ar; setup commands includes LLVM from Homebrew for this reason.
Run bazel clean when changing the installed LLVM version.
Asking for help
If you're having trouble resolving issues, please ask on #build-help, providing the output of the following diagnostic commands:
echo $CC
which clang
which clang-19
clang --version
grep llvm_bindir $(bazel info workspace)/bazel-execroot/external/+clang_toolchain_extension+bazel_cc_toolchain/clang_detected_variables.bzl
# If on macOS:
brew --prefix llvm
These commands will help diagnose potential build issues by showing which tooling is in use.
Troubleshooting debug issues
Pass -c dbg to bazel build in order to compile with debugging enabled. For
example:
bazel build -c dbg //toolchain
Then debugging works with LLDB:
lldb bazel-bin/toolchain/carbon
Any installed version of LLDB at least as recent as the installed Clang used for building should work.
Using LLDB from the command line
We include launch commands for running lldb in VSCode in
.vscode/lldb_launch.json. But it's also possible
to run lldb from the command line.
When running the debugger, include the --local-lldbinit argument to use our
preset configuration options. This requires running from the repository root.
To debug a single file_test, use the following command, pointing it to an
actual carbon test file.
bazel build -c dbg //toolchain/testing:file_test && \
lldb --local-lldbinit bazel-bin/toolchain/testing/file_test -- \
--dump_output --file_tests /path/to/some/test.carbon
Debugging with GDB instead of LLDB
If you prefer using GDB, you may want to pass some extra flags to the build:
bazel build -c dbg --features=-lldb_flags --features=gdb_flags //toolchain
Or you can add them to your user.bazelrc, they are designed to be safe to pass
at all times and only have effect when building with debug information:
echo "build --features=-lldb_flags --features=gdb_flags" >> user.bazelrc
Note that on Linux we use Split DWARF and DWARF v5 debug symbols, which means that GDB version 10.1 or newer is required. If you see an error like this:
Dwarf Error: DW_FORM_strx1 found in non-DWO CU
It means that the version of GDB used is too old, and does not support the DWARF v5 format.
Debugging other build modes
If you have an issue that only reproduces with another build mode, you can still
enable debug information in that mode by passing --feature=debug_info_flags to
Bazel.
Debugging on MacOS
Bazel sandboxes builds, which on MacOS makes it hard for the debugger to locate
symbols on linked binaries when debugging. See this
Bazel issue
for more information. To workaround, provide the --spawn_strategy=local option
to Bazel for the debug build, like:
bazel build --spawn_strategy=local -c dbg //toolchain
You should then be able to debug with lldb.
If this build command doesn't seem to produce a debuggable binary you might need
to both clear the build disk cache and clean the build. Running
scripts/clean_disk_cache.sh may not be enough, you might try deleting all the
files within the disk cache, typically located at
~/.cache/carbon-lang-build-cache. Deleting the disk cache, followed by a
bazel clean should allow your next rebuild, with the recommended options, to
supply the symbols for debugging.
For debugging on MacOS using VSCode, some people have had success using the
CodeLLDB extension. In order for LLDB to connect the project source files with
the symbols you will need to add a "sourceMap": { ".": "${workspaceRoot}" }
line to the CodeLLDB launch.json configuration, for example:
{
"version": "0.2.0",
"configurations": [
{
"name": "explorer",
"type": "lldb",
"request": "launch",
"program": "${workspaceRoot}/bazel-bin/explorer/explorer",
"args": [],
"cwd": "${workspaceRoot}",
"sourceMap": {
".": "${workspaceRoot}"
}
}
]
}