以下20道多选题和10道设计题, 用于本书的测试。
- 以下哪些是C++性能优化的核心策略?(多选)
A) 优先优化所有代码段
B) 使用更高效的算法
C) 减少内存分配次数
D) 将所有循环展开
- 关于字符串优化,正确的措施包括?(多选)
A) 使用reserve()
预分配空间
B) 频繁使用+
运算符拼接字符串
C) 用字符数组代替std::string
D) 启用移动语义避免深拷贝
- 关于阿姆达尔定律,哪些描述正确?(多选)
A) 优化非热点部分也能显著提升性能
B) 优化占比80%的代码使其快2倍,整体提升1.6倍
C) 仅优化耗时10%的代码无法显著改善性能
D) 指导优先优化耗时占比高的代码
- 哪些行为会显著增加内存访问开销?(多选)
A) 频繁访问非连续内存
B) 结构体字段按大小对齐
C) 循环遍历std::vector
D) 使用链表存储大数据
- 关于分析器(Profiler),正确的说法是?(多选)
A) 可精确找到所有性能瓶颈
B) 无法识别算法复杂度问题
C) 适合测量I/O密集型程序
D) 可能遗漏多线程竞争问题
- 优化循环的有效方法包括?(多选)
A) 将循环变量改为unsigned
B) 移除循环内的虚函数调用
C) 将循环条件提到外部
D) 所有循环改为while
形式
- 哪些属于C++11移动语义的优势?(多选)
A) 减少临时对象复制
B) 允许修改右值
C) 自动启用所有标准容器的COW
D) 支持std::unique_ptr
所有权转移
- 关于智能指针,正确的使用方式是?(多选)
A) 用shared_ptr
管理所有动态对象
B)unique_ptr
明确独占所有权
C) 优先使用make_shared
代替new
D) 用weak_ptr
解决循环引用
- 选择
std::unordered_map
而非std::map
的场景是?(多选)
A) 需要按键排序遍历
B) 哈希冲突概率低
C) 频繁插入删除操作
D) 内存占用需最小化
- 减少函数调用开销的方法包括?(多选)
A) 禁用所有内联函数
B) 使用短小的内联函数
C) 用模板替代虚函数
D) 频繁调用小函数无需优化
-
答案:B、C
解析:书中强调算法选择和内存管理是优化核心,循环展开仅在特定场景有效,盲目优化所有代码违反90/10原则。 -
答案:A、C、D
解析:+
会产生临时对象,预分配和移动语义可减少内存操作,字符数组在特定场景更高效。 -
答案:B、D
解析:阿姆达尔公式为1/((1-P)+P/S)
,B符合计算,D是核心结论,A和C违背定律。 -
答案:A、D
解析:非连续访问和链表导致缓存不友好,vector内存连续,结构体对齐可提升访问效率。 -
答案:B、D
解析:分析器依赖采样,可能遗漏算法设计问题(如O(n²))和线程同步开销。
6.答案:B、C
解析:虚函数调用和重复计算循环条件影响性能,变量类型和循环形式影响较小。 -
答案:A、B、D
解析:移动语义通过右值引用避免复制,但C++11后标准库弃用COW实现。 -
答案:B、C、D
解析:滥用shared_ptr
会导致额外开销,其余选项是推荐做法。 -
答案:B、C
解析:哈希表在平均O(1)操作下更高效,但内存可能更大且无序。 -
答案:B、C
解析:内联适合小函数,虚函数调用有额外开销,模板可静态多态。 -
关于缓存局部性,正确的优化措施是?(多选)
A) 优先使用多维数组
B) 用std::list
替代std::vector
C) 结构体字段按访问频率排列
D) 避免在热循环中随机访问内存
- 哪些属于并发编程的正确优化?(多选)
A) 尽可能细化锁的粒度
B) 所有共享变量用volatile
修饰
C) 使用无锁数据结构
D) 避免线程间频繁传递大数据
- 关于内存分配器,正确的描述是?(多选)
A) 自定义分配器可减少碎片
B)std::make_shared
合并对象和控制块内存
C) 内存池适合固定大小对象
D) 默认分配器总是最优选择
- 优化查找操作的正确方法包括?(多选)
A) 对有序数据用二分查找
B) 小数据集用线性搜索更快
C) 哈希表查找总是O(1)
D) 用std::find
替代std::binary_search
- 正确的I/O优化策略是?(多选)
A) 逐字符读取文件
B) 使用缓冲区批量读写
C) 异步处理耗时I/O操作
D) 频繁调用fstream::flush()
- 关于
std::vector
,正确的优化措施是?(多选)
A) 默认预留大量空间防扩容
B)emplace_back
避免临时对象
C) 遍历时用迭代器替代下标
D) 排序时用std::sort
而非qsort
- 哪些行为可能导致伪共享(False Sharing)?(多选)
A) 多线程修改同一缓存行的不同变量
B) 频繁访问线程局部存储
C) 结构体字段间插入填充字节
D) 使用原子操作修改变量
- 关于分支预测,正确的优化是?(多选)
A) 将高概率条件放在if
后
B) 用查表法替代复杂条件
C) 避免在循环中使用switch
D) 用无分支算法减少条件判断
- 正确的预计算优化是?(多选)
A) 在运行时计算常量表达式
B) 预生成查找表减少重复计算
C) 将循环不变式移出循环
D) 用编译时constexpr
计算
- 关于移动语义和
std::move
,正确的是?(多选)
A)std::move
强制转换为右值
B) 移动后原对象不可再使用
C) 所有标准容器支持移动语义
D) 移动构造函数需标记noexcept
- 答案:C、D
解析:连续内存访问(如vector)和字段紧凑排列提升缓存命中率。 - 答案:A、C、D
解析:volatile
不能替代原子操作,其余选项是并发优化关键。 - 答案:A、B、C
解析:默认分配器通用但非最优,特定场景需自定义分配策略。 - 答案:A、B
解析:哈希表最坏情况O(n),binary_search
需数据有序。 - 答案:B、C
解析:批量读写减少系统调用,异步避免阻塞,频繁flush增加开销。 - 答案:B、D
解析:过度预留浪费内存,迭代器与下标性能无显著差异,std::sort
通常更高效。 - 答案:A
解析:伪共享由多线程修改同一缓存行导致,填充字节可缓解。 - 答案:B、D
解析:CPU会预取指令,高概率条件应放if
前,查表和无分支减少分支误预测。 - 答案:B、C、D
解析:预计算旨在将计算提前,运行时计算常量违背此原则。 - 答案:A、C、D
解析:移动后原对象处于有效但未定义状态,通常应标记noexcept
以便容器优化。
设计题
题目1:高效字符串拼接
要求:实现字符串批量拼接,避免临时对象和重复内存分配
输入:包含1万个随机字符串的vector,每个字符串长度50-100字符
输出:拼接后的完整字符串
优化点:预分配内存、减少临时对象、选择最优拼接方式
题目2:自定义内存池实现
要求:为固定大小对象设计内存池,支持快速分配/释放
输入:10万次随机尺寸的对象创建/销毁请求(80%分配20%释放)
输出:总耗时和内存碎片率
优化点:批量内存管理、链表式空闲块管理
题目3:高效大规模数据查找
要求:在10亿有序整数中实现快速查找
输入:随机生成的有序数组和1万个查询值
输出:查询结果和总耗时
优化点:二分查找优化、缓存预取、SIMD指令
题目4:多线程日志处理器
要求:实现支持高并发的日志处理系统
输入:每秒1万条日志写入,每条日志需解析和存储
输出:处理吞吐量和CPU利用率
优化点:无锁队列、线程池、批量提交
题目5:零拷贝数据传输
要求:实现大文件网络传输的零拷贝机制
输入:1GB文件分块传输
输出:传输耗时和内存占用
优化点:mmap内存映射、sendfile系统调用
题目6:无锁哈希表实现
要求:实现线程安全的无锁哈希表
输入:100线程并发执行100万次插入/查询操作
输出:操作成功率和QPS
优化点:CAS原子操作、开放寻址法
题目7:SIMD矩阵运算
要求:实现4K×4K浮点矩阵乘法加速
输入:两个随机生成的单精度浮点矩阵
输出:运算结果和GFLOPS
优化点:AVX2指令集、循环展开、内存对齐
题目8:缓存优化搜索
要求:实现百万级游戏对象空间查询
输入:3D空间坐标数据和范围查询请求
输出:查询结果和帧率
优化点:空间划分、缓存友好数据结构、分支预测
题目9:高效JSON解析
要求:实现高性能JSON解析器
输入:100MB嵌套JSON文件
输出:解析后的DOM树和耗时
优化点:内存池分配、SIMD解析、状态机优化
题目10:延迟内存回收系统
要求:实现对象池的延迟释放机制
输入:高频对象创建/销毁请求(峰值1万QPS)
输出:内存波动图和99%延迟
优化点:批量回收、epoch-based回收、线程本地存储
答案与详解
题目1答案
传统方法:
std::string result;
for (auto& s : vec) {
result += s; // 多次重分配
}
优化方法:
size_t total = 0;
for (auto& s : vec) total += s.size();
std::string result;
result.reserve(total); // 预分配
for (auto& s : vec) {
result.append(s); // 避免临时对象
}
测试用例:
std::vector<std::string> vec(10000, std::string(80, 'a'));
auto t1 = std::chrono::high_resolution_clock::now();
// 调用两种方法
auto t2 = std::chrono::high_resolution_clock::now();
std::cout << "Time: " << (t2-t1).count() << "ns\n";
性能分析:
优化方法通过预分配消除O(n²)次内存分配,实测速度提升8-10倍。append()
直接操作内部缓冲区,避免临时字符串构造。
题目5答案
传统方法:
char buf[4096];
while(read(fd, buf, 4096)) {
send(sockfd, buf, 4096); // 多次内核拷贝
}
零拷贝方法:
void* map = mmap(file, size, PROT_READ, MAP_PRIVATE, fd, 0);
sendfile(out_fd, in_fd, NULL, file_size); // 内核直接传输
性能测试:
constexpr size_t SIZE = 1GB;
auto start = std::chrono::steady_clock::now();
// 执行传输
auto end = std::chrono::steady_clock::now();
std::cout << "Throughput: "
<< SIZE/((end-start).count()/1e9) << " GB/s\n";
其他设计题目, 稍后实现