Skip to content

Use AVX-512 streaming instructions to increase write throughput - #35

Closed
olivereanderson wants to merge 1 commit into
cobaltcore-dev:mainfrom
olivereanderson:non-temporal-512-bit-stores
Closed

olivereanderson wants to merge 1 commit into
cobaltcore-dev:mainfrom
olivereanderson:non-temporal-512-bit-stores

Conversation

@olivereanderson

@olivereanderson olivereanderson commented Apr 29, 2026 •

Copy link
Copy Markdown
Contributor

Non-temporal stores are known to help in the case of bulk write workloads. This PR thus replaces the write_page loops with a function issuing VMOVNTDQ instructions in a loop (storing 4 zmm registers to memory with a non-temporal hint per iteration) whenever avx512f is available.

Benchmarks

TL;DR: write_mibps increases by a factor between roughly 1.7 and 4x depending on the parameters, but read_mibps suffers by roughly 15-30% on Intel Sapphire Rapids + Granite Rapids and is roughly halved in the 50% read case on AMD Turin (Zen 5).

All benchmarks are performed in a Cloud hypervisor guest with direct Kernel boot.

RW_RATIO = 100

Intel Sapphire Rapids

Before this change:

bash/root# memtouch --num_threads 8 --thread_mem 512 --rw_ratio 100 --stat_ival 100 --stat_file /dev/console
...
2026-04-28T12:56:41.602+0000 read_mibps:0.00 write_mibps:15288.66

With this change:

 memtouch --num_threads 8 --thread_mem 512 --rw_ratio 100 --stat_ival 100 --stat_file /dev/console
...
2026-04-29T09:35:45.080+0000 read_mibps:0.00 write_mibps:25405.31

Intel Granite Rapids

Before this change:

memtouch --num_threads 8 --thread_mem 512 --rw_ratio 100 --stat_ival 100 --stat_file /dev/console
...
2026-04-29T09:46:35.919+0000 read_mibps:0.00 write_mibps:48910.51

After this change:

memtouch --num_threads 8 --thread_mem 512 --rw_ratio 100 --stat_ival 100 --stat_file /dev/console
...
2026-04-29T09:53:06.093+0000 read_mibps:0.00 write_mibps:130104.02

AMD Turin (Zen 5)

Before this change:

memtouch --num_threads 8 --thread_mem 512 --rw_ratio 100 --stat_ival 100 --stat_file /dev/console
... 
2026-04-29T10:31:51.814+0000 read_mibps:0.00 write_mibps:26124.03

After this change:

memtouch --num_threads 8 --thread_mem 512 --rw_ratio 100 --stat_ival 100 --stat_file /dev/console
...
2026-04-29T10:37:53.614+0000 read_mibps:0.00 write_mibps:43241.38

RW_RATIO=50

Sapphire Rapids

Before this change:

bash/root# memtouch --num_threads 8 --thread_mem 512 --rw_ratio 50 --stat_ival 100 --stat_file /dev/console
...
2026-04-28T12:57:53.182+0000 read_mibps:26912.37 write_mibps:14626.11
...
2026-04-28T12:57:55.722+0000 read_mibps:23788.17 write_mibps:13883.67
...
2026-04-28T12:58:00.395+0000 read_mibps:23918.18 write_mibps:13850.72

After this change:

bash/root# memtouch --num_threads 8 --thread_mem 512 --rw_ratio 50 --stat_ival 100 --stat_file /dev/console
...
2026-04-29T09:36:45.700+0000 read_mibps:19492.00 write_mibps:38896.96
...
2026-04-29T09:36:46.007+0000 read_mibps:18174.73 write_mibps:50314.11
...
2026-04-29T09:36:47.128+0000 read_mibps:16523.04 write_mibps:62170.18
...
2026-04-29T09:36:51.501+0000 read_mibps:17868.51 write_mibps:37468.30

We now have much less consistent numbers, but writes are always much higher and reads always a bit lower.

Granite Rapids

Before this change:

memtouch --num_threads 8 --thread_mem 512 --rw_ratio 50 --stat_ival 100 --stat_file /dev/console
...
2026-04-29T09:47:30.464+0000 read_mibps:89452.20 write_mibps:54063.75
...
2026-04-29T09:47:31.273+0000 read_mibps:89169.32 write_mibps:53755.64
...
2026-04-29T09:47:35.014+0000 read_mibps:93118.45 write_mibps:53443.48

