求职经验


一、 成就故事与项目复盘 (STAR + 深度思考)

这部分要求用结构化的方式讲述一个关键项目或成就。

  • 背景 (Situation)

    • 边缘还是核心:说明该项目在公司/团队战略中的位置。是核心业务、关键产品,还是一个边缘尝试、技术预研?
    • 为什么做 (Why):项目的初衷是什么?是解决业务瓶颈、探索新技术、响应市场变化,还是应对竞争压力?
  • 任务与行动 (Task & Action)

    • 做了什么 (What):清晰描述您在项目中承担的具体职责和交付成果。
    • 怎么做 (How):详细阐述您的技术方案、决策过程、遇到的挑战以及如何克服。这是体现您技术深度和解决问题能力的关键。
      • 核心挑战:项目中最大的技术或非技术难题是什么?(如性能瓶颈、架构缺陷、团队协作问题)
      • 技术选型:为什么选择特定的技术栈或架构?(如选择CUDA进行并行计算,选择特定的设计模式)
      • 关键决策:在关键时刻做出了哪些重要决策?
  • 结果与影响 (Result & Impact)

    • 做完后的结果 (Result):用量化指标说明成果。(如:性能提升X倍,成本降低Y%,用户增长Z%)
    • 影响范围 (Impact):这个成果影响了多大的范围?(如:影响了整个产品线、为公司节省了数百万成本、成为行业标杆)
    • 复盘 (Review)
      • 如果再来一次还做不做这个决策?为什么?:反思决策的得失。是成功的经验,还是有值得改进的地方?
      • 如何改进?更好的方案?:基于当前的认知,提出更优的解决方案或流程。

二、 个人价值观与工作风格

  • 核心价值观

    • 诚信 (Integrity):在工作中如何体现诚实、透明和责任感?
    • 沟通 (Communication):如何与不同角色(技术、产品、上级、下属)有效沟通?如何确保信息准确传递?
    • 逻辑 (Logic):在分析问题、做决策时如何体现严谨的逻辑思维?
    • 成就动机 (Achievement Motivation):驱动您追求卓越的内在动力是什么?您如何设定和追求目标?
  • 个人特质与经历

    • 擅长且喜欢的:您最擅长的技术领域或工作内容是什么?为什么喜欢它?
    • 克服的困难与挑战:分享一个您成功克服重大困难的经历。如何克服?如何解决?
    • 工作习惯:例如,“几点到公司”可能是在考察您的工作投入度和自律性(但需注意,这不应是评判优秀员工的唯一标准)。
    • 主动改变现状的例子:是否曾主动发现并推动解决了公司/团队的某个问题或流程缺陷?体现了您的主人翁精神和主动性。

三、 成长、反思与职业规划

  • 过去一年的收获与认知改变:您在过去一年中学到了什么?对技术、业务或管理的理解有何深化?
  • 后悔:是否有令您后悔的决定或错过的机会?从中学到了什么?
  • 为什么离职:(如果适用)清晰、专业地阐述离职原因,聚焦于职业发展、寻求新挑战等积极因素,避免抱怨前公司。
  • 人生的一一使命:您的人生或职业使命是什么?这体现了您的长期愿景和价值观。

四、 团队管理与领导力 (针对管理岗位)

  • 如何处理难对付的员工:考察您的冲突解决和人员管理能力。强调沟通、理解、设定明确期望和绩效管理。
  • 如何提高产出,挤出评估水分
    • 提高产出:通过目标设定(OKR)、流程优化、赋能团队、技术改进等方式。
    • 挤出评估水分:建立客观的评估标准,进行同行评审(Code Review),使用数据驱动的度量(如代码质量、线上稳定性、业务指标),避免“唯工时论”。
  • 改变躺平心态:如何激励团队成员?通过设定有挑战性的目标、提供成长机会、认可贡献、营造积极的团队文化。
  • 团队如何招人:您的招聘理念和流程是什么?如何评估候选人?
  • 团队成员北京:可能是在确认团队的地理分布或您对异地协作的看法。
  • 喜欢用什么样的人:您欣赏什么样的人才特质?(如:自驱力强、学习能力强、协作性好、技术扎实)

五、 技术基本功

  • 项目匹配:您的技术栈和经验如何与目标职位的要求相匹配?

  • 理解与表达:能否清晰、准确地解释复杂的技术概念?

  • 潜力:评估候选人的长期发展潜力。

    • 自驱力 (Self-Drive)
    • 学习能力 (Learning Ability)
    • 适应能力 (Adaptability)
    • 协作能力 (Collaboration)
  • 具体技术领域

    • C++:内存管理、性能优化、指针、模板、多线程、设计模式。
    • 算法与数据结构:排序、图论、动态规划(DP)。
    • 并行计算:CUDA编程。
    • 多线程:并发控制、同步机制。

ailab高性能算子-1面
项目拷打1,重点和部署量化流程和gridsample算子的优化以及算子的底层定义
项目拷打234,重点是模型结构拷打
介绍一下访存密集型算子
介绍一下fp32fp16int8是怎么存储的
介绍一下量化原理以及过程
介绍一下gpu和cpu的结构以及适合计算什
udp和tcp协议适合什么各自的优缺点
linux查找磁盘使用指令
算法:链表判断有环➕cuda实现向量加

ailab2面
项目拷打重点同上
介绍一下锁的概念
介绍一下交叉熵的理解
介绍一下cuda编程的并行性和并发性
介绍一下gan和cvae
手撕一下svm
手撕一下transformer

沐曦集成—ai系统架构
项目拷打重点同上
介绍一下cuda编程的并行性和并发性
介绍一下c++编程的三大特性
介绍一下map和unorderedmap底层实现
介绍一下new,malloc,智能指针
介绍一下new,malloc的底层原理
介绍一下lambda
手撕一下transformer
算法:两数之和还有一个是hash具体啥忘了

作者:yunqian_
链接:https://www.nowcoder.com/feed/main/detail/c77ef218ce5d40d281c1aea6c906de1c
来源:牛客网

平头哥芯软暑期面经

https://www.nowcoder.com/feed/main/detail/28f1631501364f319bb383804dd6ca6c?sourceSSR=enterprise

https://www.nowcoder.com/discuss/744658461929062400?sourceSSR=enterprise

1.问了一些C++基础,传参方式,结构体

2.问了项目中DMA传输,如果数据量过大怎么处理(我答开辟两块缓存区,取一块,存一块)

3.中断处理函数为什么不能有复杂操作?

4.中断处理流程是怎样的?(我以为要答处理的过程描述,但面试官希望说出一个中断处理的寄存器和具体的函数怎么变化的)

5.代码题,写一个矩形结构体,判断两个矩形是否相交,不考虑旋转坐标系

6.实现生产者-消费者模型的数据读取与存储

2、你在项目中做了什么工作
3、cuda编程模型以及cuda编程时需要注意的优化点
4、pytorch分布式是怎么做的
5、分布式并行训练性能优化需要考虑哪些东西
6、手撕linux文件名解析,不准使用stl库,我的做法是逐char分析,但面试官最后的意思是说可以自己做一个string类出来
7、遇到困难怎么办

2、对自己的评价
3、周围人对自己的评价
4、遇到的最大困难是什么
5、是怎么解决这个困难的
6、成就感最大的事情
7、一天的时间是怎么安排的
8、自己的职业规划
9、你对平头哥有什么了解
10、手上有几个offer
11、反问

根据简历一行一行地挖

第一部分是哈工大OSLAB的linux0.1,因为这部分的内容实在是太复杂,我只记得一些大概。
(1) 进程调度函数的实现细节(栈切换,时间片轮转,就绪队列,阻塞列表,优先级调度)
(2) 时间片轮转调度是怎么实现的(定时器中断)
(3) 优先级怎么实现(count = count / 2 + priority, priority越大,分配的时间片越多,每次调度挑出count最大的运行)
(4) 共享内存的实现细节(申请一页内核空间内存然后映射到用户态,然后内核中用一个共享内存结构体来控制,用户态通过shmid来找到对应的内存区域然后映射)
(5) 信号量在内核里面是什么: (核心的部分好像就是一个计数,然后顺便说了生产者消费者模型,以及为什么要把这么一个东西放进共享内存)
(6) 你在写这个东西的时候,遇到了什么具体的问题,怎么解决的?(写fork函数的时候少写了一些东西导致init进程没法正常创建别的进程,说实话,最后抄了别人的代码)
(7) 内核崩溃,gdb怎么调试(core文件)
(8) core文件有时不是第一手现场,怎么查看程序越界到了什么内存,怎么办(不记得了,答了查看CPU现场,段寄存器,偏移地址,堆栈寄存器这些)

然后第二部分是freeRTOS的项目
(1) 你的项目实时性要求如何?怎么保证实时性(实时控制任务,要求非常高,使用多级反馈队列,设置四个优先级,高优先级的任务就绪立即抢占低优先级任务)
(2) 自旋锁有用过吗(没有,但我知道这个东西是什么,顺便讲了一下)

第三部分是实习相关的项目, 只介绍了一下具体的工作。

自我介绍
拷打第一个项目
项目背景 项目结构
负责的工作是哪部分?
如何优化你这个项目

程序

程序访问虚拟地址的过程:tlb→ mmu→各级页表 →各级cache→内存→磁盘(换页)
程序的动态加载
如何给程序动态分配内存:管理堆区,brk()函数,内存池管理碎片

