Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Performance measurements should use optimized builds by default.
build -c opt

# Keep CI logs readable and include failed test output.
common:ci --announce_rc
common:ci --color=yes
common:ci --curses=no
test:ci --test_output=errors

# Instrument test binaries and their C/C++ dependencies for memory errors.
build:asan -c dbg
build:asan --copt=-fsanitize=address
build:asan --copt=-fno-omit-frame-pointer
build:asan --linkopt=-fsanitize=address
test:asan --test_env=ASAN_OPTIONS=detect_leaks=1:halt_on_error=1

# Instrument test binaries and their C/C++ dependencies for undefined behavior.
build:ubsan -c dbg
build:ubsan --copt=-fsanitize=undefined
build:ubsan --copt=-fno-omit-frame-pointer
build:ubsan --linkopt=-fsanitize=undefined
test:ubsan --test_env=UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1
File renamed without changes.
13 changes: 13 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
# Apply the clang-tidy checks that enforce the Google C++ Style Guide.
Checks: 'google-*'

# Format any clang-tidy fix-its with the corresponding clang-format style.
FormatStyle: Google

# Analyze project headers without reporting diagnostics from external modules.
HeaderFilterRegex: '(^|.*/)(lib|benchmarks)/.*'

# Treat all configured diagnostics as errors by default.
WarningsAsErrors: '*'
...
2 changes: 2 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CompileFlags:
CompilationDatabase: .
80 changes: 64 additions & 16 deletions .github/workflows/bazel.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Bazel
name: CI

on:
pull_request:
Expand All @@ -7,35 +7,83 @@ on:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
lint:
name: Lint
runs-on: ubuntu-24.04
timeout-minutes: 15

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0
with:
bazelisk-cache: true
disk-cache: ${{ github.workflow }}-${{ github.job }}
repository-cache: true
cache-save: ${{ github.event_name == 'push' }}

- name: Install clang-tidy
run: sudo apt-get update && sudo apt-get install --yes clang-tidy

- name: Lint C++ sources
run: sh tools/run-clang-tidy.sh

defaults:
run:
# MODULE.bazel and .bazelversion live in src/
working-directory: src
build-and-test:
name: Build and test
runs-on: ubuntu-24.04
timeout-minutes: 20

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0
with:
bazelisk-cache: true
disk-cache: ${{ github.workflow }}
disk-cache: ${{ github.workflow }}-${{ github.job }}
repository-cache: true
# Pull requests only restore caches, pushes to main refresh them
cache-save: ${{ github.event_name == 'push' }}
bazelrc: |
common --announce_rc
common --color=yes

- name: Bazel version
run: bazel version
- name: Build
run: bazel build --config=ci //...

- name: Test
run: bazel test --config=ci //...

sanitizers:
name: ${{ matrix.name }}
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- name: AddressSanitizer
config: asan
- name: UndefinedBehaviorSanitizer
config: ubsan

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0
with:
bazelisk-cache: true
disk-cache: ${{ github.workflow }}-${{ github.job }}-${{ matrix.config }}
repository-cache: true
cache-save: ${{ github.event_name == 'push' }}

- name: Build
run: bazel build //...
run: bazel build --config=ci --config=${{ matrix.config }} //...

- name: Test
run: bazel test //... --test_output=errors
run: bazel test --config=ci --config=${{ matrix.config }} //...
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
*~
bazel-*
*MODULE.bazel.lock
compile_commands.json
/.ijwb/
/.aswb/
/.clwb/
Expand Down
19 changes: 19 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# C/C++ rules for the reusable frame-processing libraries and executables.
bazel_dep(name = "rules_cc", version = "0.2.26")

# Test-only dependency; production libraries in lib/ do not depend on it.
bazel_dep(name = "googletest", version = "1.18.0.bcr.1", dev_dependency = True)

# Benchmark executables depend on this package, never libraries in lib/.
bazel_dep(name = "google_benchmark", version = "1.9.5")

# Generates compile_commands.json for clangd and clang-tidy. This is a
# development-only tool and is not part of production or benchmark targets.
# The pinned revision is the upstream Bazel 9 compatibility patch (PR #281),
# pending its merge into the canonical repository.
bazel_dep(name = "hedron_compile_commands", dev_dependency = True)
git_override(
module_name = "hedron_compile_commands",
remote = "https://github.com/ultimateanu/bazel-compile-commands-extractor.git",
commit = "2dc498f24587eccc2752f8a892da004d9baa8318",
)
96 changes: 79 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,96 @@
# memcopy-bench
DevTools 2026 Research Project. Violet Team

# Setup
A C/C++ research framework for evaluating data movement and frame-processing
techniques in Telecom L2 scenarios. The initial focus is PDCP and GTP-U frame
processing on x86, while reusable library code avoids unnecessary
architecture-specific assumptions.