After this change:

memtouch --num_threads 8 --thread_mem 512 --rw_ratio 50 --stat_ival 100 --stat_file /dev/console
...
2026-04-29T09:58:05.398+0000 read_mibps:79570.14 write_mibps:203479.06
...
2026-04-29T09:58:07.522+0000 read_mibps:77014.87 write_mibps:205501.08
...
...2026-04-29T09:58:11.973+0000 read_mibps:77247.63 write_mibps:207418.56

We still get roughly 4x more write_mibps and the read_mibps is penalized a bit less than on the Sapphire Rapids.

AMD Turin (Zen 5)

Before this change:

memtouch --num_threads 8 --thread_mem 512 --rw_ratio 50 --stat_ival 100 --stat_file /dev/console
...
2026-04-29T10:32:55.493+0000 read_mibps:50831.60 write_mibps:28478.09
...
2026-04-29T10:32:57.234+0000 read_mibps:52024.53 write_mibps:29265.94
...
2026-04-29T10:33:00.001+0000 read_mibps:49901.25 write_mibps:28015.01

After this change:

memtouch --num_threads 8 --thread_mem 512 --rw_ratio 50 --stat_ival 100 --stat_file /dev/console
...
2026-04-29T10:38:50.926+0000 read_mibps:28641.04 write_mibps:96322.76
...
2026-04-29T10:38:52.863+0000 read_mibps:27180.42 write_mibps:112658.40
...
2026-04-29T10:38:55.923+0000 read_mibps:26728.97 write_mibps:118604.88

We here see that write_mibps is increased by around 4x, but read_mibps is almost halved though.

Implementation details

This is my first time writing C++ and using Meson. There is thus probably a lot of room for improvements!

C++ Code

I branch on a pre-computed bool in the write_pages function and execute the fast path when avx512f is available on x86_64 targets. Normally one would perhaps rather use dynamic dispatch through a function pointer here, but I am not sure how one would write that in C++. In this particular case I also expect the performance penalty of including effectively dead code at the beginning of the function call to be rather negligible in comparison to the time spent in the hot write loop.

Meson

In order to compile the avx512 code one needs to supply the avx512f flag to the compiler, but we don't want this to apply to all code because we can then get UB on (x86_64) machines that do not have these features. I thus had to create more static_libraries with separate compiler flags. I hope this is correct, but I am not sure how idiomatic or nice my approach is here at all.

One would typically do things a bit differently in a Rust project.

Summary by CodeRabbit

  • New Features

    • AVX‑512 streaming write path for faster bulk memory writes on supported x86_64 systems
    • Runtime detection with a new command-line option to disable AVX‑512 and enforced memory alignment when active
  • Chores

    • Centralized common constants into a shared header
    • Build system updated for architecture-specific compilation and link-time object optimization

@coderabbitai

coderabbitai Bot commented Apr 29, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77d21d95-674d-4149-b5c3-de5e71321951

📥 Commits

Reviewing files that changed from the base of the PR and between 7c40fcc and 5039408.

📒 Files selected for processing (5)
  • consts.hpp
  • memtouch.cpp
  • meson.build
  • write_pages_avx512.cpp
  • write_pages_avx512.hpp
✅ Files skipped from review due to trivial changes (1)
  • consts.hpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • write_pages_avx512.hpp
  • write_pages_avx512.cpp

📝 Walkthrough

Walkthrough

Adds an AVX-512 streaming page-write path with runtime detection and a --no_avx option, a shared constants header, an x86_64-only streaming implementation, refactors memtouch write dispatch to call the streamer or fallback, and updates Meson to build/link new static libraries and AVX-specific flags.

Changes

