Skip to content

Add gsl::span vs std::span performance benchmark CI (fixes #1167)#1252

Open
Gafoor2005 wants to merge 14 commits into
microsoft:mainfrom
Gafoor2005:main
Open

Add gsl::span vs std::span performance benchmark CI (fixes #1167)#1252
Gafoor2005 wants to merge 14 commits into
microsoft:mainfrom
Gafoor2005:main

Conversation

@Gafoor2005
Copy link
Copy Markdown

Summary

Adds a continuous benchmark suite that tracks performance parity between
gsl::span and std::span on every PR, across all supported compilers
and platforms.

Closes #1167.


What this adds

  • benchmark/span_bench.cpp — benchmarks from @galenelias comparing
    gsl::span and std::span across five workloads (is_sorted, ranges,
    custom iterator loop, min_element algorithm, range-for min)
  • benchmark/CMakeLists.txt — self-contained build; fetches
    google-benchmark v1.9.0 and googletest via FetchContent; links against
    the local repo's GSL (not a pinned tag) so every PR tests its own
    changes
  • benchmark/check_regression.py — parses benchmark JSON output, pairs
    StdSpan/GslSpan variants by name, computes gsl_ns / std_ns ratio,
    writes a Markdown table, exits 1 on regression
  • .github/workflows/span_benchmark.yml — runs on every PR across 13
    configs (GCC 13/14, Clang 17/18, MSVC 2022, clang-cl, Apple Clang ×
    C++20/23); posts or updates a single PR comment with results from all
    configs

Approach

The comparison is in-run ratio (gsl_ns / std_ns) rather than
comparing against a stored baseline. Both span variants run in the same
process on the same machine at the same moment, so GitHub-hosted runner
noise (~10–15%) cancels out. A ratio above 1.15 (15% slower than
std::span) fails CI.

This sidesteps the "Status: Blocked" concern about noisy shared runners —
no self-hosted runner or external service needed.

Each benchmark runs 10 repetitions; the script uses the mean and
reports stddev so reviewers can see measurement stability.


CI matrix

OS Compiler Standard
ubuntu-latest GCC 13 C++20
ubuntu-latest GCC 14 C++20, C++23
ubuntu-latest Clang 17 C++20
ubuntu-latest Clang 18 C++20, C++23
windows-latest MSVC 2022 C++20, C++23
windows-latest clang-cl C++20, C++23
macos-14 Apple Clang C++20, C++23

Example PR comment

The workflow posts (and updates) a single comment per PR that looks like:

GCC-14-cpp20

Benchmark std mean gsl mean ratio status
IsSorted 124 ns 125 ns 1.01× ✅ 1.01×
IsSortedRanges 88 ns 89 ns 1.01× ✅ 1.01×
MinElementAlgorithm 95 ns 96 ns 1.01× ✅ 1.01×

Notes

  • -fbounds-safety config is stubbed out in the matrix with a comment
    explaining it's not yet stable in mainline Clang releases — easy to
    uncomment when it lands
  • FetchContent deps are cached on CMakeLists.txt hash so CI doesn't
    re-clone on every run
  • The comment job uses peter-evans/find-comment +
    create-or-update-comment so repeated pushes to the same PR update
    the existing comment rather than flooding the thread

Gafoor2005 added 14 commits May 19, 2026 12:41
Add benchmark suite comparing gsl::span vs std::span performance across
is_sorted and min_element operations. Includes GitHub Actions workflow
for automated testing on GCC 14, Clang 18, and MSVC with results uploaded
as artifacts for performance regression analysis.
Ensure both text and JSON benchmark outputs are properly populated
in workflow artifacts by running benchmarks separately instead of
using pipe redirection, which was leaving the text file empty.
Test PR for PR-based benchmark
@Gafoor2005
Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

Copy link
Copy Markdown
Member

@carsonRadtke carsonRadtke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this put together! It'll be great to have the infrastructure necessary to compare GSL types to standard library types. I have briefly reviewed many of the changes, please address my comments and I will perform a more in-depth review once comments have been resolved. I will also assign Copilot as a reviewer, so you get some more feedback; please address those comments too.

This currently is a proof-of-concept of a really cool feature that will help us develop GSL, but I want to make sure the PR is clean and well-understood before running any workflows and merging your changes.

Also, using AI as a tool to write code is great. Please make sure you understand the code that you are contributing. I want to hold all contributions to a high standard and right now this PR needs some work.

@@ -0,0 +1,413 @@
# .github/workflows/span_benchmark.yml
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: probably don't need the filename here.

Comment on lines +3 to +20
# Benchmarks gsl::span vs std::span on every PR across all supported compilers.
# Strategy: in-run ratio (gsl_ns / std_ns) — noise-resistant because both
# spans run in the same process, same machine, same moment.
#
# All dependencies (google-benchmark v1.9.0, GSL v4.1.0) are pulled via
# FetchContent inside the repo's CMakeLists.txt — no separate install steps.
# The build/_deps directory is cached, keyed on CMakeLists.txt hash, so
# repeated runs don't re-clone anything.
#
# Three separate job groups (one per OS) because `runs-on` can't share a
# matrix with OS-specific shell/path differences cleanly:
#
# benchmark-linux → ubuntu-latest → GCC-13, GCC-14, Clang-17, Clang-18
# benchmark-windows → windows-latest → MSVC 2022, clang-cl
# benchmark-macos → macos-14 → Apple Clang (Xcode latest)
#
# A final `comment` job waits for all three groups, collects every JSON
# artifact, runs check_regression.py, and posts (or updates) one PR comment.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: a brief description is okay, but this seems excessive.

- label: GCC-13-cpp20
cxx: g++-13
cppstd: 20
extra_flags: ""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like extra_flags is always empty. Can we remove it?

cppstd: 23
extra_flags: ""


Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a lot of empty space. Can we clean this up?

strategy:
fail-fast: false
matrix:
include:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with matrix.include, but can't we just do something like:

matrix:
  cxx: [g++-12, g++-13, g++-14, clang++-16, clang++-17, clang++-18]
  cppstd: [20, 23]

Seems easier that enumerating every variation.

Comment on lines +88 to +96
# ── Clang + -fbounds-safety (core motivation of issue #1167) ───
# Safe Buffers enforcement is what triggered this whole tracking effort.
# - label: Clang-18-cpp20-bounds-safety
# cxx: clang++-18
# cppstd: 20
# extra_flags: "-fbounds-safety"
# ----------------------------------------------------------------------------
# ⚠️ Note: As of early 2026, the feature is not yet fully available or stable in mainline, official releases (like Clang 20, 21, or 22).
# ----------------------------------------------------------------------------
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think -fbounds-safety is correct here.

Comment on lines +105 to +110
- name: Install GCC 14
if: startsWith(matrix.cxx, 'g++-14')
run: |
sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
sudo apt-get update -qq
sudo apt-get install -y g++-14
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't install gcc in the regular compilers workflow. Why do we need to do it here?

Comment thread benchmark/README.md
@@ -0,0 +1,144 @@
# `gsl::span` vs `std::span` Benchmark
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can delete this file, I think.

Comment thread benchmark/CMakeLists.txt
@@ -0,0 +1,87 @@
# benchmark/CMakeLists.txt
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some general notes:

  • I'd like this to be very similar to GSL_TEST; can we have a no -DGSL_BENCHMARK when invoking the root CMakeLists.txt?
  • I also would like to rely on the same GSL_CXX_STANDARD?
  • We already download googletest for the test suite, is it necessary to do it again here? They are also different versions. Why?
  • Google benchmark latest tag is 1.9.5, can we update to that?
  • There is a performance cost to -fno-omit-frame-pointer, can we not enable the omission?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a CI-run benchmark suite to continuously track performance parity between gsl::span and std::span, generating a per-PR Markdown report and failing CI when gsl::span exceeds a configured slowdown threshold.

Changes:

  • Adds a Google Benchmark-based executable comparing gsl::span vs std::span across several workloads.
  • Adds a Python regression checker that parses benchmark JSON, computes gsl/std ratios, and emits a PR-comment-ready Markdown report.
  • Adds a GitHub Actions workflow that runs the benchmark across a multi-OS/compiler/standard matrix, uploads results, and posts/updates a single PR comment.
Show a summary per file
File Description
benchmark/span_bench.cpp Adds the benchmark program comparing gsl::span and std::span workloads.
benchmark/README.md Documents purpose, local usage, CI matrix, and how regression detection works.
benchmark/CMakeLists.txt Self-contained CMake build that fetches benchmark dependencies and builds against the local GSL checkout.
benchmark/check_regression.py Parses JSON results, pairs Std/GSL benchmarks, computes ratios, and generates Markdown + CI status.
.github/workflows/span_benchmark.yml CI matrix runner that builds/runs benchmarks, aggregates artifacts, and posts/updates a PR comment.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 10

Comment thread benchmark/span_bench.cpp
Comment on lines +5 to +7
#include "gsl/span"
#include <benchmark/benchmark.h>
#include <numeric>
Comment on lines +202 to +217
config_regression = False
for p in pairs:
ratio, status = ratio_and_status(p["gsl_mean"], p["std_mean"], threshold)
if "regression" in status:
had_regression = True
config_regression = True

lines.append(
f"| `{p['short']}` "
f"| {fmt(p['std_mean'])} "
f"| {fmt_stddev(p['std_stddev'], p['std_mean'])} "
f"| {fmt(p['gsl_mean'])} "
f"| {fmt_stddev(p['gsl_stddev'], p['gsl_mean'])} "
f"| {ratio:.2f}× "
f"| {status} |"
)
Comment thread benchmark/CMakeLists.txt
Comment on lines +4 to +8
# All dependencies are fetched automatically via FetchContent:
# - google/benchmark v1.9.0
# - google/googletest release-1.12.1 (benchmark's internal dep)
# - microsoft/GSL v4.1.0
#
Comment on lines +7 to +10
# All dependencies (google-benchmark v1.9.0, GSL v4.1.0) are pulled via
# FetchContent inside the repo's CMakeLists.txt — no separate install steps.
# The build/_deps directory is cached, keyed on CMakeLists.txt hash, so
# repeated runs don't re-clone anything.
uses: actions/cache@v4
with:
path: build/_deps
key: fetchcontent-linux-${{ matrix.cxx }}-${{ hashFiles('CMakeLists.txt') }}
uses: actions/cache@v4
with:
path: build/_deps
key: fetchcontent-windows-${{ matrix.toolset }}-${{ hashFiles('CMakeLists.txt') }}
uses: actions/cache@v4
with:
path: build/_deps
key: fetchcontent-macos-${{ matrix.cppstd }}-${{ hashFiles('CMakeLists.txt') }}
python3 benchmark/check_regression.py \
--threshold 0.15 \
--output comment.md \
--workflow-url "https://github.com/${{ github.repository }}/blob/tree/main/.github/workflows/span_benchmark.yml" \
Comment on lines +389 to +395
- name: Find existing bot comment
uses: peter-evans/find-comment@v3
id: find_comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: github-actions[bot]
body-includes: "<!-- span-bench-report -->"
Comment on lines +399 to +405
- name: Create or update PR comment
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.find_comment.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body-path: comment.md
edit-mode: replace
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add performance tests to PR actions

3 participants