网易互娱-游戏研发-C++ 二面凉经

1. 自我介绍

2. 为什么会做ChatSDK?大模型流式与非流式传输的原理是什么?为什么用 SSE?

做 ChatSDK 是为了封装与大模型后端的交互逻辑,提供统一的、稳定的接口,方便上层业务(如辅助写代码插件)直接调用。非流式传输:客户端发出请求,服务端将完整的回答全部生成完毕后再一次性返回。等待时间长,体验极差。流式传输(基于 SSE 协议):SSE (Server-Sent Events) 是一种基于 HTTP 协议的单向长连接技术。它可以让服务端按 chunk 块不断向客户端推送生成的 token 数据(Content-Type: text/event-stream)。相比于 WebSocket,SSE 更加轻量,且天然契合 LLM 这种单纯由服务端单向输出文本流的场景。

3. 时间轮算法实现?如果要实现小数的定时任务怎么办?

时间轮的核心是利用环形数组和指针,指针以固定频率(如 1ms)向前扫描。任务根据延迟时间计算出槽位并插入对应的链表中。如果要实现小数精度(例如 0.5ms)的定时任务,有两种解决方案:

  1. 提升时间轮的分辨率(刻度):将基础 Tick 从 1ms 降低到 0.1ms,但这样会导致空槽过多,消耗更多的 CPU 轮询资源。
  2. 多层时间轮(分层设计):模仿水表或钟表机制。底层时间轮的精度设为 0.1ms,上层时间轮的精度设为 1ms。高精度的小数任务放在底层,大颗粒度任务放在上层,上层指针走完一格时,将任务降维映射到底层时间轮去精确执行。

4. 右值引用与 std::forward 的作用?

右值引用(&&)用于延长临时对象的生命周期,配合移动语义(Move Semantics),避免大对象的深拷贝。std::forward 用于实现完美转发。在模板函数传参时,参数类型可能会因为值传递或折叠规则退化为左值(即便传入的是右值)。std::forward<T> 可以根据模板类型推导,保持参数原本的左值或右值属性,再原封不动地传递给下一层函数。

5. 如何使用 C++ 实现 Python 的 dict?

Python 的 dict 是一个键可以是任意哈希类型、值也可以是任意类型的数据结构。要在 C++ 中实现,核心是结合 std::unordered_map 和类型擦除工具(如 std::any 或 std::variant):

#include <unordered_map>
#include <string>
#include <any>

class PyDict {
private:
    std::unordered_map<std::string, std::any> data;
public:
    template<typename T>
    void set(const std::string& key, T&& value) {
        data[key] = std::forward<T>(value);
    }

    template<typename T>
    T get(const std::string& key) {
        return std::any_cast<T>(data[key]);
    }
};

6. std::any 的底层原理是什么?

std::any 的本质是一种安全的“类型擦除”。它的底层通过两个核心机制实现:

  1. 模板构造函数:接收任意类型,利用 typeid 记录下原始的 std::type_info。
  2. 多态与内部虚函数表 / SBO(小对象优化):内部包含一个类似 void* 的存储空间和一系列操作函数指针。对于小体积对象,存放在对象自己分配的静态字符数组(Stack 内存)内;对于大对象,则通过 new 在堆上分配。取值时通过 std::any_cast 在运行时进行强类型检查,若类型不匹配抛出异常。

7. std::variant 和 std::any 有什么区别?什么场景下应该优先使用 std::variant?

std::any 可以装载完全未知的任意类型,是一种无界的类型擦除;而 std::variant 是一种类型安全的联合体(Union),它装载的类型必须在编译期于模板参数中固定(如 std::variant<int, string, double>)。使用场景:如果明确知道变量只会在特定的几个类型之间切换,应该优先用 std::variant,它分配固定大小的内存(取决于最大的成员),不需要动态分配内存,性能更高,且可以使用 std::visit 提供极其优雅的编译期模式匹配。

8. 请简述 tcmalloc 的核心设计思想以及它如何解决内存碎片和高并发锁竞争的?

传统 malloc 在多线程环境下竞争同一把锁,导致性能下降。TCMalloc (Thread-Caching Malloc) 采用分层内存管理机制:

  1. Thread Cache:每个线程维护一个无锁的缓存池,小对象(< 256KB)的分配和释放直接在当前线程本地完成,彻底消除了锁竞争。
  2. Central Cache:当线程缓存耗尽或回收对象过多时,向中心缓存申请或释放批量。中心缓存依然管理一定规模的对象跨度池,使用自旋锁控制。
  3. Page Heap:管理系统大块内存(页),以页(通常 8KB)为单位向操作系统申请。通过自由链表合并相邻的空闲页,有效减缓外部内存碎片。

9. 在 1G物理内存的机器上,申请 2G 空间的问题?

在 Linux 中是可以申请成功的。系统给进程分配的是虚拟内存。只要没有触发写操作(Page Fault),此时并不真正占用物理内存,malloc 会成功返回。当实际写入的数据超过 1G 时,Linux 会通过 OOM(Out Of Memory)Killer 机制杀死该进程。为了缓解此问题,项目中通常会启用 Swap 交换空间,将暂时不活跃的内存页置换到磁盘上,从而支持超出物理内存的分配需求,但代价是极高的 I/O 延迟。

10. Protobuf 的兼容性处理原则与不适用的场景是什么?

兼容原则:向前和向后兼容的核心是字段标签(Tag),绝不能改变已有字段的 Tag 编号。新增字段必须加上

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++ 常考面试题总结 文章被收录于专栏

本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务