Cohort / File(s) Summary
Constants
consts.hpp
Adds inline static constexpr constants: PAGE_SIZE = 4096, PATTERN = 0xff, DEFAULT_STAT_IVAL = 1000.
AVX-512 implementation
write_pages_avx512.hpp, write_pages_avx512.cpp
Adds streaming_write_pages_avx512(void* mem_base, uint64_t page_start, uint64_t num_pages); x86_64 uses 512-bit non-temporal stores and _mm_sfence(), other platforms abort if called.
Write-path refactor
memtouch.cpp
Introduces WorkerThread::write_pages(page_start,num_pages), runtime AVX-512 detection, --no_avx propagation, 64-byte alignment enforcement for streaming path, and replaces per-page writes with dispatcher.
Build changes
meson.build
Adds -ffat-lto-objects, creates consts and write_pages_avx512 static libs (the latter gets -mavx512f conditionally on x86_64), and links them into memtouch via dependencies.

Sequence Diagram(s)

sequenceDiagram
    participant Worker as Worker Thread
    participant PreRun as pre_run()
    participant MainLoop as Main Write Loop
    participant WritePages as write_pages()
    participant SIMD as streaming_write_pages_avx512()
    participant Fallback as fallback_write_loop()

    Worker->>PreRun: start
    PreRun->>PreRun: detect AVX-512F & apply --no_avx
    PreRun-->>Worker: set avx512_streaming flag

    MainLoop->>WritePages: write_pages(page_start, num_pages)

    alt avx512_streaming == true
        WritePages->>SIMD: dispatch(mem_base, page_start, num_pages)
        SIMD->>SIMD: compute iterations (num_pages * PAGE_SIZE/(64*4))
        SIMD->>SIMD: prepare 512-bit vector with PATTERN
        SIMD->>SIMD: perform four _mm512_stream_si512 stores per iter
        SIMD->>SIMD: _mm_sfence()
        SIMD-->>WritePages: return
    else
        WritePages->>Fallback: perform page-by-page writes (loop/memset)
        Fallback-->>WritePages: return
    end

    WritePages-->>MainLoop: done
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I tunneled through bytes and found a stream,
ZMMs humming soft in a vector dream.
Pages lined up, fences set just right,
I nibble fast — the writes take flight.
Hooray for hops and memory light! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: implementing AVX-512 streaming instructions to boost write throughput, which is the core objective of all modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@olivereanderson
olivereanderson force-pushed the non-temporal-512-bit-stores branch from 10981d0 to 4d34361 Compare April 29, 2026 12:02
@olivereanderson
olivereanderson marked this pull request as ready for review April 29, 2026 12:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
meson.build (1)

1-97: ⚠️ Potential issue | 🟡 Minor

This file still fails clang-format-check in CI.

Please apply the repo formatting rules here before merging.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@meson.build` around lines 1 - 97, The meson.build file fails the repo's
clang-format-check; run the repository formatter (e.g., pre-commit /
clang-format with the repo config) on meson.build and commit the formatted
result so CI passes. Focus on normalizing whitespace/indentation, line breaks
and trailing commas in the cpp_flags array,
write_pages_avx512_lib/static_library blocks, and the executable(...) stanza
(references: cpp_flags, write_pages_avx512_lib, consts_lib, executable) so the
file matches the project's formatting rules.
🧹 Nitpick comments (1)
write_pages_avx512.cpp (1)

10-11: Enforce the page-size divisibility invariant at compile time.

The TODO should be a static_assert to prevent silent partial-write behavior if constants change later.

Proposed refactor
 void streaming_write_pages_avx512(void* mem_base, uint64_t page_start, uint64_t num_pages) {
-        // Write 4 * 64 bytes per iteration until we are done
-        // TODO: Compile time assert that PAGE_SIZE is indeed divisible by 64 * 4
+        // Write 4 * 64 bytes per iteration until we are done
+        static_assert((PAGE_SIZE % (64 * 4)) == 0, "PAGE_SIZE must be divisible by 256 bytes");
         auto num_iterations = num_pages * (PAGE_SIZE / (64*4));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@write_pages_avx512.cpp` around lines 10 - 11, Add a compile-time check that