操作系统中断的过程

CPU

cpu流水线
有没有做过优化相关的工作

C++怎么多线程编程
C++多线程如何同步

AI

transformer的结构,是怎么计算的?
QKV分别是怎么计算的? 

反问环节
面试官先主动把业务介绍了一遍,然后问我有没有什么问题(好感大增)
1. 实习生培养:每个人分配一个师兄带,不懂的地方可以随时问  
2. 加班情况:不强制加班,实习生一般是六点
3. 转正名额:基本上都能转正
4. 技术栈建议:多了解一下内存管理方面的知识
5. 后续流程:归hr管,他也不清楚

笔试环节
一共两道题,难度中等,在阿里的平台上敲代码,不需要运行,也不需要开摄像头或者共享屏幕,但面试官可以看到你的实时输入情况,大概四十分钟

二面
面试官上来先自我介绍,然后让我也自我介绍一下;可能是第一个面试官留了面评,对于我第一个项目(岗位相关)并没有深挖
项目背景 项目结构 我的贡献 老一套,然后又问了一下对TLB和cache,流水线的了解
接着疯狂拷打我的大模型项目
为什么要用大模型做这个项目?比起传统模型好在哪?
数据处理是怎么做的?
大模型对数字的处理?
输入和输出分别是什么?
RAG部分是怎么做的?
怎么样去构建RAG的向量数据库?
数据是怎么清洗的?
faiss用了什么方法去构建索引?
在查找的时候判断相似度的依据是什么?
faiss用的查找向量的维度是多少?(问得太细了,完全记不清,只能老老实实说忘记了)
用的大模型是什么?为什么不用更新的大模型?
用的什么微调方法?
模型的参数量有多少?

还记不记得你昨天做的两道题,说一下你做的思路,有没有遗漏的地方?

反问环节
1. 部门的业务:不能说,范围很广,基本上不涉及操作系统,都是在linux上做的
2. 实习生培养计划:虽然问过了但是没话找话
3. 技术栈建议:说了一大圈,没有特别重点
4. 后续流程:说可能还有一轮大老板面(实际没有)

面试过程感觉没答出来的点也很多,表现得一般,但是没想到面完一小时就收到hr电话,说二面也通过了,约下一轮面试,并表示是hr面试,没有大老板面(听完差点没爽死,原本还发愁大老板面该怎么过)hr面试通过的话就是排序等offer了

hr面试
聊了一下学习和生活,未来规划等等,很正常的hr面试
反问多久出结果,hr表示4月中会提交一批

2.20 部门A一面

1.自我介绍

2.实习拷打

3.八股

      1.讲讲深拷贝和浅拷贝

      2.写代码的时候咋深拷贝,咋浅拷贝

      3.trycatchfinally都是干啥的

      4.try里面有return的话,执行过程是怎么样的

      5.多线程的时候都会自定义线程池,讲讲线程池的原理

      6.线程池是什么模式的体现,如何实现生产者和消费者的解耦的,讲讲你的理解

      7.解释解释CAS

      8.你比较比较你见过的垃圾回收器

      9.spring的ioc和aop如何理解

      10.autwied和resource有什么区别

      11.循环依赖spring如何解决的,spring能解决所有循环依赖吗,如果遇到解决不了的怎么办

      12.linux你输入一个命令后发生了什么

      13.你打开一个url后涉及到了什么协议

      14.你打开一个url后过程简要讲讲

4.算法

       口述K个有序链表合并

2.20 部门B一面

1.自我介绍

2.基础

       1.进程同步方式

       2.线程同步方式

       3.信号量展开讲讲

       4.消息队列展开讲讲

       5.wait发生了什么过程

       6.线程上下文切换过程

       7.用户态内核态切换时间多长

       8.用户态内核态切换中间发生了什么过程

       9.协程和线程哪个效率更高

       10.协程上下文切换过程

       11.线程上下文保存了什么,协程上下文保存了什么

       12.cpu时间片每个多大

       13.cpu时间片是怎么淘汰的,不同线程获取cpu时间片的竞争过程展开讲讲

       14.线程切换要多久,协程切换多久,进程切换多久

       15.内存访问一次要多久时间

Cpp 八股

  1. C++ 11 新特性

  2. RAII 原则

  3. 深拷贝、浅拷贝,分别应用场景

  4. C++ 20 协程简单讲讲,无栈协程、有栈协程

  5. C++ 虚函数,使用场景,继承时,析构函数设定成虚函数。

  6. C++ 三大特性,动态多态和静态多态。

  7. 多继承的问题,模版特化和偏特化。

  8. 说一下线程和进程的区别,线程之间共享内容,进程和线程通信。

  9. 静态库和动态库的原理,符号表,单例模式。

  10. 快速排序,归并排序,堆排序时间复杂度一样,结合 CPU 的缓存访问来看,哪个效率最高?

  11. 代码和文件较多时,如何定位内存错误的问题?

  12. 编译器怎么对除法优化

  13. 链表/数组插入时间复杂度

  14. 堆分配/栈分配

  15. 静态存储区内存分配与堆内存分配稳定性对比

  16. 内存碎片

  17. malloc 底层实现方式

  18. 构造函数初始化列表与构造函数体内复制的区别

  19. 构造函数是否可以放到 private 里面

  20. 构造函数和析构函数是否可以为虚函数

  21. 说明 static 的原理

  22. 介绍 stl 里面的标准库 vector

  23. 智能指针介绍

  24. 如果智能指针放到多线程中如何完成访问共享的对象

  25. 讲解一下动态绑定和静态绑定

  26. 介绍多态

  27. 构造函数为什么不可以为虚函数

  28. 析构函数为什么可以是虚函数

  29. B 继承 A,且有虚析构函数。A* a = new B 中如果调用虚析构函数,会调用父类 A 的析构函数,还是子类 B 的析构函数

  30. 说明 static_cast、dynamic_cast、const_cast、reinterpert_cast 转换四种的区别

  31. 类指针如何用 C++ 转换类别,例如 A* a 如何转换到 B* 类型?所有的指针是否都是使用 dynamic_cast 进行转换的

  32. 写一个拷贝构造函数?为什么你写的是引用传递?而不是值传递?(除了效率上的问题外)

算法题

1 .输入一个字符串,可在字符串后添加任意字符,使得新字符串变为回文串,要求输出最短的回文串。

算法:两数之和还有一个是 hash 具体啥忘了

算法:链表判断有环➕cuda 实现向量加

线程池

反转链表的递归写法

引用计数实现共享指针

手写一个 Memcpy

手写 Strcpy

手写 Strcat

手写 Strcmp

手写 Strlen

手写 Strfind

排序算法

快排

堆排序

归并排序

如何判断本机是大端序还是小端序?

C++ 多线程打印奇偶数

手撕 LRU 算法

Vector 数组创建二叉树

十进制转十六进制

大小端转换

C 语言实现多态

不用 Sizeof 如何获得 Int 所占的字节数?

C++ 通过递归实现字符串反转

  1. 如何给链表排序?要求时间复杂度 O(nlogn) 空间复杂度 O(1)
  2. 算法题:Top k 如何用堆排序实现
  3. 算法题:如何判断两个链表相交?

2.找单词出现频率 3.数出现 5 就换成 0

4.教授 A 认可 B,B 认可 C,则 A 也认可 C;自己可以认可自己;输入一个认可数组,数组中每个元素是一个认可对,求互相认可的对数。(互相认可即 A 认可 B,B 认可 A) 第四题 Tarjan 求强连通分量个数模版题

13、说一下贪心算法和动态规划。贪心:不从整体最优上加以考虑,求局部最优解。动态规划:通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推或分治的方式去解决。

图搜索算法。BFS、DFS。

算法题 图的最短连通路径长度

介绍一下交叉熵的理解

介绍一下 gan 和 cvae

手撕一下 svm

介绍一下 map 和 unorderedmap 底层实现

介绍一下 new,malloc,智能指针

介绍一下 new,malloc 的底层原理

介绍一下 lambda

手撕一下 transformer

CUDA

cuda 编程 矩阵转置会遇到什么问题 (说是什么访存合并)

  1. cuda graph 作用原理,kernel launch 流程
  2. 如何确定 blocksize 和 gridsize
  3. 什么是 default stream,它存在什么问题
  4. shared memor 的 bank conflict 及解决方法
  5. threadfence 的作用
  6. 如何 debug cuda kernel
  7. unified memory 和 zero-copy memory
  8. cuda sort 如何实现
  9. sin 函数在哪个硬件计算,这个硬件还能算什么
  10. Volat 架构特性,ITS
  11. 3090 上单个 block 能用的 shmem 最大有多少
  12. PTX 与 SASS 的区别
  13. GPU 性能 xx TFLOPS 是怎么计算的

做推理优化和高性能计算肯定是要懂点 cuda,所以大部分的题目都是用 cuda 实现,一些不太好用 cuda 实现的如 NMS 就用 c++ 写了。

cuda 实现:reduction,softmax,matrix transpose,avg pooling,算两堆 bbox 的 iou,大部分情况下都是实现 kernel 即可,少数情况需要跟 cpu 对齐。

c++ 实现:NMS,conv2d,双线性插值,layernorm,单例模式