We use [Bazel](https://bazel.build/install) to build, run and test our project.
The Bazel version is pinned in `src/.bazelversion`, use
[Bazelisk](https://github.com/bazelbuild/bazelisk) to get the right one.
## Layout

- `lib/` — reusable frame, memory, transport, and scenario implementations.
Libraries do not depend on benchmarking or reporting infrastructure.
`lib/test/` is reserved for shared test-only support; component unit tests
remain colocated with their implementations.
- `benchmarks/` — Google Benchmark executables. `memory/` holds operation
microbenchmarks; `scenarios/` will hold complete workflow benchmarks.
- `tests/integration/` — reserved for cross-component integration tests when
they are needed. Unit tests are colocated with the library code they verify.
- `tools/` — experiment automation and analysis utilities.
- `results/` — raw benchmark output and experiment metadata (kept separate
from analysis).
- `reports/` — research findings and comparative analysis.

## Prerequisites

Install [Bazelisk](https://github.com/bazelbuild/bazelisk). It selects the
Bazel version pinned in `.bazelversion`. Commands are run from the repository
root. Optimized mode is enabled by default in `.bazelrc`.

## C++ editor tooling

Generate Bazel-aware compile commands for `clangd` and `clang-tidy` after
changing C++ sources, targets, dependencies, or compile settings:

```bash
bazel run @hedron_compile_commands//:refresh_all
```

The generated root-level `compile_commands.json` is intentionally ignored by
Git. Restart the editor language server after refreshing it if it does not
reload the database automatically.

## Build, lint, and test

All commands below are run from `src/`:
```bash
cd src/
bazel build //...
bazel test //...
```

Optimized build mode (`-c opt`) is enabled by default in `src/.bazelrc`.
Run the same C++ lint check used by CI (requires `clang-tidy` on `PATH`):

**Build:**
```bash
bazel build //lib:memcpy_bench # build benchmark lib only
bazel build //main:main # build main binary and library
sh tools/run-clang-tidy.sh
```

**Test:**
Run tests with the supported sanitizers:

```bash
bazel test --config=asan //... --test_output=errors
bazel test --config=ubsan //... --test_output=errors
```

For example, the initial reusable algorithm and its colocated test are:

```bash
bazel test //test:memcpy_bench_test
bazel build //lib/memory:memcpy
bazel test //lib/memory:memcpy_test
```

**Run benchmarks:**
## Run a benchmark

```bash
bazel run //main:main
bazel run //benchmarks/memory:memcpy_benchmark

# Google Benchmark flags are passed after `--`
bazel run //main:main -- --benchmark_filter='BM_Memcpy/4096' --benchmark_format=json
# Google Benchmark options follow `--`.
bazel run //benchmarks/memory:memcpy_benchmark -- \
--benchmark_filter='BM_Memcpy/4096' \
--benchmark_format=json \
--benchmark_out=results/memcpy-4096.json
```

Store the matching experiment metadata (scenario, algorithm, input size,
compiler configuration, CPU information, and source revision) alongside raw
output in `results/`. Use `reports/` for the derived analysis.

## Development workflow

1. Add an algorithm or explicit frame-processing scenario under `lib/`.
2. Add colocated functional tests and run `bazel test`.
3. Add or extend the corresponding executable under `benchmarks/`.
4. Run controlled experiments and record raw measurements plus metadata in
`results/`.
5. Analyze results in `reports/`.

A scenario describes *what* happens to a frame; algorithms describe *how* an
individual operation is performed. Keep those concerns separate so algorithms
can be compared across independent research cases.
3 changes: 3 additions & 0 deletions benchmarks/memory/.clang-tidy
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Google Benchmark range-for loops require an intentionally unused binding.
InheritParentConfig: true
Checks: '-clang-analyzer-deadcode.DeadStores'
10 changes: 10 additions & 0 deletions benchmarks/memory/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")

cc_binary(
name = "memcpy_benchmark",
srcs = ["memcpy_benchmark.cpp"],
deps = [
"//lib/memory:memcpy",
"@google_benchmark//:benchmark_main",
],
)
41 changes: 41 additions & 0 deletions benchmarks/memory/memcpy_benchmark.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "lib/memory/memcpy.h"

#include <cstddef>
#include <cstdint>
#include <vector>

#include <benchmark/benchmark.h>

namespace {

std::vector<std::byte> MakeInput(std::size_t size) {
std::vector<std::byte> input(size);
std::uint32_t state = 0xC0FFEEu;
for (std::byte& value : input) {
state = state * 1664525u + 1013904223u;
value = static_cast<std::byte>(state >> 24);
}
return input;
}

void BM_Memcpy(benchmark::State& state) {
const auto size = static_cast<std::size_t>(state.range(0));
const std::vector<std::byte> source = MakeInput(size);
std::vector<std::byte> destination(size);

for (auto _ : state) {
memcopy_bench::memory::CopyWithMemcpy(
destination.data(), source.data(), size);
benchmark::DoNotOptimize(destination.data());
benchmark::ClobberMemory();
}

state.SetBytesProcessed(static_cast<std::int64_t>(state.iterations()) *
static_cast<std::int64_t>(size));
}

BENCHMARK(BM_Memcpy)->RangeMultiplier(4)->Range(64, 64 << 20);

} // namespace

BENCHMARK_MAIN();
18 changes: 18 additions & 0 deletions lib/memory/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")

cc_library(
name = "memcpy",
srcs = ["memcpy.cpp"],
hdrs = ["memcpy.h"],
visibility = ["//visibility:public"],
)

cc_test(
name = "memcpy_test",
srcs = ["memcpy_test.cpp"],
deps = [
":memcpy",
"@googletest//:gtest_main",
],
)
11 changes: 11 additions & 0 deletions lib/memory/memcpy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "lib/memory/memcpy.h"

#include <cstring>

namespace memcopy_bench::memory {

void CopyWithMemcpy(void* destination, const void* source, std::size_t size) {
std::memcpy(destination, source, size);
}

} // namespace memcopy_bench::memory
10 changes: 10 additions & 0 deletions lib/memory/memcpy.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#pragma once

#include <cstddef>

namespace memcopy_bench::memory {

// Copies exactly size bytes from source to destination using std::memcpy.
void CopyWithMemcpy(void* destination, const void* source, std::size_t size);

} // namespace memcopy_bench::memory
Loading