PAGE_SIZE is divisible by (64*4) to prevent silent partial-write behavior:
replace the TODO with a static_assert using PAGE_SIZE and the divisor (64*4),
and keep the existing calculation of num_iterations = num_pages * (PAGE_SIZE /
(64*4)); reference PAGE_SIZE and num_iterations (and the expression (64*4)) so
the static_assert is colocated near where num_iterations is computed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@memtouch.cpp`:
- Around line 57-61: There are clang-format/style and a small syntax issue in
the preprocessor block that sets avx512_streaming: add the missing semicolon
after "avx512_streaming = false", ensure the `#if/`#else/#endif block formatting
matches project style, then run clang-format over memtouch.cpp to fix all
formatting violations (also address the other changed regions referenced around
the avx512_streaming logic and the areas corresponding to the earlier noted
hunks). Keep the variable name avx512_streaming and the
__builtin_cpu_supports("avx512f") call intact while correcting punctuation and
formatting.
- Around line 57-61: The non-x86 conditional branch is missing a semicolon
causing a compile error; in the `#else` branch set avx512_streaming = false; (add
the trailing semicolon) so the assignment to the variable avx512_streaming in
the preprocessor branch that contains __x86_64__ / else is syntactically
correct.

In `@write_pages_avx512.cpp`:
- Around line 3-8: The file unconditionally includes the x86-only header
<immintrin.h>, which breaks builds on non-x86 targets; move the `#include`
<immintrin.h> inside the existing architecture guard so it is only included when
__x86_64__ is defined and thus only used by the streaming_write_pages_avx512
function and any x86-specific code.

---

Outside diff comments:
In `@meson.build`:
- Around line 1-97: The meson.build file fails the repo's clang-format-check;
run the repository formatter (e.g., pre-commit / clang-format with the repo
config) on meson.build and commit the formatted result so CI passes. Focus on
normalizing whitespace/indentation, line breaks and trailing commas in the
cpp_flags array, write_pages_avx512_lib/static_library blocks, and the
executable(...) stanza (references: cpp_flags, write_pages_avx512_lib,
consts_lib, executable) so the file matches the project's formatting rules.

---

Nitpick comments:
In `@write_pages_avx512.cpp`:
- Around line 10-11: Add a compile-time check that PAGE_SIZE is divisible by
(64*4) to prevent silent partial-write behavior: replace the TODO with a
static_assert using PAGE_SIZE and the divisor (64*4), and keep the existing
calculation of num_iterations = num_pages * (PAGE_SIZE / (64*4)); reference
PAGE_SIZE and num_iterations (and the expression (64*4)) so the static_assert is
colocated near where num_iterations is computed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61ee0765-2f21-4628-a3f6-8fb35fc3bced

📥 Commits

Reviewing files that changed from the base of the PR and between d7fc9fd and 4d34361.

📒 Files selected for processing (5)
  • consts.hpp
  • memtouch.cpp
  • meson.build
  • write_pages_avx512.cpp
  • write_pages_avx512.hpp