这里面让我印象比较深刻的是 layernorm,用 cuda 写个 layernorm 不难,但面试官让我用 vadd/vsub/vmul/vdiv 等向量指令实现一个 layernorm,我人都傻了。一是咱平时写 cuda 都是 SIMT 的编程模型,cpu 优化是 SIMD,这俩写起来有差别;二是没提供 sqrt,得自己用牛顿法求,而且还没有比较运算符,浮点数的比较还有一些 trick,最后肯定是寄了。

另外就是某大模型公司,要求实现 softmax,需要跟 cpu 版本对齐。我写了个 3pass 的 softmax,可惜面试过程中结果没有对齐,面完下来 5 分钟就解了 bug,也算比较可惜吧。

Cuda 编程一般怎么优化 问了卷积如何优化 懂不懂共享内存 你一般怎么对 Cuda 编程进行优化

然后是 c++ 八股 C 的 Malloc 和 c++ 的 new 有什么区别

Cpp 的四种强制类型转换 深拷贝和浅拷贝 智能指针 如何防止内存泄漏 智能指针的实现原理

偏硬件的 zero、vLLM 这些,还有 transformer 的各个组件及参数,GLM 相比 Llama2 的改进,包括 post-norm、swiglu、casual 注意力和 prefix 注意力、Deep-Norm、RMS-Norm 这些,Qwen 中 MoE 的作用,有无做过 Bert 相关的训练 or 微调

模型架构、flash attention2 相对 1 的改进、transformer 串烧,zero、Llama1/2/3.1/3.2 各自的特点和改进、数据合成有无了解、推理加速这些,最后给了一道题 mid 也顺利都答上来了

让写了 MoE 架构的代码、快排的代码、还有道 BFS 的代码

这场只有一面,问了问项目、经典 transformer、推理优化怎么实现的、做过哪些模型的预训练和微调、Tensor 并行、流水线并行、数据并行、zero、Megatron、ANN 有无了解、最多用过多少卡,然后让写一道 mid

还有 longformer、稀疏注意力、attention 的本质、位置编码的设计问题、对代码智能的理解等等

问了许多训练相关的问题,例如参数显存、TP/PP/DP 三种并行的区别以及配比,写了道 gemm 的 cuda,还深入问了下 gemm 的优化方向,问了许多 megtron 相关的问题。

代码题是求 n!n! 的结尾有多少个 0。

第一道是给出一串字符串,以及一次操作:将 [l, r] 区间的字符串反转,求问是否可以通过一次操作使得字符串成为回文串。

第二道是给出一个数,求它所有的素因子。

二面时面试官说快点走流程,用 cuda 写道 gemm。

小红书进去是做 vllm 魔改来适配私有魔改模型。

一面时介绍了具体项目,讲了下 deepseek 开源周的一些东西,

代码题是一道层序遍历,然后又问了下对

PD 分离的理解。

MOE 模型在推理上的优化措施,以及 DeepEP 这些,但方向不太符合,遂拒。

一面时问的蛮深入的,基本也是一些 deepseek 开源周的内容,以及 vllm、PD 分离相关的东西。问到现在主流的 moe 模型推理时通讯过程存在的问题以及解决办法,这块关注的比较少,面试官细心的讲了下这块的知识。

二面时主要是 cuda 面,包括 gemm 的优化、以及求方差的题。然后又让从注意力机制讲起,去讲下对所有推理优化相关的认识。面试官很 nice,教了许多学习的方法和思维。

代码题:给定一个数值,用 1,3,5 的硬币来组合他,最少需要的数量是多少。第二天挂掉了。

用 cuda 写一道归约求和的题,写的有点烂,

AI

vllm 源码相关的细节问题,问连续批处理的实现等等,问提高 kv cache 命中的优化方向,问的很多问题是我都没听说过的。

Deepseek 开源周 机器学习/深度学习/训练推理相关:

  1. 了解 Transformer
  • 吗?底层是什么结构?cuda 中如何优化?
  • 说一下你对大模型的理解。
  • cuda 中如何写 Softmax?某个参数过大如何解决?
  • Dropout 和 BatchNorm- 在训练和推理时有什么区别?
  • 说一下你了解的无监督学习算法。
  • 知道 Faster Transformer- 吗?有了解如何实现的吗?
  • Paged Attention 有了解吗?
  • 知道 TensorRT

并发编程 锁 信号量 创建线程的几种方式

lambda 表达式的底层是怎么实现的

std::move 使用场景,他比赋值构造好在哪

lock_guard 相比较于 lock/unlock 能防止什么问题?

模型量化 问在 int8 量化怎么保证在不降点的情况下保持推理速度

HPC

  1. 描述一下 SM 的结构,在写 kernel 的时候共享内存大小和寄存器文件数量需要注意吗?
  2. 共享内存和寄存器分别应该存放哪些数据,其用量与 SM 上活跃的线程块的关系。
  3. bank 冲突是什么?描述具体结构,如何解决?
  4. 说一下分支冲突,如果 warp 内有冲突,部分符合 if 条件,部分符合 else 条件,是否需要等待?
  5. 项目中用过 TensorCore 吗?了解 TensorCore 的原理吗?
  6. 为什么用 float4 向量来存取数据?有什么好处?
  7. 为什么用双缓冲优化?了解 cuda 流和 cuda graph 吗?
  8. 除了 MPI,有知道现在用的更多的 GPU 通信库吗?
  9. Nsight Computing 中,经常关注的与内存相关的指标。有关注 L1 Cache- 命中率吗?
  • GPU 指令集优化方面了解吗?有做过 PTX- 相关的优化吗?
  • GEMM- 是计算密集型还是访存密集型算子?
  • 知道 cutlass
  1. 中如何对 GEMM 进行优化的吗?

  2. CUDA Reduction 或者向量相乘等可以转化为 Reduction 的 Kernel。

  3. CUDA 实现数组排序算法(双调排序)

  4. CUDA 不考虑共享内存,只使用全局内存来做向量矩阵乘法,向量是行主序,矩阵乘向量和向量乘矩阵哪种访存和计算模式更好?说一说哪种用到了归约?

  5. 有 n 个线程和 n 个元素,在 logn 时间内对 n 个元素进行排序。

设计多 CPU 系统的时候需要注意什么?如何保证缓存一致性?

transformer 和 attention 的问题好答,其实核心内容就是多头注意力,他就是一个全局的加权求和,给相当于给把新的词向量表示成原来的词向量的线性组合,这个线性组合的系数就是一个相关性系数

大模型训练

instructGPT

简单介绍 MoE 大模型,需要的计算模块

transformer 结构及需要的计算模块

Attention 的输入的含义和获得方式

python list 去重

python 装饰器,作用

python 内存管理

https://cppguide.cn/

介绍一下访存密集型算子

介绍一下 fp32fp16int8 是怎么存储的

介绍一下量化原理以及过程

OS

介绍一下锁的概念

介绍一下 gpu 和 cpu 的结构以及适合计算什

udp 和 tcp 协议适合什么各自的优缺点

linux 查找磁盘使用指令

linux 查看文件大小,查看网络状态

多线程和多进程,应用场景

进程间通信,应用场景

计算机系统

3、在 main 函数前,运行一个函数怎么处理?

8、动态链接和动态绑定?动态绑定:程序运行过程中,把函数(或过程)调用与响应调用所需要的代码相结合的过程。动态链接:需要使用时,才装载目标文件。

8、介绍计算机的存储架构?寄存器、cache、主存、辅存(硬盘,磁带,光盘,闪存等)

9、CPU 中应用程序员可见的寄存器的分类?PC 寄存器的作用?3 类,通用寄存器、段寄存器、标志和控制寄存器(CPU 寄存器一共 6 类,系统地址寄存器、调试寄存器和测试寄存器程序员不可见)。程序计数器负责存放下一条指令的地址。

5、说一下 cache 映射的分类和特点。组相联映射,全相联映射,直接映射。特点……

4、调试程序的时候设置过断点嘛?如何设置条件断点?对不起,没有接触过条件断点。我如果要选择性的看数据,是直接打印出来的。

5、怎么进行堆栈监视?

6、程序在内存中的分段?分 5 段,命令行参数和环境变量、堆区、栈区,数据区,代码区。……

9、寄存器和 cache 哪一个更快?cache 的实现方式是什么?主存的实现方式是什么?有什么区别和相同?寄存器快,cache 是 SRAM 实现的。主存是 DRAM 实现的。主存需要刷新,cache 不需要。都是断电易失。

4、GPU 中的内存架构是什么?SP 共享 Register File,SM 独享 L1 和 SM 内共享内存,所有 SM 共享 L2 和全局内存。

  1. GPU 是 SIMT 架构吗

  2. DPU 设计架构

  3. 抢占式调度算法

  4. 了解公司的业务吗

  5. 职业规划

  6. 希望做什么方向

编程算法

2、openMP 并行区怎么开?pragma omp parallel…

3、for(int i=0;i<100;i++){sum++;}   在并行区内和串行区的运行结果是否相同?如何变成相同的?  开放式的,依据自己的习惯来做就可以了。一般来说,我习惯设置数据副本,最后合并。(vector 也行)

8、STL 里面的 deque 是怎么实现的?map 中控数组加指针,指针指向缓冲区即分段连续空间。

6、如果出现了哈希冲突怎么解决?开放定址法,再哈希法,链地址法,建立公共溢出区。

