今天面试阿里后端开发,
● 面试官提问:谈谈你对 const 理解
● 我这样回答的: const 仅仅表示变量不能修改,太简答了,我早就知道了。
● 面试官回复:你觉得 这样能面过 P7 ,百万年薪岗位吗。
痛定思痛,今天,聊聊比较重要性能优化的思路:
● 编译期优化
● 从 cpu 角度理解 编译期优化
一. 在项目中带来什么收益,why
1.1 编译期计算
● 所有比较在编译期完成,零运行时开销。
● 例如 if constexpr std::is_same_v (先别走看,不同特性,有着同样原理)
● 更多阅读 CppCon 2014
CppCon 2014: Walter E. Brown "Modern Template Metaprogramming: A Compendium, Part I
1.2 为什么能带来这样收益,第一性原理或者核心实现原理 是什么?
std::is_same_v 是 C++17 引入的编译期类型比较工具,
用于判断两个类型是否严格相同。
if T and U name the same type (taking into account const/volatile qualifications),
provides the member constant value equal to true. Otherwise value is false.
● 具备内部实现先不考虑
● is_same_v 是 is_same<T, U>::value 的简写:
template <typename T, typename U>
inline constexpr bool is_same_v = is_same<T, U>::value;
其底层基于模板元编程中的偏特化机制实现:
与 typeid 的对比
特性 std::is_same_v typeid
比较阶段 编译期 运行时
多态类型 静态类型 动态类型(可能派发到派生类)
性能影响 零开销 RTTI 开销
适用场景 模板元编程、静态检查 运行时类型识别、调试
typeid 运算符
// expre_typeid_Operator.cpp
// compile with: /GR /EHsc
include
include
class Base {
public:
virtual void vvfunc() {}
};
class Derived : public Base {};
using namespace std;
//typeid 运算符允许在运行时确定对象的类型。
int main() {
Derived* pd = new Derived;
Base* pb = pd;
cout << typeid( pb ).name() << endl; //prints "class Base *"
cout << typeid( *pb ).name() << endl; //prints "class Derived"
cout << typeid( pd ).name() << endl; //prints "class Derived *"
cout << typeid( *pd ).name() << endl; //prints "class Derived"
delete pd;
}
if constexpr
● 在普通代码中用 if constexpr 基本没有实际用途。
● if constexpr 是 C++17 引入的编译期条件判断语句。
它在编译时判断条件,如果条件为假,则不会对分支内的代码进行实例化
与模板的关系 在模板中,if constexpr 可以根据模板参数的类型或值,在编译期选择不同的代码路径
● 核心原理:编译时分支决策 编译时条件求值
if constexpr 的条件必须是编译时常量表达式(如 std::is_same_v<T, int>)。编译器在模板实例化或代码生成阶段直接计算条件值,而非运行时
● 分支代码选择性编译
○ 条件为 true:仅编译 if 分支的代码,else 分支(若有)被完全丢弃,不进行语法检查。
○ 条件为 false:仅编译 else 分支,if 分支代码被丢弃
特性 if constexpr 普通 if
条件求值时机 编译时 运行时
分支代码处理 仅编译匹配分支,丢弃其余 编译所有分支,运行时选择执行
语法检查范围 仅检查匹配分支 检查所有分支的语法合法性
适用场景 模板元编程、类型分发 运行时条件判断
无效代码安全性 丢弃分支可包含无效代码 所有分支必须语法合法
1.3 预计多大收益:CPU的分支预测是如何影响程序运行
● 性能优化利器之constexpr
● 消除分支预测失败开销
● 原理:传统 if 在运行时进行分支预测,失败时需清空流水线(约 10~20 周期开销)2。
● if constexpr 优势:在编译时丢弃未匹配分支,生成无分支跳转的代码,彻底避免预测失败17。
● 案例:在 LLVM 编译器中,模板元编程改用 if constexpr 后,解析器的吞吐量提升 12%~15%(测试数据基于 SPEC CPU 2017)
● 实际性能测试对比
测试场景 传统 if
/ SFINAE if constexpr 提升幅度
类型分发(百万次调用) 42 ms 29 ms 31%
JSON 解析(大文件) 78 MB/s 吞吐量 92 MB/s 吞吐量 18%
网络协议处理(小包) 4.2 μs/包 3.5 μs/包 17%
二、举例说明
把字符串变成整数
该函数利用模板和 if constexpr,实现了"根据目标类型自动选择合适的字符串转数值函数"。
只有支持的类型(int、long、long long、double)会被实例化
include
include
include <type_traits>
using namespace std;
template
T str_to_num(const std::string& s)
{
if constexpr (is_same_v<T, int>) {
return std::stoi(s);//如果转换成功的话,stoi函数将会把转换后的得到数字以int类型返回
} else if constexpr (is_same_v<T, long>) {
return std::stol(s);
} else if constexpr (is_same_v<T, long long>) {
return std::stoll(s); //long long stoll
} else if constexpr (is_same_v<T, double>) {
return std::stod(s);
}
}
//g++ 3_if.cpp -std=c++17
int main() {
std::string s4 = "3.14159";
double d = str_to_num (s4);
std::cout << "double: " << d << std::endl;
return 0;
}
2.2 序列化 不存在的字段对应类构造
案例:从谷歌事故报告看技术透明度:我们差的不是SRE,是承认问题的勇气 。配置一个空策略导致解析 null 这几张
业务人员:清楚 什么字段 对应什么类型。
template
bool JSONDecoder::decode_json(const char *name, T& val, JSONObj *obj, bool mandatory)
{
JSONObjIter iter = obj->find_first(name);
if (iter.end()) {
if (mandatory) {
std::string s = "missing mandatory field " + std::string(name);
throw err(s);
}
if constexpr (std::is_default_constructible_v ) {
val = T();
}
// 如果条件为 true,那么 {} 里面的代码 val = T(); 就会被编译。
return false;
}
保证了代码的健壮性:
它确保了即使 JSON 中缺少了某个可选字段,
你的变量也能被初始化为一个合理、确定的"空"状态,
而不是未定义的值。
在大型分布式系统如 Ceph、GlusterFS、分布式 KV 引擎(如 RocksDB)开发过程中,我们常常需要根据类型 T 的特性(是否可构造、可拷贝、可移动)来决定容器行为、初始化策略、内存对齐方式甚至 IO 缓冲区创建方式。
std::is_default_constructible 是 C++ 类型萃取(Type Traits)体系的一部分,它提供了一种在编译期判断类型是否支持默认构造的机制
template< class T >
inline constexpr bool is_default_constructible_v = is_default_constructible ::value;
三、CPU眼里的C/C++
使用到工具
● https://godbolt.org/
● 或者 gdb
3.1 编译期计算 什么含义
● const 与 constexpr 同样效果效果
int main() {
const int val = 1 + 2; //明确一数值
return 0;
}
上述代码汇编结果如下:
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 3
mov eax, 0
pop rbp
ret
从上述汇编结果可以看出,在编译阶段就将val赋值成3,也就是说在编译阶段完成了求值操作
相反 普通的函数 求值过程将会延后至运行时候
constexpr int val = 1 + 2;//性能优化利器之constexpr
3.2 if constexpr 和 if 区别
三个 if 判断,最后编译代码后只有 1 个
基础回顾:c++代码生成的汇编代码
步骤 命令 等价命令 输出文件
预处理 cpp gcc -E .i, .ii
编译 cc1, cc1plus gcc -S .s
汇编 as gcc -c .o, .obj
链接 ld gcc 可执行文件
1 GCC编译C/C++的四个过程
gcc 是 GUN Compiler Collection的缩写。
预处理(pre-processing),E:插入头文件,替换宏,展开宏
gcc -E main.c -o main.i
编译(Compiling)S:编译成汇编
gcc -S main.i --o main.s
汇编(Assembling) c:编译成目标文件
gcc --c main.s --o main.o
链接 (Linking):链接到库中,变成可执行文件
gcc main.o --o main
./main
也可以一次性完成:
gcc main.c --o main
g++ test.cpp -o test
objdump -d tes
最动人的作品,为自己而写,刚刚好打动别人
我在寻找一位积极上进的小伙伴,
一起参与神奇早起 30 天改变人生计划,发展个人事业,不妨 试试
1️⃣关注公众号:后端开发成长指南(回复面经获取)获取过去我全部面试录音和大厂面试复盘攻略
2️⃣ 感兴趣的读者可以通过公众号获取老王的联系方式。
加入我的技术交流群Offer 来碗里 (回复"面经"获取),一起抱团取暖
----------------我是黄金分割线-----------------------------
抬头看天:走暗路、耕瘦田、进窄门、见微光,
● 我要通过技术拿到百万年薪P7职务,别人投入时间完成任务,别人采取措施解决问题了,不要不贪别人功劳,
● 但是不要给自己这样假设:别人完成就等着自己完成了,大家一个集团,一个公司,分工不同,不,这个懒惰表现,这个逃避问题表现,
● 别人不这么假设,至少kpi上不会写成自己的,至少晋升不是你,裁员淘汰是,你的整个公司ceo,整个部门总裁,整个领导不帮助一下的,他们不这么想 ,你什么没做,战略是别人10年一点带你研究的多难,项目拆分别人10年完成多少问题,项目实现10年安排组织一点点完成多少bug,多少代码,是不要给自己这样假设:你等了看了观察10年什么做 ,0 贡献,
● 但是不要给自己这样假设,别人全部市场,别人全部市场,别人占据全部客户,一切重要无比,你太差,太才,思考不行,沟通不行,认知不行,去tmd,给别人丢脸。这个方面我无法控制,在这方面经历任何问题应该的。
● 我控制 的事情是 我必须亲自了解行业遇到难题,了解有什么需求,行业解决方案,我可以从三万英尺看问题,像周围人学习,像免费公开英文资料学习,从模仿开始。然后免费公开。我要通过技术拿到百万年薪P7职务,我必须糊涂混沌中走出来
● 目标:拿百万年 想进入一线大厂,但在C++学习和应用上存在瓶颈,渴望跨越最后一道坎。
● 现状:缺乏实战,渴望提升动手能力公司的项目不会重构,没有重新设计的机会,导致难以深入理解需求。
● 成为优秀完成任务,成为团队、公司都认可的核心骨干。优秀地完成任务= 高效能 + 高质量 + 可持续 + 可度量
低头走路:
● 一次专注做好一个小事。
● 不扫一屋 何以扫天下,让自己早睡,早起,锻炼身体,刷牙保持个人卫生,多喝水 ,表达清楚 基本事情做好。
● 我控制 的事情是 我通过写自己代码拿到百万收益。代码就是杠杆,我必须创造可以运行在2c2g云主机小而美产品出来(服务普通人),而不是运行构建至少10台64cpu 300g内存物理机大而全项目(领航者,超越其他产品,出货全球N1,这个还是有停留有限斗争游戏,为top 10人企业服务)我必须糊涂混沌中走出来