Comment thread memtouch.cpp Resolved
Comment thread write_pages_avx512.cpp Resolved
Comment thread memtouch.cpp Outdated
auto page = n + pages_read + pages_written;
write_page(page);
}
*/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we can remove the old code. No need to keep it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I forgot about that 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread memtouch.cpp Outdated
@@ -50,11 +54,21 @@ class WorkerThread {
stats() {}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

would be good to default initialize avx512_streaming to false here. This way, you can also remove the #else path in pre_run()

@tpressure tpressure Apr 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Wait, even better would be to do avx512_streaming(!!__builtin_cpu_supports("avx512f")), wouldn't it?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The compiler also complains about this :)

../memtouch.cpp: In constructor ‘WorkerThread::WorkerThread(unsigned int, bool, unsigned int, unsigned int, uint64_t)’:
../memtouch.cpp:44:5: warning: ‘WorkerThread::avx512_streaming’ should be initialized in the member initialization list [-Weffc++]
   44 |     WorkerThread(unsigned id_,
      |     ^~~~~~~~~~~~

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed to __builtin_cpu_supports("avx512f") && (!no_avx) in the constructor.

Comment thread write_pages_avx512.cpp Outdated
void streaming_write_pages_avx512(void* mem_base, uint64_t page_start, uint64_t num_pages) {
(void) mem_base;
(void) page_start;
(void) num_pages;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This function should panic because we don't expect it to be actually called, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes,

Does C++ have something equivalent to Rust's unreachable! (or at least panic! ) ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There is __builtin_unreachable but I would not use it here because the the compiler simply does not generate an function epilogue. Hence, when the function is called at runtime, weird stuff happens.

I would suggest to do somthing like

printf("this function should not be called\n");
__builtin_trap() // generates #UD

@tpressure tpressure Apr 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You can also use

std::abort()

which raises SIGABRT

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

std::_Exit(-1) is also an alternative

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed to std::abort().

Comment thread memtouch.cpp Outdated


void write_pages(uint64_t page_start, uint64_t num_pages) {
#if defined (__x86_64__)

@tpressure tpressure Apr 29, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

No need to #if defined this here because you already did this in streaming_write_pages_avx512 and avx512_streaming should always be false for architectures that do not support it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah good catch! I initially did the logic inline and forgot to remove it afterwards.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed it now, but maybe technically it is better to keep it?

Other platforms will have to check against a trivially false branch on every invocation if the compiler isn't able to figure this out.

Comment thread meson.build Resolved
@tpressure

Copy link
Copy Markdown
Collaborator

Nice improvement, I left a few remaks.

@tpressure

Copy link
Copy Markdown
Collaborator

I will check the read-bandwidth issue on my systems and report back

@tpressure

Copy link
Copy Markdown
Collaborator

Also, it would be really nice if we guard this behind a parameter, at least to opt out. This will make the comparison between avx and non-avx simpler because we do not need to recompile the binary. Maybe --no-avx?

@olivereanderson
olivereanderson force-pushed the non-temporal-512-bit-stores branch from 4d34361 to 7c40fcc Compare April 29, 2026 15:15
@olivereanderson

Copy link
Copy Markdown
Contributor Author

Also, it would be really nice if we guard this behind a parameter, at least to opt out. This will make the comparison between avx and non-avx simpler because we do not need to recompile the binary. Maybe --no-avx?

Done, you can now disable the avx512f path via --no_avx (I use _ to stay consistent with the other parameters).

Will address the other remarks later.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
memtouch.cpp (1)

79-84: Batch the warmup writes too.

When avx512_streaming is enabled, this loop calls streaming_write_pages_avx512() once per page, and that routine fences once per call in write_pages_avx512.cpp:20-23. That throws away a lot of the new write-path gain and makes --once slower than the steady-state path.

♻️ Suggested refactor
-        for (uint64_t page{0}; page < num_pages; ++page) {
-            if (terminate) {
-                break;
-            }
-            write_pages(page, 1);
-        }
+        constexpr uint64_t warmup_batch_pages = 64;
+        for (uint64_t page{0}; page < num_pages and not terminate;
+             page += warmup_batch_pages) {
+            auto pages_this_batch =
+                std::min<uint64_t>(warmup_batch_pages, num_pages - page);
+            write_pages(page, pages_this_batch);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@memtouch.cpp` around lines 79 - 84, The warmup loop currently calls
write_pages(page, 1) per page which, when avx512_streaming is enabled, invokes
streaming_write_pages_avx512() and triggers a fence per page; change the loop to
batch multiple pages into a single write_pages call so the AVX512 streaming path
is called once per batch (reducing fence frequency). Modify the loop in
memtouch.cpp to accumulate a batch size (e.g., up to a constant BATCH_PAGES or
until terminate/num_pages boundary) and call write_pages(batch_start,
batch_count) for each batch; adjust or overload
write_pages/streaming_write_pages_avx512 as needed so it accepts a page count
and only performs the fence once per batch while preserving the terminate check
between batches.
meson.build (1)

65-73: Model consts as a header-only dependency.

consts.hpp only exports inline constexpr values, so this target has nothing to compile or link. Wrapping it in static_library() adds an unnecessary archive step and can be brittle across Meson/toolchain combinations; declare_dependency(sources: ..., include_directories: ...) is the cleaner shape here.

♻️ Suggested refactor
-# Shared constants
-consts_lib = static_library(
-  'consts',
-  sources: ['consts.hpp'],
-  cpp_args: cpp_flags,
-  link_args: linker_flags
-)
-
-consts_lib_dep = declare_dependency(link_with: consts_lib)
+# Shared constants
+consts_lib_dep = declare_dependency(
+  include_directories: include_directories('.'),
+  sources: ['consts.hpp'],
+)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@meson.build` around lines 65 - 73, The target `consts_lib` is being created
as a static_library even though `consts.hpp` is header-only; replace the
static_library + declare_dependency pattern with a single header-only dependency
created by declare_dependency(sources: ['consts.hpp'], include_directories:
include_directories('.')) and assign that to `consts_lib_dep` (remove
`consts_lib` and its static_library call and the link_with usage). Ensure any
consumers use `consts_lib_dep` as the dependency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@memtouch.cpp`:
- Around line 18-22: The xmmintrin.h include and the runtime AVX-512 check are
x86-specific and must be guarded: move the `#include` <xmmintrin.h> into the
existing `#if` defined(__x86_64__) block that already wraps
<write_pages_avx512.hpp>, and change the constructor initialization of
avx512_streaming (where __builtin_cpu_supports("avx512f") is called) so that the
call is only compiled under `#if` defined(__x86_64__) and avx512_streaming
defaults to false on non-x86_64 targets; update references in memtouch.cpp to
use the guarded initialization so non-x86 builds do not reference
__builtin_cpu_supports or xmmintrin.h.

---

Nitpick comments:
In `@memtouch.cpp`:
- Around line 79-84: The warmup loop currently calls write_pages(page, 1) per
page which, when avx512_streaming is enabled, invokes
streaming_write_pages_avx512() and triggers a fence per page; change the loop to
batch multiple pages into a single write_pages call so the AVX512 streaming path
is called once per batch (reducing fence frequency). Modify the loop in
memtouch.cpp to accumulate a batch size (e.g., up to a constant BATCH_PAGES or
until terminate/num_pages boundary) and call write_pages(batch_start,
batch_count) for each batch; adjust or overload
write_pages/streaming_write_pages_avx512 as needed so it accepts a page count
and only performs the fence once per batch while preserving the terminate check
between batches.

In `@meson.build`:
- Around line 65-73: The target `consts_lib` is being created as a
static_library even though `consts.hpp` is header-only; replace the
static_library + declare_dependency pattern with a single header-only dependency
created by declare_dependency(sources: ['consts.hpp'], include_directories:
include_directories('.')) and assign that to `consts_lib_dep` (remove
`consts_lib` and its static_library call and the link_with usage). Ensure any
consumers use `consts_lib_dep` as the dependency.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69b6a097-4ff9-4a9a-ad5d-302f8d1fe3b3

📥 Commits

Reviewing files that changed from the base of the PR and between 4d34361 and 7c40fcc.

📒 Files selected for processing (5)
  • consts.hpp
  • memtouch.cpp
  • meson.build
  • write_pages_avx512.cpp
  • write_pages_avx512.hpp
✅ Files skipped from review due to trivial changes (2)
  • write_pages_avx512.hpp
  • consts.hpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • write_pages_avx512.cpp

Comment thread memtouch.cpp Resolved
@olivereanderson
olivereanderson force-pushed the non-temporal-512-bit-stores branch from 7c40fcc to e5107cf Compare April 29, 2026 15:25
Comment thread memtouch.cpp
@@ -68,7 +80,7 @@ class WorkerThread {
if (terminate) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I am wondering whether terminate could be hoisted out of the loop?

The value of terminate should not be able to change in between loop iterations right?

It would be nice to replace this entire loop with

if !(terminate) {
    write_pages(0, num_pages);
}

@phip1611 phip1611 May 4, 2026 •

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 think the problem is that the warmup operation can take multiple seconds or even a minute (assume you write 14tb) and should be therefore abortable early. Therefore, we should keep it as is. terminate is set to true indirectly from the signal handler

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So you are saying that the value of the terminate bool can change between loop iterations?

If so should we not be doing atomic loads here?

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.

So you are saying that the value of the terminate bool can change between loop iterations?

yes.

If so should we not be doing atomic loads here?

Would technically be more correct, but in the worst case we would do simply one iteration more then necessary. You could fix this in a dedicated commti if you want.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

but in the worst case we would do simply one iteration more then necessary.

No, in the worst case we have UB. It certainly would be in Rust and I suspect the same to be true in C++.

@phip1611 phip1611 May 4, 2026 •

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.

Ah yes, thanks! Of course. I am not used to think about UB in C++ (barely programming with it these days) 😁
More information: https://stackoverflow.com/a/78876575

Using a atomic variable would be the safer approach: std::atomic<int> x;

@olivereanderson olivereanderson May 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Then to conclude: we leave this as is here and I address the existing UB in a follow up PR.

Signed-off-by: Oliver Anderson <oliver.anderson@cyberus-technology.de>
On-behalf-of: SAP oliver.anderson@sap.com
@olivereanderson
olivereanderson force-pushed the non-temporal-512-bit-stores branch from e5107cf to 5039408 Compare April 30, 2026 12:18

@phip1611 phip1611 left a 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.

Nice addition! My review was done with the help of Claude/Sonnet 4.6: I have
some comments. Also in general, your commit messages should explain why you use AVX512 and what the estimated performance gains are.

write_pages_avx512.cpp, line 12 — must-fix

The TODO is trivially resolvable since PAGE_SIZE is constexpr:

static_assert(PAGE_SIZE % (64 * 4) == 0, "PAGE_SIZE must be divisible by 256");

write_pages_avx512.cpp, line 15 — nit

C-style cast; use static_cast<char*>(mem_base) for consistency with the rest of the codebase.


meson.build, line 62 — should-fix

consts_lib is declared as a static_library with only a .hpp source. Meson will
produce an empty archive and the resulting link_with dependency on all consumers is
unnecessary overhead. Replace with a plain declare_dependency(include_directories: ...)
— no static_library, no linker dependency.


meson.build, line 86 — nit

Missing blank line between write_pages_avx512_lib_dep = declare_dependency(...) and the
following comment, inconsistent with the rest of the file.


memtouch.cpp, --no_avx argument — nit

Consider --no_avx512 to make clear this targets AVX-512 specifically and leaves room
for future SIMD paths. The existing flags use underscores consistently so the separator
style isn't the issue.


memtouch.cpp, pre_run() alignment assert — nit

The assert is gated on avx512_streaming but mmap(MAP_ANONYMOUS) already guarantees
page-granularity alignment (≥ 4096 bytes >> 64 bytes), so it can never fire. Either drop
it or make it unconditional with a comment referencing the mmap guarantee.


consts.hpp — nit

inline static constexpr at namespace scope is redundant: constexpr implies inline
since C++17. Use plain constexpr (or inline constexpr if you want to be explicit,
though that too is redundant at namespace scope).


Suggested commit split

One broader suggestion: the patch bundles a build system refactor, a new SIMD backend,
and a runtime dispatch mechanism into a single commit, which makes it harder to bisect
and review. I'd suggest splitting into three commits:

Commit 1: build: extract constants into shared consts.hpp

Move PAGE_SIZE, PATTERN, and DEFAULT_STAT_IVAL out of memtouch.cpp into a new
consts.hpp header so they can be shared with future translation units without pulling
in all of memtouch.cpp. No functional change.

Commit 2: memtouch: add AVX-512 non-temporal write backend

Add write_pages_avx512.cpp / .hpp implementing streaming_write_pages_avx512().
The function writes 256 bytes per loop iteration using four _mm512_stream_si512 stores
(non-temporal, bypassing the cache) and issues _mm_sfence on exit to restore store
ordering. The translation unit is compiled separately with -mavx512f so the flag does
not leak into the rest of the build. On non-x86 targets the function stub aborts, guarded
by the #if defined(__x86_64__) call-site check in memtouch.cpp.

Commit 3: memtouch: use AVX-512 streaming writes when available

Wire up the new backend via runtime CPU feature detection
(__builtin_cpu_supports("avx512f")). Workers opt in at construction time and fall back
to the existing memset-based path when AVX-512 is absent or --no_avx512 is passed.
Expected benefit: non-temporal stores bypass the LLC on write, reducing memory write
latency and avoiding cache pollution for this write-dominated workload.

@olivereanderson

Copy link
Copy Markdown
Contributor Author

Closing in favour of #37

Comment thread memtouch.cpp Outdated
static constexpr int PATTERN(0xff);
static constexpr int DEFAULT_STAT_IVAL(1000);

static std::atomic<bool> TERMINATE;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

please don't use upper case for variables, even if they are global. Just call it terminate or global_terminate if you want to make it more obvious.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Also, you can auto initialize this to false with:

static std::atomic<bool> global_terminate {false};

This saves you from initializing it in main()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Wrong PR?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

yes

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.

3 participants