Just always having int as default if usage is under contract.
Reference:
Object Lifetimes reading minute
alignas(int) unsigned char buffer[sizeof(int)]; // used for provenance contract.
alignas(int) unsigned char buffer[sizeof(int)]; // used for provenance contract.
One byte types:
char8_t is faster due to optimizer.
this is that a pointer of char, unsigned char, or std::byte type can point to anywhere, thus
data.size() could potentially being modified inside the loop, thus accessing data.size() is necessary.
As for char8_t has no this privilege, thus data.size() is fixed and stored inside a register.
As for char8_t has no this privilege, thus data.size() is fixed and stored inside a register.
The Problem: "Type Aliasing" & The Strict Aliasing Rule
In C++, the compiler assumes that two pointers of different types do not point to the same memory location. This allows for massive optimizations.
However, there is a historical exception to this rule: char types (char, signed char, unsigned char) and std::byte are allowed to alias anything.
Thus:
For char, unsigned char, and std::byte:
The compiler looks at data[i] = ... and thinks: "Uh oh. Writing to this byte might technically be overwriting the memory where the std::vector object itself keeps its internal size tracker!"
Because a char* can point anywhere, the compiler is forced to be incredibly cautious. It re-reads data.size() from memory on every single iteration of the loop just in case the last write accidentally changed it. This slows things down.
For char8_t:
When char8_t was introduced in C++20 for UTF-8 string data, the committee decided not to give it this "alias anything" superpower. It obeys strict aliasing.
The compiler looks at the loop and thinks: "Writing to a char8_t array cannot possibly change the std::vector's internal size variables because they are different types."
also, under meta-programming, type inference of those bitfields type has to be casted.
Reference:
L2 cache is used for data and instructions.
e.g.
slower, due to branch and if branch succeed, instructions are replaced in the cache.
slower, due to branch and if branch succeed, instructions are replaced in the cache.
Reference:
Always start with DoD; focus on algorithm.
Reference:
Align with the cache line to avoid false sharing
All-in-all summary
Avoid long branches:
// BAD
void process_data(Data* d) {
if (!d) {
// LONG BRANCH / COLD CODE
// Imagine 100 lines of logging, stack tracing,
// and complex error recovery logic here.
log_error("Null pointer detected...");
cleanup_subsystems();
notify_admin_via_snmp();
throw std::runtime_code("Fatal Error");
}
// This "hot" code is now physically far away from the 'if'
// check in the compiled binary.
d->value += 42;
d->status = Ready;
}
// Better, avoid I-Cache miss
// Move cold logic to a non-inline function
[[noreturn]] void handle_fatal_error() {
log_error("Null pointer detected...");
cleanup_subsystems();
notify_admin_via_snmp();
throw std::runtime_code("Fatal Error");
}
void process_data_optimized(Data* d) {
// C++20 [[likely]]/[[unlikely]] attributes guide the compiler
if (!d) [[unlikely]] {
handle_fatal_error(); // A jump to a far-away location happens ONLY on error
}
// This code is now physically adjacent to the 'if' check
d->value += 42;
d->status = Ready;
}
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.