3、C++ 的 stl 库熟悉嘛?说一下 map 的底层构造?底层是红黑树,一种自平衡二叉搜索树。

4、说一下平衡二叉搜索树和红黑树有什么区别?(1) 调平机制不同,红黑是二倍,AVL 是差值不超过 1;(2)RB 插入效率高,统计性能好,AVL 查找效率高

5、unordered_map 的底层是什么?简单介绍一下。哈希表,随机搜索效率高,依赖于哈希映射函数……

6、说一下智能指针的分类和用法。share、auto(C++11 弃用)、weak、unique……

7、多态是什么?怎么实现的?父类中同一个方法,在继承的子类中表现出不同的形式。重写和重载……

静态链接,动态链接

红黑树,具体带系数的时间复杂度

内存泄漏,怎么解决

模板特化,偏特化,模板实例化是在哪个阶段,模板怎么拒绝一个类型

智能指针,shared_ptr 是线程安全的吗?

  1. C++ 虚函数实现机制,单继承、多继承、虚继承的内存布局
  2. 四种 cast
  3. 三种智能指针
  4. 函数模板声明与定义能否分离
  5. CRTP 静态多态
  6. vector 扩容,resize 和 reserve
  7. 单例模式

HPC 笔试题

inline unroll

不定项选择题(必答) 对于function inline和loop unroll的优化,下列描述正确的是 A function inline和loop unroll的优化,提升了指令fetch的locality,增加更多的指令优化机会,但容易导致binary size的增大; B function inline优化在GPGPU中非常重要,编译器能够通过优化消除掉所有的函数调用 C loopunroll优化在GPGPU中非常重要,编译器能够通过优化消除掉所有的loop代码; D function inline避免了函数调用的开销,和调用中变量的出入栈开销;

  1. 分析选项 A
    • 提升指令 fetch 的 locality:function inline(函数内联)是将被调用函数的代码直接嵌入到调用处,loop unroll(循环展开)是将循环体代码重复展开。这样做使得程序代码在内存中的布局更加紧凑,在取指令(fetch)时,能在相邻内存区域获取更多指令,提升了指令 fetch 的局部性(locality)。
    • 增加指令优化机会:代码合并后(内联和循环展开后代码结构改变),编译器可以对更大范围的代码进行整体分析和优化,例如进行更有效的指令调度等,增加了指令优化的机会。
    • 导致 binary size 增大:函数内联会将函数代码重复嵌入到调用处,循环展开会重复循环体代码,这都会使得最终生成的二进制代码(binary)包含更多重复内容,从而导致 binary size 增大。所以选项 A 描述正确。
  2. 分析选项 B
    • 在 GPGPU(通用图形处理器)中,function inline 优化确实比较重要,因为它可以减少函数调用开销,提高执行效率。然而,编译器不可能消除掉所有的函数调用。有些函数调用可能涉及多态等复杂情况,或者是为了实现特定的软件架构设计,无法被内联。所以 “编译器能够通过优化消除掉所有的函数调用” 这种说法过于绝对,选项 B 描述错误。
  3. 分析选项 C
    • 在 GPGPU 中,loop unroll 优化很重要,它能减少循环控制指令的开销,提高并行性等。但编译器不能消除掉所有的 loop 代码。有些循环可能依赖于运行 - 时的条件,无法在编译阶段完全展开消除,或者展开后可能会导致代码量过大等问题。所以 “编译器能够通过优化消除掉所有的 loop 代码” 这种说法过于绝对,选项 C 描述错误。
  4. 分析选项 D
    • function inline 将被调用函数的代码直接嵌入调用处,避免了函数调用过程中保存和恢复寄存器状态、跳转到函数入口和从函数返回等开销,也就是避免了函数调用的开销。同时,函数调用时变量的出入栈操作(用于传递参数和保存局部变量等)也因为内联而不再需要,避免了调用中变量的出入栈开销。所以选项 D 描述正确。

综上,正确答案是 AD。

复杂度

考虑以下几种常见的时间复杂度,请选出所有正确的陈述 当比较O(n^2)和O(nlogn)两种不同时间复杂度时,在大数据集上后者通常会表现出更好的性能 如果一个算法的时间复杂度为O(1),这意味着无论输入大小如何变化,该算法执行所需的时间都是恒定不 的。 二分查找算法在有序列表中的最坏情况下的时间复杂度为0(logn)。 对于任意两个正整数n和m(假设n>m),O(n)总是比(m)表示的算法更慢。

  1. 分析选项A: - 时间复杂度反映了算法运行时间随输入规模增长的变化情况。(O(n^{2}))表示算法运行时间与输入规模(n)的平方成正比,(O(n\log n))表示算法运行时间与(n)和(\log n)的乘积成正比。 - 当(n)(大数据集意味着(n)很大)逐渐增大时,(n^{2})的增长速度远远快于(n\log n)。例如,当(n = 1000)时,(n^{2}=1000\times1000 = 1000000),而(n\log n)(以(2)为底为例,(\log_{2}1000\approx10)),(n\log n\approx1000\times10 = 10000)。所以在大数据集上,具有(O(n\log n))时间复杂度的算法通常比(O(n^{2}))的算法表现出更好的性能,选项A正确。
  2. 分析选项B: - (O(1))是一种常数时间复杂度。这意味着算法执行的基本操作次数不随输入规模(n)的变化而变化,始终保持一个固定的常数。例如,从数组中根据已知索引获取一个元素,无论数组有多大,只要索引是确定的,获取操作的时间是恒定的。所以无论输入大小如何变化,该算法执行所需的时间都是恒定不变的,选项B正确。
  3. 分析选项C: - 二分查找算法的基本思想是每次将有序列表分成两部分,然后根据目标值与中间元素的比较结果,决定在左半部分还是右半部分继续查找。 - 在最坏情况下,每次查找都只能排除一半的元素,直到找到目标元素或者确定目标元素不存在。设列表长度为(n),查找次数(k)满足(n = 2^{k}),对其变形可得(k=\log_{2}n),所以二分查找算法在有序列表中的最坏 - 情况下的时间复杂度为(O(\log n))(一般默认以(2)为底),选项C正确。
  4. 分析选项D: - (O(n))和(O(m))表示的是算法时间复杂度的渐近表示。渐近表示关注的是当输入规模趋向于无穷大时算法的性能趋势。 - 虽然(n\gt m),但(O(n))和(O(m))代表的是不同的增长趋势,不能简单地说(O(n))总是比(O(m))表示的算法更慢。例如,(O(100n))和(O(n^{2})),当(n)较小时(如(n = 1)),(100n = 100),(n^{2}=1),此时(O(n^{2}))的算法更快;当(n)足够大时,(n^{2})增长速度远超(100n),(O(n^{2}))的算法就会比(O(100n))慢。所以不能仅根据(n\gt m)就判定(O(n))总是比(O(m))表示的算法更慢,选项D错误。 综上,正确答案是ABC。

调度

不定项选择题(必答) 在高负载服务器中,有时会观察到1/O速度突然变慢,排查后发现是由于文 最可能引发这个现象? 以下哪种操作 用户进程频繁申请大量内存,触发了内存回收机制 B 系统启用了实时调度策略,限制了内存分配。 系统的文件描述符耗尽,导致缓存回收失败。 频繁的磁盘I/O操作导致了缓存写入阻塞。

  1. 分析选项 A
    • 当用户进程频繁申请大量内存时,系统内存资源紧张。为了满足新的内存请求,操作系统会触发内存回收机制 。文件系统缓存也占用内存空间,在内存回收过程中,就有可能回收文件系统缓存,从而导致 I/O 速度因缓存被回收而变慢。所以选项 A 有可能引发该现象,不符合题意,排除。
  2. 分析选项 B
    • 系统启用实时调度策略主要是为了保证实时任务能够及时得到处理,它限制内存分配是为了优先满足实时任务对资源的需求。这种限制内存分配的操作可能会使得系统在内存紧张时去回收文件系统缓存等内存资源,进而引发 I/O 速度因缓存被回收而变慢。所以选项 B 有可能引发该现象,不符合题意,排除。
  3. 分析选项 C
    • 文件描述符主要用于标识打开的文件、套接字等 I/O 资源。当系统的文件描述符耗尽时,主要影响的是新的 I/O 相关操作(如无法打开新文件等),但它和文件系统缓存被回收之间并没有直接的因果关系 。缓存回收通常是由于内存资源紧张等原因触发,而不是文件描述符耗尽导致。所以选项 C 最不可能引发文件系统缓存被回收从而使 I/O 速度变慢的现象,符合题意。
  4. 分析选项 D
    • 频繁的磁盘 I/O 操作可能会导致磁盘繁忙,当进行缓存写入磁盘操作时,由于磁盘忙等原因可能出现写入阻塞。在这种情况下,系统可能为了释放内存资源或调整 I/O 相关资源,而回收文件系统缓存,进而引发 I/O 速度变慢。所以选项 D 有可能引发该现象,不符合题意,排除。

综上,答案选 C。

服务器

