公众号
点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了
qq群 点击进入 满了加这个 729240657
欢迎投稿,推荐或自荐文章/软件/资源等,评论区留言
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2025-01-08 第288期
主要改动就是gsl span性能提升,以前相比std::span很慢(边界检查)
考虑我们想要调用不同的基类
看代码
struct WidgetBase
{
// local mode
WidgetBase();
// remote mode
WidgetBase(std::string const& server);
// The mutex makes this non-copyable, non-movable
std::mutex m_mutex;
};
struct WidgetOptions
{
⟦ random stuff ⟧
};
struct Widget : WidgetBase
{
Widget(WidgetOptions const& options) :
// 不好使
CanBeLocal(options)
? WidgetBase()
: WidgetBase(GetServer(options))
{}
static bool CanBeLocal(WidgetOptions const&);
static std::string GetServer(WidgetOptions const&);
};
怎么搞?通过create函数+返回值优化来转发
struct Widget : WidgetBase
{
Widget(WidgetOptions const& options) :
WidgetBase(ChooseWidgetBase(options))
{}
static bool CanBeLocal(WidgetOptions const&);
static std::string GetServer(WidgetOptions const&);
private:
static WidgetBase ChooseWidgetBase(
WidgetOptions const& options)
{
if (CanBeLocal(options)) {
return WidgetBase();
} else {
return WidgetBase(GetServer(options));
}
}
};
分享一下失敗的經驗, 還想不到什麼好方法讓Module跟非Module溝通..
目前不推荐使用module
他的场景是这样的,检查一组uint8数组判断偶数个数,并且已经确认偶数在0-255之间
简单代码示这样的
auto count_even_values_v1(const std::vector<uint8_t> &vec)
{
return std::count_if(
vec.begin(),
vec.end(),
[](uint8_t x) { return x % 2 == 0; }
);
}
观察汇编
.LCPI0_1:
.byte 1
count_even_values_v1():
; ............
vpbroadcastb xmm1, byte ptr [rip + .LCPI0_1]
.LBB0_6:
vmovd xmm2, dword ptr [rsi + rax]
vpandn xmm2, xmm2, xmm1
vpmovzxbq ymm2, xmm2
vpaddq ymm0, ymm0, ymm2
add rax, 4
cmp r8, rax
jne .LBB0_6
不知道为什么扩展成64位了。
原因在于countif的返回值difference_type,
但我们结果明显小于255,可以改成uint8
所以代码改成这样
template<typename Acc, typename It, typename Pred>
Acc custom_count_if(It begin, It end, Pred pred)
{
Acc result = 0;
for (auto it = begin; it != end; it++)
{
if (pred(*it))
result++;
}
return result;
}
auto count_even_values_v2(const std::vector<uint8_t> &vec)
{
return custom_count_if<uint8_t>(
vec.begin(),
vec.end(),
[](uint8_t x) { return x % 2 == 0; }
);
}
再看汇编
.LCPI0_2:
.byte 1
count_even_values_v2():
; ............
vpbroadcastb ymm1, byte ptr [rip + .LCPI0_2]
.LBB0_11:
vmovdqu ymm2, ymmword ptr [rdx + rax]
vpandn ymm2, ymm2, ymm1
vpaddb ymm0, ymm2, ymm0
add rax, 32
cmp rdi, rax
jne .LBB0_11
可以看到之前的汇编是dword
每次处理4个8位值(dword),通过vpandn提取最低位并取反,再通过vpmovzxbq将结果零扩展为64位累加
现在是ymmword
每次处理32个8位值(ymmword),使用vpaddb直接累加8位结果
测试显示速度快两倍以上 godbolt https://godbolt.org/z/vvfeTsfKx
这个例子让我想起之前聊过的64*64计算问题
如果计算64 x 64 -> 128 不要提前转128 计算途中转就可以,编译器知道你想干嘛,提前转128会导致编译器认为你想生成256的数去截断 128,导致多余的mul
https://github.com/StarRocks/starrocks/pull/11966
他这个优化就是先写出坑的代码,再改汇编改进
能确定结果集的优化会更立竿见影
另外修改返回值也能获得这个优化,感谢LH_mouse/萧叶轩指出
如果压缩就最外层zstd/lz4 不考虑字段兼容性问题,只为了快
游戏场景pb接受不了的场景
https://github.com/CppCon/CppCon2024/blob/main/Presentations/Using_Modern_Cpp_to_Build_XOffsetDatastructure.pdf
https://github.com/ximicpp/XOffsetDatastructure
https://www.youtube.com/watch?v=agRbVcMkqTY
看一乐
讲了分支预测like/unlike,缓存优化,SIMD
缓存优化讲了
一个loop tiling举例
const int BLOCK = 1024;
for (int start = 0; start < N; start += BLOCK) {
int end = std::min(start + BLOCK, N);
for (int i = start; i < end; ++i) {
result[i] += compute(A[i], B[i]);
}
}
讲了循环展开/向量化,函数内联和指令缓存效应,返回值优化,链接优化(LTO/WPO)内存对齐/填充,SOA/AOS
其他的没啥说的,讲过多次
讲了无锁编程,线程亲和,numa
false sharing问题,对象在同一个内存行导致互相影响
比如
struct PaddedCounters {
alignas(64) std::atomic<int> c1;
alignas(64) std::atomic<int> c2;
};
其他没啥有用的东西
检查过多的上下文切换,如何判定
缓解
检测内存碎片
缓解
案发现场
struct Base {
virtual ~Base() = default;
virtual auto operator==(Base const&) const -> bool = 0;
};
struct Derived : Base {
int m1, m2;
bool operator==(Base const& rhs) const override {
if (typeid(rhs) == typeid(Derived)) { // 确保类型严格匹配
return *this == static_cast<Derived const&>(rhs); // 调用默认比较
}
return false;
}
bool operator==(Derived const&) const = default; // 默认生成
};
默认生成会生成
auto Derived::operator==(Derived const& rhs) const -> bool {
return static_cast<Base const&>(*this) == static_cast<Base const&>(rhs)
and m1 == rhs.m1
and m2 == rhs.m2;
}
调用基类,基类再调用子类,无限循环
解决方法就是这种接口(operator ==)别虚函数
考虑这么个场景
struct Transaction;
bool should_send(Transaction *t);
void send(Transaction *t);
void abandon(Transaction *t);
void resolve(Transaction *t)
{
if (should_send(t)) {
send(t);
} else {
abandon(t);
}
}
在金融交易系统中,大部分交易请求会被放弃(abandon()),仅有少数需要发送(send())。由于分支预测器倾向于预测进入高频路径(abandon()),当实际需要执行低频的 send() 时,会导致以下性能问题
这种场景是likely这种东西无法搞定的,需要的是硬件写死的那种需求
不是说likely不行,你加上likely没法改变运行状态中分支预测器的行为,只是改变汇编
有一种玩法就是cache warm,长时间跑假的执行的数据骗他。不过CPU肯定上升就是了