Last updated:2026-10-12
C++ restrict and Compiler Optimization¶
While reviewing a ClickHouse PR (ClickHouse#19946) I saw __restrict used in real code and studied it on the spot: how much optimization room can one keyword give the compiler? Conclusion first: in hot loops where "the compiler cannot prove two pointers do not alias", restrict delivers a clearly visible speedup.
1. Background: Why Pointer Aliasing Slows Code Down¶
When the compiler optimizes loops, its worst fear is two pointers referring to the same memory (aliasing). Look at this classic scenario:
If out and a alias, then writing out[i] may change a[i], so the compiler dares not reorder reads and writes, dares not use vector instructions, and can only proceed honestly one instruction at a time. restrict, in turn, is the programmer's promise to the compiler: memory accessed through this pointer will not alias with any other pointer. With that promise made, the compiler can let loose.
2. Measurement: The Aggregate Computation Hotspot¶
The benchmark below simulates the hot path of a database aggregation computation (a simplified version of the ClickHouse PR); the only difference between the two versions is __restrict:
#include <vector>
#include <memory>
#include <benchmark/benchmark.h>
#define LEN 1<<20
using AggregateDataPtr = char *;
struct AvgState {
uint64_t numerator{0};
uint64_t denominator{0};
double divide() { return numerator / denominator; }
};
class Column {
public:
std::vector<uint64_t> & data() {return data_; }
private:
std::vector<uint64_t> data_;
};
template <class Derived>
class IAggregate {
public:
void addBatch(size_t batch_size, AggregateDataPtr state, Column *args) __attribute__((noinline)) {
for (size_t i = 0; i < batch_size; ++i) {
static_cast<const Derived *>(this)->add(state, args, i);
}
}
void addBatchWithoutOpt(size_t batch_size, AggregateDataPtr state, Column *args) __attribute__((noinline)) {
for (size_t i = 0; i < batch_size; ++i) {
static_cast<const Derived *>(this)->addWithoutOpt(state, args, i);
}
}
};
class AvgAggregator final : public IAggregate<AvgAggregator> {
public:
static AvgState& data(AggregateDataPtr __restrict place) { return *reinterpret_cast<AvgState *>(place); }
static AvgState& data_without_opt(AggregateDataPtr place) { return *reinterpret_cast<AvgState *>(place); }
void add(AggregateDataPtr __restrict state, Column * args, uint64_t row_num) const {
data(state).numerator += args[0].data()[row_num];
++(data(state).denominator);
}
void addWithoutOpt(AggregateDataPtr state, Column * args, uint64_t row_num) const {
data_without_opt(state).numerator += args[0].data()[row_num];
++(data_without_opt(state).denominator);
}
};
static void BM_loop_with_optimize(benchmark::State& state) {
AvgAggregator avgAggregator;
Column column;
for (size_t i = 0 ; i < LEN; i++) {
column.data().push_back(i);
}
Column arg0[] = {column};
// Code inside this loop is measured repeatedly
for(auto _ : state) {
auto avgState = std::make_unique<AvgState>();
avgAggregator.addBatch(LEN, reinterpret_cast<AggregateDataPtr>(avgState.get()), arg0);
}
}
static void BM_loop_without_optimize(benchmark::State& state) {
AvgAggregator avgAggregator;
Column column;
for (size_t i = 0 ; i < LEN; i++) {
column.data().push_back(i);
}
Column arg0[] = {column};
// Code inside this loop is measured repeatedly
for(auto _ : state) {
auto avgState = std::make_unique<AvgState>();
avgAggregator.addBatchWithoutOpt(LEN, reinterpret_cast<AggregateDataPtr>(avgState.get()), arg0);
}
}
BENCHMARK(BM_loop_without_optimize);
BENCHMARK(BM_loop_with_optimize);
https://github.com/ClickHouse/ClickHouse/pull/19946
3. Results and Summary¶
(1) The version with __restrict (BM_loop_with_optimize) is clearly faster: once the two state pointers numerator/denominator are annotated, the compiler keeps them in registers and the loop fully vectorizes; the un-annotated version reloads from memory every time;
(2) restrict is currently not a standard C++ keyword; the mainstream approach is compiler extensions: GCC/Clang use __restrict (or __restrict__), MSVC uses __restrict, and C++26 is discussing standardization (the std::indirect/no_alias proposals);
(3) warning: restrict is a promise with no insurance — if aliasing occurs, it is undefined behavior. Only use it on hot paths where you can prove there is no aliasing (for example, independent state blocks returned by an allocator).
One-sentence summary: in a hot loop with multiple pointer reads/writes and genuinely no aliasing, __restrict is an extremely cost-effective optimization. The accompanying code is in the benchmark above; feel free to run it yourself on quick-bench.
Related Topics¶
- C++ Pure Virtual Functions and Abstract Classes | C++那些事
- C++ assert | C++那些事
- C++ Bit Fields | C++那些事
- Implementing C++ Polymorphism in C | C++那些事
- C++ const Constants and Qualifiers | C++那些事
- The Complete C++ const Family | C++那些事
- C++ Dereference Operator * | C++那些事
- C++ Initialization and Assignment | C++那些事
- Ways to Return Multiple Values in C++ | C++那些事
- C++ mutable Keyword | C++那些事