不定项选择题 (必答) 在某服务器中,发现某块高负载网卡的中断处理程序致 降低网卡的工作频率以减少中断触发次数。 B 增加CPU核心数量来分担中断负载。 使用中断负载分配(IRQAffinity),将中断分布到多个CP核心 将中断绑定到特定CPU核心以减少上下文切换。

  1. 分析选项 A
    • 降低网卡工作频率,从原理上讲,网卡发送和接收数据的速度会变慢,从而产生的中断请求次数也会相应减少。因为中断处理程序会占用 CPU 资源,中断触发次数减少,CPU 花费在该网卡中断处理上的时间就会降低,进而有可能缓解 CPU 使用率飙升的问题,所以该方案是有效的。
  2. 分析选项 B
    • 增加 CPU 核心数量,虽然 CPU 核心增多了,但如果不采取合理的分配策略,新增加的 CPU 核心不一定能分担该高负载网卡的中断负载。高负载网卡的中断处理程序可能还是集中在原来的某些核心上运行,无法有效利用新增核心来降低整体 CPU 使用率,所以该方案不一定能有效解决问题 。
  3. 分析选项 C
    • 使用中断负载分配(IRQ Affinity),它的作用是将中断分布到多个 CPU 核心。这样一来,原本集中在少数核心上处理的高负载网卡中断,就可以分散到多个核心进行处理,避免单个或少数核心因处理过多中断而过载,能够更均衡地利用 CPU 资源,从而有效降低因该网卡中断处理导致的 CPU 使用率飙升问题,所以该方案是有效的。
  4. 分析选项 D
    • 将中断绑定到特定 CPU 核心,能够减少上下文切换。当一个中断在不同核心间频繁切换处理时,会产生大量的上下文切换开销,消耗 CPU 资源。把中断固定绑定到特定核心,可避免这种不必要的开销,让 CPU 更专注于处理中断任务,提高处理效率,在一定程度上缓解 CPU 使用率飙升的情况,所以该方案是有效的。

综上,选项 A、C、D 的解决方案是有效的。

要判断哪种解决方案 “最有效”,需进一步分析各选项:

选项 A

降低网卡工作频率虽能减少中断次数,但会直接降低网卡的数据处理性能(如吞吐量、传输速度等),属于 “以牺牲功能性能为代价” 的优化,通常不是首选的 “高效” 解决方案。

选项 B

增加 CPU 核心数量属于硬件扩容,成本高且不一定能 “精准” 解决 “中断集中导致的 CPU 过载” 问题(若不做中断负载分配,新增核心可能无法参与中断处理),效率和性价比都不高。

选项 C

使用中断负载分配(IRQ Affinity),能在不牺牲硬件性能、不额外增加硬件成本的前提下,将高负载网卡的中断 “均衡分散” 到多个 CPU 核心,让每个核心分担部分中断处理压力,从 “资源调度优化” 的角度高效利用现有 CPU 资源,是软件层面的精准优化,对系统性能影响小且能直接缓解中断集中导致的 CPU 过载。

选项 D

将中断绑定到特定 CPU 核心,虽能减少上下文切换,但中断仍集中在单个 / 少数核心,高负载网卡的中断仍可能让这些核心过载,无法从根本上解决 “高负载中断导致 CPU 使用率飙升” 的问题(因为压力没被分散)。

综上,选项 C(使用中断负载分配,将中断分布到多个 CPU 核心)是最有效的解决方案

cache

不走项选择题(必管) 在片上缓存中,以下哪些是可能需要验证的性能指标? 缓存- 致性延迟 B 缓存替换算法效率 缓存命中率 缓存访问延迟

  1. 分析选项 A:缓存一致性延迟
    • 在多处理器或多核系统中,不同的缓存可能会存储同一数据的副本 。当一个缓存中的数据被修改时,需要确保其他缓存中的副本也能及时更新或保持一致,这个过程存在时间延迟,即缓存一致性延迟。它直接影响到系统数据的一致性和整体性能,所以是片上缓存需要验证的重要性能指标之一。例如,在多核 CPU 中,若多个核心都对共享数据有缓存,核心 1 修改了缓存中的数据,要将这个修改传播到其他核心的缓存,这个传播过程的延迟对系统运行效率有很大影响,故 A 选项正确。
  2. 分析选项 B:缓存替换算法效率
    • 片上缓存的容量是有限的,当新的数据需要存入缓存,而缓存已满时,就需要采用一定的缓存替换算法来决定将哪个旧数据从缓存中移除 。不同的缓存替换算法(如最近最少使用 LRU、先进先出 FIFO 等)在不同的应用场景下表现不同。高效的缓存替换算法能够提高缓存的利用率,减少数据未命中的情况。例如,LRU 算法通过将最近最少使用的数据替换出去,理论上能让经常访问的数据留在缓存中。验证缓存替换算法效率,有助于评估在当前系统负载下,缓存是否能以最优方式管理数据,所以它是片上缓存需要验证的性能指标,B 选项正确。
  3. 分析选项 C:缓存命中率
    • 缓存命中率是指 CPU 要访问的数据在缓存中能找到的概率 。缓存的目的就是为了让 CPU 快速获取数据,减少对低速主存的访问。缓存命中率越高,说明 CPU 从缓存中获取数据的比例越大,系统整体性能也就越好。例如,在一个程序频繁访问某些数据的情况下,如果缓存命中率高,CPU 就能快速从缓存读取数据进行处理,大大提升运行速度。因此,缓存命中率是衡量片上缓存性能的关键指标,是需要验证的,C 选项正确。
  4. 分析选项 D:缓存访问延迟
    • 缓存访问延迟是指从 CPU 发出访问缓存的请求到获取到数据所经历的时间 。它反映了缓存响应 CPU 请求的速度。较低的缓存访问延迟意味着 CPU 能更快地获取到所需数据,从而提高系统的执行效率。例如,对于实时性要求较高的应用,缓存访问延迟的高低直接影响到应用的实时性能表现。所以缓存访问延迟是片上缓存需要验证的重要性能指标,D 选项正确。

综上,ABCD 四个选项都是片上缓存可能需要验证的性能指标。

网卡

在某服务器中,发现某块高负载网卡的中断处理程序导致CPU使用 降低网卡的工作频率以减少中断触发次数。 增加CPU核心数量来分担中断负载。 使用中断负载分配(IRQAffinity),将中断分布到多个CPU核心 将中断绑定到特定CPU核心以减少上下文切换。

  1. 分析选项 A
    • 降低网卡工作频率确实能减少中断触发次数。因为网卡工作时数据收发等操作会触发中断让 CPU 处理,中断次数减少,CPU 用于处理该网卡中断的时间就会降低 。然而,降低网卡工作频率会直接导致网卡的数据传输能力下降,影响服务器网络数据的收发效率,这是以牺牲网络性能为代价来降低 CPU 使用率,不是一种理想的解决方案,所以选项 A 不太有效。
  2. 分析选项 B
    • 增加 CPU 核心数量,虽然理论上可以提供更多的计算资源。但对于高负载网卡的中断处理,单纯增加 CPU 核心数量,并不能直接将中断负载合理地分配到新增的核心上。因为系统默认的中断处理机制不会自动充分利用新增的核心来处理网卡中断,所以不一定能有效分担该网卡的中断负载,对解决当前网卡中断导致的 CPU 使用率飙升问题效果不佳,选项 B 不太有效。
  3. 分析选项 C
    • 中断负载分配(IRQ Affinity)技术,它的原理是可以将网卡产生的中断分布到多个 CPU 核心上。这样多个核心可以同时处理网卡的中断请求,避免单个 CPU 核心因处理高负载网卡中断而过载,充分利用多核 CPU 的资源,能较为有效地降低因某块高负载网卡中断导致的 CPU 使用率飙升问题,选项 C 是有效的解决方案。
  4. 分析选项 D
    • 将中断绑定到特定 CPU 核心,目的是减少上下文切换。上下文切换是指 CPU 从一个任务切换到另一个任务时所做的一系列保存和恢复工作。但绑定到特定核心,如果该核心本身负载已经较高,或者不能充分利用其他空闲核心的资源,就无法很好地解决高负载网卡中断导致的 CPU 使用率飙升问题。它没有像中断负载分配那样充分利用多核优势来分担中断负载,所以效果相对有限,选项 D 不太有效。

综上,正确答案是 C,因为使用中断负载分配(IRQ Affinity)将中断分布到多个 CPU 核心,能有效利用多核 CPU 资源,解决高负载网卡中断使 CPU 使用率飙升的问题。

缓存

