CppCon 2015 学习:The Birth of Study Group 14
这段描述讲的是在某些系统或应用中,内存使用的限制和挑战,通常是在嵌入式设备、游戏主机或高性能图形应用中常见的情况。帮你拆解理解如下:
理解内存使用的典型场景和限制
1. 固定内存预算(Fixed memory budgets)
- 可用内存容量是固定且有限的,开发者必须严格控制内存使用,不能随意膨胀。
2. 100 MB 到几 GB 级别
- 虽然几百兆到几GB听起来不少,但对于复杂的图形或游戏应用来说这其实是有限的资源。
3. 共享 CPU/GPU 内存
- CPU 和 GPU 共享同一块物理内存,
- 需要在纹理数据、动画数据、帧缓冲区(framebuffers)等之间合理分配内存。
4. 无交换空间和临时磁盘缓存(No swap space and no temporary disk scratch space)
- 没有虚拟内存或硬盘做辅助缓存,内存不足时无法通过“换页”缓解,
- 内存分配必须非常谨慎。
5. 硬件升级不可行
- 不能通过加内存或更换设备解决问题,通常因为目标平台是固定的(如游戏机、嵌入式设备)。
6. 用户无升级选项(比如游戏主机)
- 最终用户可能没有办法升级硬件,只能靠软件优化。
总结
这描述的正是资源受限系统内存管理的现实,对软件设计提出了严格要求:
- 必须高效利用内存,避免浪费。
- 需要对内存分配和释放有精细控制。
- 不能依赖操作系统的虚拟内存机制。
- 代码必须考虑多种内存压力场景。
**计算时间(Computation Time)**方面的性能挑战,尤其是在游戏引擎和高性能软件中常见的问题。帮你拆解理解:
计算时间相关的挑战和考虑
1. 供应商库中调试迭代器的开销(Cost of debug iterators in vendor libraries)
- 一些标准库或第三方库在调试模式下会启用额外检查(如迭代器有效性检测),导致性能下降。
- 这种额外开销在性能敏感场景(游戏、实时系统)中不可接受。
2. 许多游戏引擎甚至替换 std::vector
- 标准库容器(如 std::vector)不是为极端性能调优设计,可能包含额外开销。
- 游戏引擎为了最大化性能,会用自己定制的容器实现,避免不必要的功能和检查。
3. 关闭不想要的“特性”需要不同的魔法操作
- 不同编译器和库实现,有各自关闭调试或安全检查的“秘技”(编译选项、宏定义等),难以统一管理。
4. dynamic_cast vs 自研反射系统
- C++ 的
dynamic_cast
运行时类型检查虽然方便,但性能开销明显。 - 很多性能敏感项目使用自己设计的反射机制来替代,以获得更快的类型识别。
5. 并非所有 O(log N) 操作都一样
- 虽然复杂度相同,但不同数据结构和实现的常数因子差异巨大。
- 比如:
boost::flat_map
是基于有序数组的实现,缓存友好,性能常数因子小。std::map
是基于红黑树的节点链表,指针跳转多,缓存效率低。
总结
- 性能优化不仅看算法复杂度,更关注实现细节和常数因子。
- 标准库在某些场景下不够轻量,可能成为性能瓶颈。
- 游戏等实时应用往往需要定制容器和反射机制来最大化效率。
- 关闭调试开销和避免不必要的运行时检查是关键。
内存分配模式不一致 带来的问题,特别是在不同容器实现、平台和编译器之间的差异,导致程序行为难以预测。帮你详细拆解理解:
不一致的内存分配模式(Inconsistent Allocation Patterns)
1. 容器实现差异(Container implementation differences)
- 不同标准库实现(libstdc++、MSVC STL、libc++ 等)对同一容器的内部细节有差异,尤其是内存分配策略。
2. 空容器是否分配内存?(Does an empty container allocate?)
- 有的实现中,即使容器是空的,也可能预先分配少量内存(比如小容量优化),
- 有的实现则完全不分配,直接保存空状态。
3. Vector 的增长率和初始容量?(Vector growth rate and initial capacity?)
- 不同实现的
std::vector
可能采用不同的增长因子(如 1.5 倍、2 倍等), - 初始容量也可能不同,这影响到内存分配次数和性能。
4. 小字符串和小对象优化?(Small string and small object optimizations?)
- 小字符串优化(SSO)是常见优化:短字符串直接存储在对象内部,无需堆分配。
- 不同库对 SSO 的实现不同,导致内存使用和性能表现差异。
5. 多大尺寸的函数对象会让 std::function 分配堆内存?(What size functor will require std::function to allocate?)
std::function
通常有内部缓冲区存放小函数对象,超过这个大小就需要动态分配堆内存,- 不同实现缓冲区大小不同,导致堆分配行为不一致。
6. 跨平台和不同 C++ 实现时行为难以预测(Behavior is unpredictable when porting between platforms and C++ implementations)
- 上述差异加上不同编译器、操作系统,程序在不同环境下的内存分配行为和性能表现可能完全不同,
- 给性能调优和内存管理带来挑战。
总结
- 容器和标准库的内部细节不是标准规定的,而是实现细节,因此会有差异。
- 这些差异影响内存分配行为,导致性能波动和不可预测的内存使用。
- 对于性能敏感或资源受限的系统,不能完全依赖默认实现,可能需要定制或深入了解实现细节。
如果你需要,我可以帮你: - 列出主流 STL 实现关于这些行为的差异;
- 给出规避不一致行为的实战建议;
- 或者写示例代码,展示如何检查和控制这些内存分配。
标准库及相关实现的细节和多样性,尤其是实际项目中对标准库功能的替代与定制。帮你拆解理解如下:
实现细节(Implementation Details)
1. std::async 是否使用线程池?
- 标准C++中
std::async
的行为由实现决定,标准没有强制要求必须用线程池。 - 有些实现直接创建新线程,有些可能用线程池来复用线程资源以提升性能和减少开销。
- 因此
std::async
在不同平台/库表现可能不同。
2. 标准库功能经常被重新实现
- 由于标准库在性能、内存使用和特性上不一定满足所有需求,许多大型项目会自己重写或改写部分容器和算法。
3. EASTL(Electronic Arts 标准模板库)
- EA针对游戏开发需求,设计了专门优化的标准库实现。
- 主要目标是高性能、低延迟、内存使用可控。
4. STLport(基于SGI STL的实现)
- 一个跨平台的STL实现,提供对旧系统的支持以及修复和扩展。
5. folly::FBVector(Facebook自定义std::vector)
- Facebook的folly库中针对性能和特定场景优化的容器。
- 通过定制实现,改善内存分配和访问效率。
6. llvm/ADT(LLVM的自定义容器)
- LLVM项目使用的抽象数据类型库,提供高效的容器和工具,专为编译器开发优化。
总结
- 标准库虽然方便且通用,但在高性能或特定场景下往往不够理想。
- 大公司和项目常常会基于标准库理念,自行实现定制版本以满足严格性能需求。
- 了解这些替代实现对深入掌握C++性能优化和工程实践很有帮助。
C++ 语言的一些运行时开销(es 指的是“执行开销 Execution Overhead”),帮你详细拆解理解:
C++ 运行时开销相关问题(Execution Overhead)
1. RTTI(Run-Time Type Information)
- 运行时类型信息机制,支持
dynamic_cast
和typeid
。 - 这种机制会引入额外数据结构,增加程序的二进制大小和运行时开销。
2. dynamic_cast 产生大量额外数据
dynamic_cast
依赖 RTTI,可能会导致额外的内存使用和运行时开销。- 如果广泛使用,会影响性能。
3. 虚函数(Virtual Functions)
- 虚函数通过虚表(vtable)实现多态,调用时需要间接跳转,影响性能。
- 但现代编译器优化较好,这一开销相对较小。
- 虚函数也会影响内联(Inlining),降低性能。
4. 这些问题虽然现在不如以前重要,但仍需关注
- 随着编译器优化进步,虚函数和 RTTI 的开销有所减少,
- 但在高性能或嵌入式环境中依然需要权衡。
5. C++ 抽象并非总是“零开销”(Not always zero-cost abstractions)
- 理论上,C++ 的抽象应在编译时消除,但实际中并不总是如此。
- 一些抽象会带来额外运行时负担。
6. 异常处理(Exceptions)
- 异常机制为了支持栈展开和资源清理,会阻止某些优化。
- 异常处理代码通常会带来额外体积和执行开销。
总结
- C++ 的某些特性(RTTI、虚函数、异常)会带来额外开销,
- 在性能敏感代码中应谨慎使用或寻找替代方案(如手写类型识别、自定义多态机制等)。
- 理解这些开销,有助于写出更高效的代码。
**异常(Exceptions)和运行时类型信息(RTTI)**在游戏开发等特定领域被禁用及其带来的影响。帮你拆解理解:
Exceptions & RTTI 在特定环境中的使用问题
1. 游戏开发中常用编译选项 -fno-exceptions
和 -fno-rtti
- 为了降低运行时开销和二进制大小,游戏通常禁用 C++ 的异常和 RTTI 支持。
- 这样能减少异常处理和类型识别相关的复杂代码。
2. 部分重要平台对异常支持不可靠或根本不支持
- 一些嵌入式设备、游戏主机或定制平台对异常机制支持有限,可能导致异常相关代码不可用。
3. 禁用异常和 RTTI 后,try/throw/dynamic_cast 的行为未定义
- C++ 标准没有明确规定当异常或 RTTI 被禁用时的行为,导致代码不可移植或不稳定。
4. 禁用异常/RTTI 通常导致编译错误,很多库不能直接用
- 许多第三方库默认使用异常和 RTTI,禁用它们会导致编译失败,
- 需要修改库源码或者绕开这些特性才能使用。
5. 这不仅仅是游戏行业的问题,也不是小众担忧
- 很多高性能或嵌入式开发环境也会遇到类似问题。
6. 参考资料
- LLVM 编码规范建议不使用 RTTI 和异常:LLVM Coding Standards
- Google C++ 编码指南关于异常的建议:Google C++ Style Guide - Exceptions
总结
- 禁用异常和 RTTI 是为了性能、代码大小和平台兼容性的权衡。
- 这带来代码兼容性和库支持的挑战,需要提前设计应对方案。
- 理解这种权衡有助于写出跨平台、高性能的 C++ 代码。
Memory Budgets(内存预算)
1. 内容创作者和生产团队的重点
- 这里的“内容创作者”指的是艺术家、设计师、发行、测试(QA)等非程序员角色。
- 他们是实际制作资源和内容的人,对资源消耗影响最大。
2. 让内容团队自己回答内存预算问题
- 程序员的时间宝贵且昂贵,理想情况下,应该让内容创作者能自己判断和控制他们制作的资源是否符合内存预算。
- 这样减少程序员在内存调优上的负担。
3. 在长时间测试中捕获内存统计数据,且不依赖昂贵/缓慢的工具
- 希望在比如3小时的测试运行中,能够实时、精准地收集内存使用情况。
- 这些统计必须高效,不能显著影响程序性能。
4. 需要细粒度的内存统计和预算控制
- 细粒度意味着能具体到某块资源、某个模块的内存消耗,方便定位和优化。
- 这样便于团队制定具体的内存预算和限制。
总结
- 内存预算管理不仅是程序员的责任,更应是内容制作团队的日常工作部分。
- 需要高效、准确且细粒度的内存使用数据反馈机制。
- 这样既节省程序员资源,又能保证内存控制目标得以实现。
这段示例代码和说明讲的是自定义内存分配器(Custom Allocators),通常用于大型游戏引擎或性能关键系统,以精细管理内存资源。帮你拆解理解:
Custom Allocators(自定义分配器)示例解析
1. 定义分配器宏:DEFINE_ALLOCATOR
- 伪代码定义了多个分配器,比如
MeshData
专门管理图形模型数据的内存,Enemies
专门管理敌人角色的数据。 - 这种命名和分组帮助团队清晰划分内存用途。
DEFINE_ALLOCATOR(MeshData, "Graphics/Meshes");
DEFINE_ALLOCATOR(Enemies, "GameObjects/Characters/NPCs/Enemies");
2. 类绑定分配器:USE_ALLOCATOR
VertexMesh
类用USE_ALLOCATOR(MeshData)
绑定到MeshData
分配器。- 这意味着
VertexMesh
对象的所有内存分配操作都会通过MeshData
分配器处理。
class VertexMesh {USE_ALLOCATOR(MeshData);
};
3. 运行时分配器绑定和资源加载
- 运行时,将某些游戏对象路径模式与特定分配器关联,方便动态管理。
- 例如,所有
"objects/enemies/*.json"
路径下的敌人对象都使用Enemies
分配器。 - 通过
LoadGameObject
加载资源时,会自动用相应分配器管理内存。
RegisterGameObjectAllocator("objects/enemies/*.json", Enemies());
GameObject* guard = LoadGameObject("objects/enemies/evil_guard.json");
理解点总结
- 分配器分类: 将不同类型的资源使用不同分配器分配,方便调试、统计和优化。
- 类与分配器绑定: 通过宏或其他机制,类的内存申请都经过指定分配器,避免内存混乱。
- 动态分配器绑定: 运行时根据资源路径或类型,选择对应分配器,灵活管理内存。
- 好处:
- 细粒度内存使用统计
- 优化内存布局和性能
- 便于定位内存问题
**游戏开发中内存分配接口(Allocation Interfaces)**的特点和挑战。帮你拆解理解:
Allocation Interfaces(内存分配接口)在游戏中的情况
1. 标准 C++ 分配器(Standard allocator)在游戏中很少用
- 标准库中的 allocator 接口设计偏通用且复杂,不完全符合游戏对性能和控制的需求。
2. 标准分配器接口不理想
- 接口缺乏内置对不同内存区域(memory regions)分配的统计和管理支持,游戏中通常需要明确区分内存区域(如纹理、动画、物理数据等)。
3. 自定义分配器对对齐(alignment)有内在需求
- SIMD(单指令多数据)操作需要特定对齐的内存,而许多平台的全局
new
/delete
并不保证对齐,导致性能下降。 - 游戏中的自定义分配器通常需要明确对齐规则,保证内存布局符合硬件要求。
4. 许多游戏使用更简单、专门的自定义分配器接口
- 这些接口通常更轻量、更专注于特定需求(如对齐、区域管理、统计),远比标准分配器接口灵活。
5. C++11 对分配器接口做了改进,但仍不完美
- C++11 标准改进了 allocator 模型,提升了灵活性,
- 但对 node-based 容器(如
std::map
)的 allocator 重新绑定机制依然复杂、令人困惑。
6. 分配器通常不关心它分配的具体类型
- 分配器只负责“给定大小和对齐要求的内存”,不需要知道这块内存用来存放什么。
- 这也适用于分配器自身对大小或对齐有严格限制的情况。
总结
- 游戏开发更倾向于使用专门设计、简单且高效的自定义内存分配器接口,而不是标准 C++ allocator。
- 自定义接口支持对齐要求、区域内存管理和精细统计,满足游戏性能和内存预算需求。
- 理解这些差异,有助于设计符合实际需求的内存管理系统。
性能(Performance) 在游戏或高性能软件中的关键因素和特点。帮你梳理理解:
Performance(性能)关键点解析
1. 某些硬件分支预测能力很差
- 分支预测是现代CPU提高执行效率的重要机制。
- 一些硬件(尤其是嵌入式设备或某些游戏主机)分支预测表现差,代码中频繁且不可预测的分支会导致严重性能下降。
2. 缓存局部性越来越关键
- CPU缓存是缩短访问内存延迟的关键,程序对缓存的友好程度(数据和指令的局部性)直接影响性能。
- 优化数据结构和算法以提升缓存命中率非常重要。
3. 桌面软件可接受的小效率,在游戏里不可接受
- 游戏等实时系统对性能要求极高,哪怕很小的低效也会导致明显体验下降。
- 所以游戏开发中对效率的容忍度极低。
4. 调试时性能依然重要
- 在开发和调试阶段,也不能完全牺牲性能,否则调试效率和体验都会受影响。
5. 内存使用和性能紧密耦合
- 访问内存越多、越分散,性能就越差。
- 好的内存布局和管理策略能显著提升性能。
6. 需要为真实硬件设计的算法和数据结构
- 理想中的数学算法很棒,但硬件架构在不断变化和演进,
- 算法和数据结构设计必须考虑当前和目标硬件特性,比如缓存层级、并行能力、内存带宽等。
7. 纯数学模型稳定,但硬件环境不断变化
- 数学本身不变,但硬件更新速度快,优化也需要跟上硬件发展。
- 因此持续关注硬件趋势,调整设计策略。
总结
- 性能优化是游戏和高性能软件的核心挑战之一。
- 需要关注硬件特点,尤其是缓存和分支预测。
- 不仅上线时,调试阶段也要重视性能。
- 内存管理和算法设计必须结合硬件特性才能达到最优。
标准库(STL)算法没覆盖的一些重要算法,尤其是在性能关键的领域:
Some missing algorithms(缺失的一些算法)
1. Radix Sort(基数排序)
- 为什么重要?
- 针对整数键排序非常高效。
- 对于整数类型的数据,基数排序可以远快于基于比较的
std::sort
,因为它不是比较排序,而是基于位操作或数字分布。
- 适用场景: 大批量整数排序,或者能映射为整数键的场景。
2. Spatial and geometric algorithms(空间和几何算法)
- 标准库没有内建的空间数据结构算法,比如:
- 四叉树、八叉树
- KD树
- 最近邻搜索
- 碰撞检测算法
- 这些对游戏开发、图形处理和物理计算非常关键。
3. Imprecise but faster math algorithms(不精确但更快的数学算法)
- 有些数学算法可以用近似算法替代,牺牲少量精度换取大幅性能提升。
- 例如:快速平方根估算(Fast inverse square root)、近似三角函数等。
- 这些算法在实时渲染和游戏物理中很有用。
总结
- 基数排序 是整数数据排序的“王者”,值得学习和使用。
- 游戏和图形领域常用的空间几何算法,标准库缺失,需要自行实现或使用第三方库。
- 牺牲部分精度换取速度的数学算法,在性能关键场景非常实用。
这段内容讲的是标准库中缺少的一些容器类型,特别是在游戏和高性能场景下常用但 STL 没有提供的容器。
Some missing containers(缺失的一些容器)
1. Intrusive linked list container(内嵌链表容器)
- 特点:链表节点本身包含链表指针(前驱、后继),不需要额外的内存分配。
- 优势:
- 减少动态内存分配,提升性能和内存使用效率。
- 支持静态初始化(常量数据链表)。
- 没有“self iterators”问题(迭代器失效少)。
- 应用:需要高效管理对象生命周期或对象间关系时。
2. Cache-friendly hash table(缓存友好型哈希表)
- 标准库的
std::unordered_map
通常基于链表冲突解决,指针跳转多,缓存局部性差。 - 游戏和高性能场景下更偏好开放寻址或线性探测等方式实现的哈希表,减少缓存未命中。
3. Contiguous containers(连续容器)
- 标准库中有
std::vector
、std::array
等连续容器,但有时游戏需要更特定优化版本(比如小容量优化版)。 - 连续容器方便 SIMD 优化、缓存优化。
4. Stack containers(栈容器)
- 有些场景需要在栈上分配容器(避免堆分配开销),比如小型固定容量数组或小型 vector。
- 这些容器在性能敏感代码中极其有用。
总结
- 游戏和高性能系统常用的容器往往不是标准库自带的,而是需要定制化:减少内存分配、提升缓存效率、支持栈分配等。
- 内嵌链表和缓存友好的哈希表是最典型的例子。
- 针对具体需求设计容器,才能最大化性能和内存利用。
边界最坏情况时间(Bounded Worst Case Time) 在游戏和实时应用中的重要性,特别是在性能和用户体验上的影响。
Bounded worst case time(有界最坏情况时间)解析
1. 最坏情况时间 vs 平均情况时间
- 平均情况时间:代码执行的平均速度,可能偶尔出现很慢的情况。
- 最坏情况时间:代码执行最慢时的耗时上限。
- 重要性:即使平均帧率很高,如果偶尔出现长时间卡顿(最坏情况差),会导致游戏体验极差。
2. 平稳的30fps > 抖动的60fps
- 一个持续稳定的30帧每秒(fps)体验,往往比时快时慢、时而达到60fps的体验更好。
- 抖动(jitter)会给玩家带来画面不连贯的感觉。
3. 对虚拟现实(VR)尤其重要
- VR对画面流畅度极其敏感。抖动不仅影响体验,更会引起晕动症(nausea)。
- 保持帧时间的稳定性是VR开发的一项核心要求。
4. 垃圾回收(GC)的权衡
- 垃圾回收机制通常会带来“停顿”或“卡顿”,导致最坏情况时间增长。
- 不同内存管理策略的权衡:
| 策略 | 吞吐量(Throughput) | 延迟(Latency) | 游戏表现 |
| ------------------ | --------------- | ----------- | ---- |
| 垃圾回收(GC) | 高 | 高 | 可能卡顿 |
| 引用计数(Ref Counting) | 低 | 低 | 更平稳 | - 高吞吐量,高延迟意味着总体效率好但偶尔卡顿明显。
- 低吞吐量,低延迟意味着性能稍逊,但更平滑,没有明显卡顿。
总结
- 最坏情况时间有界(bounded) 是实时应用和游戏的关键。
- 平稳、无抖动的帧率体验优于峰值高但不稳定。
- VR对这种稳定性要求更苛刻。
- 内存管理策略需权衡吞吐量和延迟,避免影响游戏流畅度。
编译时间过长(Long Compilation Times) 的问题及其根源,尤其是在 C++ 项目中的体现。
Long Compilation Times(长编译时间)解析
1. 模板和头文件膨胀(Template/include bloat)
- 模板代码(template)和大量包含的头文件会导致编译器处理大量代码,增加编译时间。
- 例如,
std::unique_ptr
、std::array
这类 STL 模板相比传统的裸指针和数组,头文件更大、依赖更多,导致编译变慢。
2. 标准库头文件庞大
- 以 VC14(Visual C++ 2015)为例,
<memory>
头文件代码超过 2000 行,且有大量依赖其他头文件,导致编译开销明显。 - 引入一个 STL 容器或智能指针,可能拉入整个库的大量代码。
3. “C with classes”风格代码编译更快
- 简单、非模板、少头文件依赖的代码风格(类似 C 语言加类)编译速度快。
- 不用过度依赖模板和复杂的库可以显著缩短编译时间。
4. 其他影响编译时间的因素
- 文件 I/O:编译器读取大量头文件和源文件。
- 复杂的语法和语法树分析。
- 模板实例化:模板参数的不同组合导致代码膨胀。
- 优化阶段:高级优化会显著增加编译时间。
总结
- C++ 项目编译慢很大程度是模板、头文件和 STL 大量代码引起的。
- 减少不必要的头文件包含、控制模板使用、使用预编译头等技巧可缓解。
- 有时选择简单的“C with classes”风格能大幅缩短编译时间。
长加载时间(Long Loading Times) 对开发效率和用户体验的影响,以及相关的解决思路。
Long Loading Times(长加载时间)解析
1. 长时间关卡加载影响生产力
- 游戏中的关卡、资源加载时间过长,导致开发和测试周期变长,降低工作效率。
- 频繁等待加载,打断开发流程,特别是在调试阶段更明显。
2. 调试模式下加载时间更长
- Debug 模式下,额外的检测和符号信息使加载更慢。
- 导致开发时反复测试和调试更费时。
3. 希望实现无加载屏迭代(Iterate without loading screens)
- 开发时希望能快速修改、测试内容,而无需每次都等待加载画面。
- 提升开发效率,缩短迭代周期。
4. C++ 的“编辑并继续”功能(Edit and Continue)
- IDE 支持在调试时修改代码,立刻生效,无需重启程序。
- 加快调试速度,但支持有限且复杂。
5. 集成在 IDE 或游戏引擎中的热重载
- 游戏引擎支持资源和代码的动态加载和替换,减少重启和重新加载时间。
6. 脚本语言的实时重载
- 使用 Lua、Scheme、Python 等脚本语言支持即时修改代码并运行,极大缩短迭代时间。
7. 着色器语言的实时重载
- HLSL、GLSL 着色器的实时热重载,允许快速调整视觉效果,无需重启游戏。
总结
- 长加载时间是游戏开发中的效率杀手。
- 通过“编辑并继续”、热重载机制、脚本和着色器动态加载,能显著提高迭代效率。
- 目标是实现无加载屏的快速开发体验。
定点数(Fixed-point Numbers) 的背景、动机和应用场景,特别是在 C++ 标准化和实际开发中的进展。
Fixed-point Numbers(定点数)解析
1. 领导者和参考项目
- 主要贡献者:Lawrence Crowl 和 John McFarlane。
- 相关项目和资料:
- John McFarlane 的 GitHub 项目 fixed_point
- N3352 提案文档
- Crowl 的论文《C++ Binary Fixed-Point Arithmetic》
2. 定点数的应用场景
- 缺少浮点单元(FPU)的平台:某些嵌入式系统或老旧硬件不支持硬件浮点运算,使用定点数更高效。
- 统一的数值精度:浮点数的有效位数随数值大小变化,而定点数提供恒定的分辨率。
- 如屏幕坐标的表示:图形和游戏中需要对位置进行统一且精确的量化。
3. 提议的接口
std::fixed_point<Repr, Exponent>
:一个固定表示法数值类型,模板参数控制表示方式和小数点位置。std::make_fixed<IntegerBits, FractionBits>
:工厂函数,方便构造指定整数位和小数位数的定点数。
4. 与浮点数的区别
- 浮点数在范围很大时有效位数变化大,精度不均匀。
- 定点数则保持固定的精度和分辨率,更适合某些实时和嵌入式应用。
总结
- 定点数是一种在某些平台和应用中优于浮点数的数值表示方法。
- C++ 标准化正在考虑将定点数纳入标准库,以便更好地支持这些场景。
- 了解定点数有助于针对硬件限制或特殊精度需求做出更优设计。
**环形缓冲区(Ring Buffer)**的概念、背景和用途,以及C++标准化的相关进展。
Ring Buffer(环形缓冲区)解析
1. 主导者与提案
- 主要贡献者:Guy Davidson
- 相关提案文档:Ring Buffer Proposal
- 标准化状态:已被 Kona(C++标准会议)批准
2. 基本概念
- 环形缓冲区是一个连续内存的FIFO(先进先出)队列,当尾部达到末尾时,可以回绕到起始位置继续写入,形成环状结构。
- 它保证了高效的连续内存访问,利于缓存和性能。
3. 示例用途
- 音频应用:连续地将音频采样数据送入DAC(数模转换器)播放。
- 网络应用:排队准备发送的数据包。
- 其它实时或高性能队列需求场景。
4. 提议的接口
std::static_ring<T, N>
:静态大小的环形缓冲区,缓冲区大小在编译时确定。std::dynamic_ring<T, std::vector<T>>
:动态大小的环形缓冲区,内部用std::vector
管理存储。
5. 对比:连续 vs 非连续
- 连续环形缓冲区(图中示意1-4连续循环)
- 优点:缓存友好,内存局部性好。
- 缺点:大小固定或者动态调整复杂。
- 链表(非连续)
- 优点:灵活动态,容易扩容。
- 缺点:内存分散,缓存命中率低,效率低。
总结
- 环形缓冲区提供高效、连续内存的FIFO数据结构,适合对性能和延迟敏感的场景。
- C++正在标准化静态和动态版本的环形缓冲区,方便开发者直接使用。
- 适用音频流、网络包队列、生产者消费者模型等多种场合。
Extended Memory Operations and Algorithms(扩展的内存操作和算法) 提案的背景、内容和目标,尤其是针对高性能自定义容器的支持。
Extended Memory Operations and Algorithms 解析
1. 贡献者与提案状态
- 主要贡献者:Brent Friedman
- 相关提案均已获 Kona(C++标准会议)批准
- 相关提案文档:
- raw_storage_iterator 改进
- uninitialized 操作
- unstable_remove 提案
2. 背景和目标
- 当前C++标准库支持的内存和构造/析构操作有限,尤其在编写自定义高性能容器时不够灵活和高效。
- 该提案旨在提供更多底层操作支持,比如更高效的内存构造、移动、销毁等,减少冗余和性能损失。
3. 具体内容
std::raw_storage_iterator
改进- 使其支持移动语义(move)和原地构造(emplace),提升容器元素构造效率。
- 新函数引入
std::destroy
:销毁范围内的对象,调用析构函数。std::uninitialized_move
:在未初始化内存中原地移动对象。std::uninitialized_value_construct
:在未初始化内存中构造对象并初始化为默认值(类似值初始化)。std::uninitialized_default_construct
:在未初始化内存中进行默认构造。
std::unstable_remove
- 类似于
std::remove
,但不保证保持元素的相对顺序,因此能提供更高的性能(通过交换和直接覆盖实现)。
- 类似于
总结
- 该提案通过增强底层内存操作接口,给开发者更多高效构建自定义容器和算法的工具。
- 支持更灵活、更高性能的内存构造、移动和销毁操作。
unstable_remove
通过放弃稳定性换取性能,适合不关心元素顺序的场景。
“Flat” Associative Containers(扁平化关联容器) 提案,核心思想和优势如下:
“Flat” Associative Containers 解析
1. 主要贡献者与提案链接
- 贡献者:Sean Middleditch
- 相关文档:flat_containers.md
- 已通过 Kona(C++标准会议)批准
2. 核心理念
- 扁平化(Flat):容器元素在内存中连续存储(contiguous memory),类似
std::vector
的内存布局。 - 缓存友好:由于数据是紧密存储,CPU 缓存命中率更高,访问速度更快。
3. 实现细节
- 关联容器实现采用排序数组 + 二分查找。
- 提供与传统关联容器(如
std::set
、std::map
)类似的接口(查找、插入、删除等)。 - 通过调用类似
std::lower_bound
的算法实现快速查找。
4. 对比传统关联容器
特性 | 传统关联容器 (std::set ) | 扁平化关联容器 (flat_set ) |
---|---|---|
内存布局 | 节点链表(分散内存) | 连续内存(数组或向量) |
查找方式 | 树结构(如红黑树) | 二分查找 |
缓存友好性 | 差(节点指针跳转,缓存不友好) | 好(连续内存,缓存命中率高) |
插入/删除性能 | 较稳定,O(log n) | 插入删除涉及移动元素,平均开销较高,适合读多写少的场景 |
典型使用场景 | 频繁修改的集合 | 读多写少,查询性能要求高的场景 |
形象对比(图示)
- 扁平化容器示意(连续内存):
3 4 6 7
^ ^
| |
start end
- 传统 std::set 示意(节点分散):
9/ \7 4/ \3 6
节点在内存中分散,访问需要多次跳转。
总结
- 扁平化关联容器通过利用连续内存和二分查找,获得更好的缓存性能和查找速度。
- 特别适合查询多、修改少的场景(例如配置查找、只读数据集等)。
- 是对传统基于节点的关联容器的有效补充。
关于线程安全的 STL(标准模板库)改进的早期探索,关键点如下:
Thread-safe STL(线程安全 STL)解析
1. 贡献者与背景
- 由 Brett Searles 提出早期草案
- 目标是让 STL 更好地支持并发编程
- 鼓励继续完善以便在标准化工作组中深入讨论
2. 背景动因
- 现代软件尤其是游戏、服务器、并发应用广泛使用多线程
- STL 容器和算法在多线程场景下安全性有限
- 标准库目前大多数操作本身不是线程安全的(除非用户自己加锁)
3. 探索内容
- 研究如何设计线程安全的 STL 容器和算法
- 可能引入内部同步机制,或者提供可配置的同步策略
- 目标是减少用户手动加锁的复杂性,提高并发编程的安全性和效率
4. 当前状态
- 仅为早期草案
- 尚未形成具体标准或实现
- 需要进一步的设计、讨论和实践验证
总结
这表明 C++ 标准化组织对提升 STL 在多线程环境中的适应性非常关注,期望未来能提供更好的原生支持,减少程序员在多线程使用 STL 时的负担和错误风险。
**线程栈大小(Thread Stack Size)**的问题和改进提案,重点如下:
Thread Stack Size(线程栈大小)理解
1. 提案背景
- 提案人:Patrice Roy
- 参考链接:
http://h-deb.clg.qc.ca/WG21/SG14/thread_ctor_stack_size.pdf
2. 问题描述
- 在嵌入式系统和游戏开发中,线程栈大小必须可控,因为内存资源有限且预算紧张
- 当前 C++ 标准线程库
std::thread
在创建线程时,无法指定栈大小 - 而且线程创建后,栈大小也不能修改
std::thread::native_handle()
只能访问底层线程句柄,但不能解决栈大小设置问题
3. 提案内容
- 允许在创建线程时精确指定线程栈大小,不仅仅是给出建议的最小值
- 这对嵌入式系统尤为重要,因为它们的内存预算非常严格,需要精细控制内存使用
- 可能成为标准库新特性,为多平台提供统一接口
4. 预期进展
- Patrice Roy 可能会在C++ Kona会议上亲自做介绍和推动该提案
总结
这项提案针对实际开发中对内存控制的严格需求,尝试让 C++ 标准线程库更灵活,更适合嵌入式和游戏领域的使用场景。
这段内容讲的是异常处理(Exception Handling)成本分析的初步调研和讨论,重点如下:
Exception Cost Analysis(异常成本分析)理解
1. 报告者
- Patrice Roy 提出初步且粗略的异常处理开销调研结果
2. 内容
- 研究了现代编译器(如 GCC、Clang、MSVC)中异常处理机制的成本
- 讨论了异常处理在性能和代码大小上的影响
3. 讨论焦点
- SG14 邮件列表中进行了讨论
- 主要探讨异常处理的成本是否真的对 C++ 社区产生了显著负面影响
- 是否需要进行标准或实现层面的改进以减轻异常开销
4. 状态
- 仍处于非常早期的头脑风暴阶段
- 尚无具体方案,只是提出问题并开始探讨可能的解决思路
总结
异常处理虽然提供了强大的错误处理能力,但它带来的性能成本可能在某些场景(如游戏、嵌入式)不被接受,这项分析尝试量化这个成本并促使社区思考如何改进。
与性能相关的 C++ 标准扩展提案(Related Work) 的一个快速综述,主要集中在 错误处理、协程、SIMD(单指令多数据) 等方面,下面逐项进行解读:
Coroutines(协程)
- 提案编号 N4499
- 作者:Gor Nishanov 和 Daveed Vandevoorde
- 协程是 C++20 引入的语言特性,用于简化异步代码编写,比如网络、游戏脚本、UI 更新等。
- 协程不像线程,它是 编译器驱动的状态机,不涉及上下文切换,性能开销低。
noexcept library additions
- 在库接口层面明确声明哪些函数不会抛出异常(
noexcept
)。 - 有助于优化,例如更好的内联、移除异常相关的元信息。
- 特别适用于性能敏感的场景(如游戏、嵌入式)。
Use std::error_code
for signaling errors
- 作为
throw
的替代方案。 std::error_code
是一种轻量级错误报告机制,可以用于返回值检查,无需异常机制支持。- 广泛用于系统级编程(如文件系统、网络),更 predictable、更便于控制内存/性能。
Early SIMD in C++ investigation
- SIMD(Single Instruction, Multiple Data)是高性能并行计算的基础。
- 提到一些早期 C++ 中关于 SIMD 的草案建议,例如:
Vec<T, N>
:一种固定宽度的矢量类型for simd (;;)
:提议添加 SIMD-aware 的循环语法
- 当前这些提案尚未在 SG14(高性能/游戏/嵌入式 C++ 工作组)中正式推进
总结
这些相关工作都指向一个目标:
在不牺牲可读性和安全性的前提下,将 C++ 打造成更高效、更可控的系统编程语言。
它们分别在:
- 协程(异步简化)
- 错误处理(低成本替代异常)
- SIMD(向量化加速)
等方面做出了探索。
总结了 SG14(高性能与嵌入式 C++ 工作组)当前的一些正在进行中的工作方向。它列出了几个正在酝酿但尚未形成完整提案的项目,计划在正式编写立场文档(position paper)之前进一步讨论。下面是对每项内容的详细理解:
plf::colony 和 plf::stack(由 Matthew Bentley 提出)
plf::colony
是一种替代std::vector
/std::list
的容器:- 为了避免插入/删除带来的迭代器失效
- 适合频繁插入删除且对遍历性能有要求的场景
- 使用 block-based 分配策略,提高缓存友好性
plf::stack
是一个高性能堆栈容器,类似std::stack
但更节省内存、更快
目标:提供在游戏开发和高性能环境中更适合的容器类型。
Intrusive Containers(由 Hal Finkel 提出)
- “侵入式容器”是一种容器,它要求元素自身持有链接信息(如指针),比如
boost::intrusive::list
。 - 优势:
- 零内存分配:没有节点封装的额外开销
- 性能极高:完全控制内存布局,便于 cache 优化
- 常用于游戏、内核、驱动等不允许频繁动态分配的场合。
GPU/Accelerator Support(由 Michael Wong 和 Nicolas Guillemot 研究)
- 探索将 C++ 标准容器和算法扩展到可以直接支持 GPU 和其他加速器(如 AI 加速芯片、FPGA)。
- 可能目标:
- 统一 CPU/GPU 编程模型
- 改进容器以便在 GPU 上使用(如可在 CUDA 或 OpenCL 下使用的 STL)
- 与 SYCL(Khronos 推出的基于 C++ 的并行计算标准)方向一致。
总结:
项目 | 目标 | 适用场景 |
---|---|---|
plf::colony / plf::stack | 高效容器替代 | 游戏、实时系统 |
Intrusive Containers | 零分配、高性能容器 | 内核、嵌入式 |
GPU/Accelerator 支持 | STL 能在 GPU 上运行 | 通用计算、AI、图形渲染 |
SG14(C++ 高性能/嵌入式方向工作组)对未来 C++ 发展的方向与关注点。它表达了对性能优化、更严格容器行为、更小编译负担等方面的诉求。下面逐条解释:
未来发展方向解读(Future Directions)
1. 新的容器和算法
- 推动将更适合高性能场景的容器和算法加入标准库。
- 示例:
radix sort
(基数排序)比std::sort
在整数排序上更快。 - 示例容器:
flat_map
(扁平映射)、ring_buffer
(环形缓冲区)、plf::colony
(高效插删容器)等。
- 示例:
2. 对已有容器和算法加更严格限制
a. 默认构造的标准容器不允许内存分配
- 当前标准容器(如
std::vector
)默认构造可能会预分配内存。 - 对嵌入式/游戏等场景不友好,浪费宝贵资源。
- 提议:让
vector()
构造时不分配任何内存,直到push_back()
才分配。
b. 标准类型的 move 操作必须 noexcept
std::move()
默认不意味着 “不抛异常”。- 如果
move
不是noexcept
,很多算法(如std::vector::emplace_back
)会退回使用copy
。 - 要求:默认类型在移动时保证不抛异常,以提升性能并启用更多优化。
3. 其他杂项问题(Miscellaneous issues)
a. 拆分 bloated headers,比如 <algorithm>
<algorithm>
文件巨大,引入几十甚至上百个模板函数。- 编译器处理非常慢(尤其在 debug 模式下)。
- 建议:将其拆分为更细粒度的头文件,例如
<find>
,<sort>
,<partition>
等。
b. 研究异常(Exceptions)和 RTTI 带来的性能开销与替代方案
- 当前标准没有提供关闭异常/RTTI 的官方机制。
- 实际上很多游戏/嵌入式团队都用
-fno-exceptions
、-fno-rtti
。 - 建议:研究标准内更明确的方式关闭这些功能,并处理相关类型行为。
总结
项目 | 背后动机 | 谁会受益 |
---|---|---|
新容器/算法 | 更适合现代硬件、更高性能 | 游戏、嵌入式、金融系统 |
严格容器行为 | 更可控内存、更少隐性代价 | 嵌入式、低内存设备 |
noexcept move | 启用更多优化 | 所有性能导向开发者 |
拆分头文件 | 加快编译 | 所有 C++ 项目 |
异常/RTTI 开销研究 | 提供轻量替代 | 高性能场景如游戏引擎 |