下是响选择题(必答) 在高负载服务器中,有时会观察到1/O速度突然变慢, 排查后是文件系统缓存被回收导致的, 最可能引发这个现象? 用户进程频繁申请大量内存,触发了内存回收机制。 B 系统启用了实时调度策略,限制了内存分配。 系统的文件描述符耗尽,导致缓存回收失败。 频繁的磁盘I/O操作导致了缓存写入阻塞。

  1. 分析选项 A
    • 用户进程频繁申请大量内存,触发内存回收机制。内存回收机制主要关注的是内存资源的管理与释放,其目的是为了满足进程对内存的需求,避免内存泄漏等问题。它本身并不直接作用于文件系统的 I/O 操作,与文件系统缓存被回写以及 I/O 速度变慢没有直接关联。所以选项 A 不太可能引发该现象。
  2. 分析选项 B
    • 系统启用实时调度策略,限制了内存分配。实时调度策略重点在于保证实时任务能够及时获得 CPU 资源以及合适的内存等资源,以满足实时性要求。它对内存分配的限制主要围绕实时任务和非实时任务之间的资源分配平衡,并非直接针对文件系统的缓存和 I/O 操作。因此,它一般不会直接导致文件系统缓存被回写进而使 I/O 速度变慢,选项 B 不符合。
  3. 分析选项 C
    • 系统的文件描述符耗尽,导致缓存回收失败。文件描述符是操作系统用于标识打开文件的一种机制。当文件描述符耗尽时,系统无法正常地进行文件相关操作。文件系统的缓存管理依赖于正常的文件操作流程,缓存回收也需要借助文件描述符等资源来完成相关操作。如果文件描述符耗尽,缓存回收就会受到影响,可能导致缓存不能及时被正确回写,从而使得 I/O 速度突然变慢,该选项与题目中因文件系统相关导致 I/O 速度变慢的描述相符,是有可能引发此现象的。
  4. 分析选项 D
    • 频繁的磁盘 I/O 操作导致了缓存写入阻塞。虽然频繁磁盘 I/O 操作可能会对系统整体性能有影响,但这里强调的是缓存写入阻塞,而题目中明确说是文件系统缓存被回写等导致 I/O 变慢,缓存写入阻塞和缓存被回写概念不同。缓存写入阻塞不一定直接等同于文件系统缓存被回写出现问题,所以选项 D 不太可能是引发该现象的原因。

综上,最可能引发这个现象的是选项 C。

线程同步

在单cpu核的系统中运行多线程程序的时候,也需要通过锁者原子操作来做线程间的同步控制,以免影响功能正确 正确 A 错误

  1. 分析题目涉及的知识点
    • 本题考查在单 CPU 核系统中多线程程序线程间同步控制的必要性。关键在于理解单 CPU 核环境下多线程的执行特点以及锁和原子操作在同步控制中的作用。
  2. 阐述单 CPU 核系统多线程执行原理
    • 在单 CPU 核的系统中,虽然从宏观上看多个线程好像在同时运行,但实际上在某一时刻,CPU 只能执行一个线程的指令。多个线程是通过 CPU 的时间片轮转等调度方式来交替执行的。
    • 当多个线程访问共享资源(比如共享变量、共享数据结构等)时,就可能出现问题。例如,线程 A 和线程 B 都要对同一个共享变量进行修改操作,由于线程的执行是抢占式的,可能线程 A 执行了部分修改操作后,时间片到了,CPU 转而执行线程 B 的操作,线程 B 也对该共享变量进行修改,之后线程 A 又继续执行剩余的修改操作。这样就会导致最终共享变量的值并不是预期的正确结果,出现数据竞争等问题。
  3. 说明锁和原子操作的作用
    • 锁机制可以保证在同一时刻只有一个线程能够访问被锁保护的共享资源。当一个线程获取到锁后,其他线程就必须等待,直到该线程释放锁,这样就能避免多个线程同时对共享资源进行操作而产生冲突。
    • 原子操作是指不可被中断的操作,在单 CPU 核系统中,原子操作可以确保对共享资源的访问是完整的,不会被其他线程打断。例如,某些 CPU 指令本身就是原子的,像对特定寄存器的读写等操作,在执行过程中不会被其他线程干扰,从而保证了数据访问的正确性。
  4. 得出结论
    • 为了保证多线程程序在单 CPU 核系统中功能的正确性,避免因多个线程对共享资源的并发访问产生数据竞争等问题,确实需要通过锁或者原子操作来做线程间的同步控制。所以答案是 A(正确)。

HPC

在并行计算环境中实施规约操作时,为了保证进确性和高效性, 哪些方面? 优化负载均衡,避免某些节点过载而其他节点空闲 增加线程间的同步次数,尽量保持各线程同步执行 确保规约运算符满足结合律 设计合理的数据划分方案

  1. 分析选项 A
    • 在并行计算环境中,负载均衡至关重要。如果某些节点过载,意味着这些节点承担了过多的计算任务,运行速度会减慢,而其他节点空闲则造成了计算资源的浪费。
    • 优化负载均衡,能让各个计算节点相对均衡地分担计算任务,充分利用所有节点的计算能力,从而提高整体计算效率,同时也有助于保证计算结果的正确性(因为不会因个别节点长时间处理任务而影响后续依赖其结果的计算步骤 )。所以选项 A 正确。
  2. 分析选项 B
    • 增加线程间的同步次数,虽然在一定程度上能保证各线程执行的协调性,但过多的同步操作会引入额外的开销。同步操作通常需要线程等待其他线程到达特定状态,这会降低并行计算的并发度。
    • 并行计算的优势在于多个线程同时执行以提高效率,过多同步会使线程间相互等待,导致整体执行时间变长,降低了计算的高效性。所以一般不建议通过增加线程间同步次数来保证正确性和高效性,选项 B 错误。
  3. 分析选项 C
    • 规约操作是将多个数据通过特定运算符进行逐步合并计算得到一个最终结果的过程。如果规约运算符不满足结合律,那么不同的计算顺序可能会得到不同的结果,这就无法保证计算结果的正确性。
    • 例如在并行计算中,不同线程可能会以不同顺序对数据进行规约计算,只有运算符满足结合律,才能确保无论怎样的计算顺序,最终都能得到正确统一的结果。所以确保规约运算符满足结合律对于保证规约操作的正确性很重要,选项 C 正确。
  4. 分析选项 D
    • 合理的数据划分方案是并行计算的基础。不同的数据划分方式会影响到各个计算节点或线程的计算量和通信量。

    • 例如,将数据均匀且合理地划分到不同线程或节点,能避免出现数据倾斜(即某些节点处理数据量过大,而其他节点处理数据量过小)的情况,从而保证负载均衡,提高计算效率,同时也有利于正确地进行规约计算(因为合理划分能让每个部分的计算有序进行 )。所以设计合理的数据划分方案对保证正确性和高效性有帮助,选项 D 正确。

综上,答案是 ACD。

计算

矩阵乘法运算符合结合律和分配律,因此在计算芯片上通过运行数学上完全等价的线性变换,可以降低计算量且计算 结果精度一致。 正确 错误 B

  1. 分析矩阵乘法运算性质
    • 矩阵乘法确实满足结合律,即对于矩阵A、B、C,有((AB)C = A(BC));也满足分配律,包括左 - 分配律(A(B + C)=AB+AC)和右 - 分配律((B + C)A=BA + CA)。这是矩阵运算的基本性质。
  2. 探讨基于运算性质进行线性变换与计算量及精度的关系
    • 在计算芯片(如用于深度学习等涉及大量矩阵运算的场景)中,利用矩阵乘法的结合律和分配律,将原本复杂的矩阵运算表达式进行重新组合(即运行数学上完全等价的线性变换)。例如,在一些深度学习的矩阵乘法计算中,通过合理运用结合律,改变矩阵相乘的顺序,可能会减少中间结果的存储需求以及计算过程中的乘法和加法次数,从而降低计算量 。
    • 因为这些线性变换是基于严格的矩阵运算定律进行的,本质上是对相同数据按照数学等价的方式进行计算,所以计算结果的精度是一致的。例如,无论先计算哪两个矩阵相乘,只要计算过程中没有舍入误差等额外因素(在理论上基于等价线性变换的计算不存在这种额外差异),最终结果都应该是相同的。
  3. 得出结论
    • 题干中 “矩阵乘法运算符合结合律和分配律,因此在计算芯片上通过运行数学上完全等价的线性变换,可以降低计算量且计算结果精度一致” 的表述是正确的。所以应该选择 A 选项。

算子融合

深度学习框架,往往通过融合来提升模型训练和推理的行建度。融合是把 GPU来说,融合成一个Kernel。以下哪些关于融合优点的描述是正确的? 减少访存数据读取 B 提升访存带宽 减少访存占用量 减少Kernel launch的时间

  1. 分析选项 A
    • 当把多个操作融合成一个 Kernel 时,原本多个操作可能需要多次从内存读取数据。融合后,一些中间数据不需要再次从内存读取,而是在融合的 Kernel 内部直接使用之前计算产生的中间结果 。例如,在一些连续的矩阵运算中,融合操作可以避免重复读取相同的矩阵数据用于不同的计算步骤。所以融合可以减少访存数据读取,选项 A 正确。
  2. 分析选项 B
    • 访存带宽是指在单位时间内可以从存储器传输到处理器的数据量。融合操作并没有直接改变存储器和处理器之间传输数据的物理带宽限制。它主要是通过减少不必要的数据读取等方式来优化数据的使用,而不是提升访存带宽本身。所以选项 B 错误。
  3. 分析选项 C
    • 融合操作使得多个操作在一个 Kernel 中执行,一些中间数据不需要像未融合时那样长时间占用内存空间等待后续操作读取。例如,在一系列连续的图像处理操作融合后,中间的图像数据不需要一直保留在内存中供不同的处理步骤分别读取,从而减少了访存占用量。所以选项 C 正确。
  4. 分析选项 D
    • 在 GPU 中,每次启动 Kernel(Kernel launch)都需要一定的开销,包括设置参数、调度等操作。当把多个操作融合成一个 Kernel 时,相比之前分开执行多个操作需要多次启动 Kernel,现在只需要启动一次融合后的 Kernel,从而减少了 Kernel launch 的次数,也就减少了 Kernel launch 的时间。所以选项 D 正确。

综上,正确答案是 ACD。

C++

lambda

下定项选择题(必管) 以下关 lambda表达 式的描述中,哪些是正确的? (٨) 定义全局变量 可以指定返回 总是创建匿名函数 可以捕获外部变量

  1. 分析选项 A
    • lambda 表达式的主要功能并非定义全局变量。lambda 表达式是一种用于创建匿名函数对象的语法结构,它专注于定义可调用的函数逻辑,和定义全局变量没有直接关系。所以选项 A 错误。
  2. 分析选项 B
    • 在 C++11 的 lambda 表达式中,可以指定返回类型 。例如,当 lambda 表达式的函数体较为复杂,编译器无法自动推导返回类型时,就可以显式指定返回类型。如[](int a, int b) -> int { return a + b; },这里-> int就是显式指定了返回类型为int。所以选项 B 正确。
  3. 分析选项 C
    • lambda 表达式的一个重要特性就是创建匿名函数。它不像普通函数那样有具体的函数名,通过[](捕获列表)、()(参数列表)、->(返回类型,可省略)和{}(函数体)这些部分组成一个没有名字的函数定义。所以总是创建匿名函数,选项 C 正确。
  4. 分析选项 D
    • lambda 表达式可以捕获外部变量。捕获方式有值捕获(如[x],按值捕获变量x)、引用捕获(如[&x],按引用捕获变量x)以及混合捕获等。捕获外部变量使得 lambda 表达式能够在其函数体内使用外部作用域中的变量。所以选项 D 正确。

综上,正确的是选项 B、C、D。

template

不定项选 择题(必答) L 关于C++中template的使用,哪些选项是正确的? 20 A 对于编译器来说,函数/类模板和正常函数/类在对代码进行类型检查是有区别的,在某些情况下可能存在模板 代码中的常规错误是无法发现的。 B 模板是为了一种或者多种未明确定义的类型而定义的函数或者类。在使用模板时,需要显式地或者隐式地指定 模板参数。 利用C++类的多态特性,通过某一个公共基类里面实现通用的算法代码,在继承的子类中实现特定类型的操作 函数,也能到达模板函数的目的。好处是可读性、可调试性和执行性能更好,坏处是放弃了模版在编译期支持 的类型和作用域检查。 I D 一般推荐模板的使用方式,是声明和定义放在同一个头又文件中,如果分离可能会导致link错误,比如a.h中声明 一个模板的定义,a.cpp中实现模板相关的函数实现,然后main.cpp中#includea.h使用这个模板,最后完成编 译链接的过程。

  1. 分析选项 A
    • 在 C++ 中,函数 / 类模板在编译时的类型检查机制与正常函数 / 类不同。模板在实例化之前,编译器不会对其进行完整的类型检查。例如,模板代码中可能存在一些依赖于具体模板参数类型的操作,在未实例化时,编译器无法确定这些操作对于特定类型是否合法。所以在某些情况下,模板代码中的常规错误(比如针对特定类型不支持的操作)在模板未实例化时确实可能无法被发现 。因此,选项 A 的说法是正确的。
  2. 分析选项 B
    • 模板分为函数模板和类模板,它就是为一种或多种未明确定义的类型(模板参数类型)而定义的函数或类。当使用模板时,需要指定模板参数,指定方式可以是显式指定,如std::vector<int> vec;(这里显式指定模板参数为int );也可以是隐式指定,比如编译器根据函数调用时传递的实参类型来推断模板参数类型,如定义template <typename T> T add(T a, T b) { return a + b; },调用add(1, 2);时,编译器可隐式推断出Tint。所以选项 B 的说法正确。
  3. 分析选项 C
    • 利用 C++ 类的多态特性,通过在公共基类中实现通用算法代码,在子类中实现特定类型操作函数,确实能实现类似模板函数的代码复用目的。多态方式的代码,其逻辑结构相对清晰,可读性较好,调试时也更容易理解各部分的功能。在运行时,根据对象的实际类型调用相应的虚函数,执行性能也不错。然而,与模板相比,模板在编译期就进行类型检查和实例化,能在编译期发现很多类型相关的错误,而多态这种方式放弃了编译期的类型和作用域检查,它是在运行期确定调用的函数。所以选项 C 的说法正确。
  4. 分析选项 D
    • 在 C++ 中,一般推荐将模板的声明和定义放在同一个头文件中。这是因为模板的实例化依赖于其定义可见。如果在头文件(如 a.h)中声明模板,在源文件(如 a.cpp)中定义模板相关函数实现,当其他源文件(如 main.cpp)包含 a.h 使用该模板时,在链接阶段可能会出现问题。因为模板的实例化是在使用它的地方进行的,若链接器找不到模板的完整定义,就会导致 link 错误。所以选项 D 的说法正确。

综上,ABCD 四个选项的说法都是正确的。

访存

不定项选择题(必第) 以下两种CPU代码哪种性能更好?

#pragma unroll(1)
for (intx=0;x<ROWs;++x)
for (int y=0;y<COLS;++y)
data[x][y]=x+y;
#pragma unroll(1)
for (inty=0;y<COLs;++y)
for (intx=0;x<ROWs;++x)
data[x][y] = x + y;

第一种 A B 都一样 第二种 随机

  1. 理解#pragma unroll指令
    • #pragma unroll是一种编译器指令,作用是让编译器对循环进行展开。#pragma unroll(1)表示将循环展开一层 。循环展开的目的通常是减少循环控制的开销(如循环条件判断、递增循环变量等操作的开销),在一定程度上提高指令级并行性。
  2. 分析两种代码的内存访问模式
    • 对于第一种代码:
      • 外层循环控制x,内层循环控制y。它是按行优先的方式访问二维数组data[x][y]。在现代计算机的内存层次结构中,内存是以块(如缓存行)为单位进行读取和写入的。按行优先访问二维数组,符合大多数计算机内存的存储和读取特性。因为二维数组在内存中是按行连续存储的,这样的访问方式可以更好地利用数据在缓存中的局部性原理(空间局部性,即当一个数据被访问时,其附近的数据很可能也会被很快访问到),减少缓存缺失,提高数据访问效率。
    • 对于第二种代码:
      • 外层循环控制y,内层循环控制x。它是按列优先的方式访问二维数组data[x][y]。由于二维数组按行连续存储在内存中,按列优先访问会导致频繁的跨缓存行访问,大大增加缓存缺失的概率 。每次访问新的列元素时,很可能该元素不在当前缓存行中,需要从内存中读取新的缓存行,这会带来较高的内存访问延迟,降低性能。
  3. 综合性能比较
    • 虽然#pragma unroll(1)对两种代码都有减少循环控制开销的作用,但内存访问效率对性能影响更为显著。第一种代码按行优先访问数组,能更好利用缓存局部性,减少缓存缺失带来的性能损耗。而第二种代码按列优先访问数组,缓存缺失严重,性能相对较差。
    • 所以第一种代码性能更好,答案选 A。

操作符

不定项选择题 (必答) 三个32bit的整数a、b、c相乘得到乘积d

uint64_td=a*b*c
B
uint64_td=a*b* (uint64_t)c:
uint64_td=(uint64_t)a*b*c
uint32_td=a*b*c
  1. 分析题目
    • 已知有三个 32 - bit 的整数 a、b、c,要将它们相乘的结果存储到变量 d 中。32 - bit 整数相乘,结果可能超出 32 - bit 的表示范围,所以需要用 64 - bit 的整数类型来存储结果,这里uint64_t是 64 - bit 无符号整数类型。
  2. 分析选项 A
    • uint64_t d = a * b * c; 这里 a、b、c 都是 32 - bit 整数,在进行a * b * c运算时,由于 a、b、c 都是 32 - bit,先进行a * b运算,结果仍然是 32 - bit(如果没有溢出的话,即使溢出也是以 32 - bit 形式呈现),然后再与 c 相乘,整个运算过程都是在 32 - bit 整数运算范畴内,可能会发生溢出且没有将结果转换为 64 - bit 进行存储,所以这种写法不正确。
  3. 分析选项 B
    • uint64_t d = a * b * (uint64_t)c; 先进行a * b运算,这一步还是 32 - bit 整数运算,结果是 32 - bit(可能溢出),然后再与转换为uint64_t类型的 c 相乘。虽然 c 转换为了 64 - bit,但前面a * b的结果没有转换,仍然存在溢出风险且没有正确将整个乘积以 64 - bit 完整表示,所以这种写法不正确。
  4. 分析选项 C
    • uint64_t d = (uint64_t)a * b * c; 首先将 a 转换为uint64_t类型,此时(uint64_t)a * b的运算就会按照 64 - bit 整数乘法规则进行(因为有一个操作数是 64 - bit 了),得到的中间结果也是 64 - bit,再与 c 相乘,c 会自动提升为 64 - bit(因为另一个操作数是 64 - bit)进行 64 - bit 整数乘法运算,最后结果是 64 - bit 的,能够正确存储到uint64_t类型的 d 中,这种写法正确。
  5. 分析选项 D
    • uint32_t d = a * b * c; 用 32 - bit 的整数类型uint32_t来存储 a、b、c 三个 32 - bit 整数相乘的结果,明显会发生溢出,因为三个 32 - bit 整数相乘结果很可能超出 32 - bit 的表示范围,所以这种写法不正确。

综上,正确答案是 C。

指针

不定项选择题 (必答) 以下关于指针说法正确的有 int(p)[n]p为指向一维数组的指针,这个一维数组有n个整型数据 B intp();函数带返回指针,指针指向返回的值 int*p[n];表示指针数组,每个元素均为指向整型数据的指针

  1. 分析选项 A
    • int (*p)[n] 这种声明形式中,p 是一个指针。括号将 *p 括起来,表明 p 首先是一个指针,它指向的是一个包含 n 个 int 类型元素的一维数组 。所以 p 为指向一维数组的指针,这个一维数组有 n 个整型数据,选项 A 说法正确。
  2. 分析选项 B
    • int *p(); 这种声明形式,() 优先级高于 *,所以它表示 p 是一个函数,该函数的返回值是一个指针,这个指针指向 int 类型的数据,而不是说指针指向返回的值这种模糊不准确的表述。准确说法应该是返回的指针指向内存中存储的 int 类型数据。所以选项 B 说法错误。
  3. 分析选项 C
    • int *p[n] 中,[] 优先级高于 *,这表明 p 是一个数组,数组名为 p,数组有 n 个元素,每个元素的类型是 int *,也就是每个元素均为指向整型数据的指针,这就是指针数组的定义。所以选项 C 说法正确。

综上,说法正确的是选项 A 和选项 C。

extern c

extern”C”声明的主要目的是什么? 增加代码的执行速度 B 防止name mangling 定义全局变量 允许C++代码调用C语言代码

  1. 分析选项 A
    • “extern “C”” 声明的主要目的并非增加代码的执行速度。它主要解决的是语言之间的函数调用兼容性问题,而不是对代码执行效率进行优化。所以选项 A 错误。
  2. 分析选项 B
    • 防止 name - mangling(名字改编)确实是 “extern “C”” 声明带来的一个效果。在 C++ 中,为了支持函数重载等特性,编译器会对函数名进行改编(name - mangling),使得函数名在目标文件中有独特的表示。而 C 语言没有函数重载,函数名没有这种改编机制。当 C++ 要调用 C 函数时,加上 “extern “C”” 声明,能让 C++ 编译器按照 C 语言的方式处理函数名,避免名字改编带来的不匹配。但这不是其最主要目的,其主要目的是为了实现 C++ 与 C 语言代码的交互调用 。所以选项 B 不准确。
  3. 分析选项 C
    • “extern “C”” 声明和定义全局变量没有直接关系。定义全局变量通常是通过在函数外部声明变量来实现,“extern “C”” 主要用于处理不同语言间函数接口的问题。所以选项 C 错误。
  4. 分析选项 D
    • C++ 语言引入 “extern “C”” 声明的主要目的就是允许 C++ 代码调用 C 语言代码。由于 C 和 C++ 编译器对函数名的处理方式不同(C++ 有名字改编,C 没有),如果不使用 “extern “C”” 声明,C++ 代码在链接 C 语言编写的库函数等代码时,会出现链接错误。“extern “C”” 告诉 C++ 编译器,按照 C 语言的规则去处理被声明的函数,从而实现 C++ 代码对 C 语言代码的调用。所以选项 D 正确。

综上,答案是 D,即 extern “C” 声明的主要目的是允许 C++ 代码调用 C 语言代码。

内联

使用宏来封装重复的代码片段比内联函数更节省代码空间,因为它不会增加额外的函数调用开销 错误 正确 B

  1. 分析宏和内联函数的原理
    • :宏是一种简单的文本替换机制。在预处理阶段,预处理器会按照宏定义,将代码中出现的宏标识符直接替换为对应的代码片段。例如,若定义#define ADD(a, b) ((a)+(b)),代码中出现ADD(3, 5),预处理器会直接把它替换成((3)+(5)) 。由于只是文本替换,宏在使用时不会产生函数调用的开销,但是当宏被多处使用时,相同的代码片段会在目标代码中多次出现,导致代码体积增大。
    • 内联函数:内联函数是在编译阶段,编译器会将内联函数的调用处直接替换为函数体的代码。与宏不同,内联函数具有函数的特性,比如有类型检查等。虽然它也避免了函数调用的开销(像普通函数调用会涉及压栈、跳转、出栈等操作),但它不会像宏那样在多处使用时导致大量重复代码。编译器在处理内联函数时,会进行一定的优化处理,不会使代码过度膨胀。
  2. 分析题目表述
    • 题目说 “使用宏来封装重复的代码片段比内联函数更节省代码空间”,但根据上述宏和内联函数的原理分析,宏因为是简单文本替换,在多处使用时会使相同代码多次出现,导致代码空间增大;而内联函数不会出现大量重复代码的情况。所以宏并不比内联函数更节省代码空间。
    • 题目中 “因为它不会增加额外的函数调用开销” 这句话,虽然宏确实不会增加额外函数调用开销,但这与它是否节省代码空间并无直接关联,且从实际情况看,宏在节省代码空间方面不如内联函数。
  3. 得出答案
    • 综上所述,该表述是错误的,应选择 A 选项。

volatile

关于volatile关键字在C语言中的使用,哪些描述是正确的? 它经常用于嵌入式系统中与硬件寄存器的交互 它保证了变量的原子操作 它用于防止编译器对代码进行某些类型的优化 它指示编译器该变量的值可能会在外部环境中改变

  1. 分析选项 A
    • 在嵌入式系统中,硬件寄存器的值可能会随时被硬件电路改变 。例如,某个定时器寄存器的值会随着时间计数而变化,或者外部中断触发后相关状态寄存器的值会改变。volatile关键字用于告诉编译器,对应变量的值可能会以编译器无法预测的方式(即通过硬件交互等外部方式)发生改变。所以在嵌入式系统中与硬件寄存器交互时,经常会使用volatile关键字来修饰与这些寄存器映射的变量,以确保编译器正确处理对这些变量的读写操作。因此,选项 A 描述正确。
  2. 分析选项 B
    • volatile关键字并不能保证变量的原子操作。原子操作是指不可被中断的操作,在多线程或多任务环境下,对共享资源的某些操作需要保证原子性,例如对一个变量的自增操作,如果不保证原子性,在多线程同时访问时可能会出现数据竞争等问题。而volatile主要作用是防止编译器优化导致对变量的错误读写,和原子操作没有直接关系。像i++这样的操作,即使i被声明为volatile,也不能保证其原子性。所以选项 B 描述错误。
  3. 分析选项 C
    • 编译器在优化代码时,可能会对一些变量的读写进行优化。例如,如果编译器发现一段代码中多次读取同一个变量的值,且在这段代码中该变量没有被修改,编译器可能会只读取一次该变量的值并缓存起来,后续使用时直接从缓存中获取,而不再次从内存读取。但如果该变量的值可能会在外部(如被硬件或其他线程改变),这种优化就会导致程序读取到错误的值。volatile关键字用于防止编译器进行这类可能导致错误的优化,确保每次对volatile修饰的变量的读写都是真实地对内存进行操作。所以选项 C 描述正确。
  4. 分析选项 D
    • volatile关键字的本质含义就是指示编译器,被它修饰的变量的值可能会在编译器控制范围之外(即外部环境)发生改变。比如在多线程环境下,一个线程修改了共享变量,对于另一个线程来说,这个变量的值就像是从外部改变的;在嵌入式中硬件改变寄存器映射变量的值也是类似情况。编译器根据这个指示,会正确处理对该变量的读写操作,不会因为优化而产生错误。所以选项 D 描述正确。

综上,正确答案是 ACD。

union

在C语言中,对于联合体(union)的使用,哪些陈述正确的? 联合体中的所有成员共享内存空间 B 联合体可以用于实现类型强制转换 联合体的成员可以同时存储不同的值 联合体的大小至少为其最大成员的大小

  1. 分析选项 A
    • 在 C 语言中,联合体的本质特点就是所有成员共享同一块内存空间。这意味着在联合体所占用的内存区域内,不同时刻可以存储不同成员的数据,但这些成员都是从同一起始地址开始存放的。例如定义一个联合体union data { int i; char c; };ic会共享同一块内存。所以选项 A 的陈述是正确的。
  2. 分析选项 B
    • 联合体可以用于实现类似类型强制转换的效果。由于联合体成员共享内存,当把一个类型的数据存储到联合体的某个成员中,然后从另一个不同类型的成员去读取时,就相当于进行了一种特殊的类型转换(但要注意这种转换需要谨慎使用,因为它绕过了正常的类型检查机制)。比如上述union data,先给i赋值,再从c读取,就实现了一种数据在不同类型视角下的转换。所以选项 B 的陈述是正确的。
  3. 分析选项 C
    • 因为联合体所有成员共享内存空间,在某一时刻,只能有一个成员的值是有效的。当给一个成员赋值时,会覆盖原来存储在共享内存中的数据,所以不能同时存储不同成员的值。例如在union data中,给i赋值后,c原来的值就被覆盖了,此时c的值是i值按字符类型解释后的值,而不是c原来可能有的值。所以选项 C 的陈述是错误的。
  4. 分析选项 D
    • 联合体的大小取决于其最大成员的大小。因为联合体要为其最大的成员提供足够的内存空间来存储数据,其他较小的成员就共享这块足够大的内存区域。比如union { int i; char c; double d; } u;,由于double类型是最大的,占 8 个字节,所以这个联合体的大小就是 8 个字节。所以选项 D 的陈述是正确的。

综上,正确的选项是 A、B、D。