小彭老师面试经验
最近好像很流行面经……小彭老师也来写一下。
泽森科工 (2021.12.07)
- 张剥士在taichi论坛里主动找上来联系,表示我们很欣赏你在太极的伟大贡献,希望和我们泽森一起创造更好的新产品(什么挖墙角)
- 张剥士在微信电话里开始语音面试了,上来就要求实现sqrt函数(牛顿迭代法即可,小彭老师不小心写错,有死循环,张剥士表示不要紧,这个你肯定现实中很容易调试改正的,主要是牛顿迭代需要导数的解析式,容易写错,naive的初学者也可以选择写二分法,余切法等)
- 有没有了解过雷神3的快速sqrt函数?(知道,但好像是神奇的二进制运算,没有深入了解过为什么这样做)
- 知道float的布局吧(23位底数mantissa,8位指数exponent,1位符号sign)
- 而牛顿迭代法需要近似,sqrt最好的近似,实际上就是把exponent位除以2,对不对?(小彭老师恍然大悟,怪不得需要右移1位,原来是让exponent除2,雷神的sqrt因为有良好的近似初值,接下来就只需要一两步就能收敛)
- 接下来脑筋急转弯:我有从 1 到 100 这 100 个数,组成超长的数组,随机排列,现在这里面缺少了一位(比如 1 2 3 4 5 7 8 9 10 就是缺少了 6)
- 现在,我一边报数你一边操作,我报完的那一刻,你必须立即告诉我,缺失的那一个数是什么,并且不允许使用记事本(也就是要求常数空间复杂度,很简单,你报的同时我往一个计数器里累加求和,然后算出来的总求和和 5050 相减,得到的就是缺失的那个数了,比如 1+2+3+4+5+7+8+9+10 - 55 = -6,那么就知道缺的是 6)
- 如果我是缺了两个数呢?(那我弄两个计数器,一个求和,一个求积,然后联立二元二次方程)
- 我提供一个生成0到1区间均匀随机数的函数frand,如果我指定一个分布,比如要求高斯分布,如何生成符合这个分布的随机数生成函数?(frand是均匀随机数函数,要映射到指定密度分布的函数,可以先对该概率密度函数pdf求积分,得到累积概率函数cdf,求其反函数icdf,然后使用icdf(frand())即为符合该分布的随机数生成函数,例如对于高斯分布来说,他的cdf函数是erf,也就是erfinv(frand())可以生成高斯分布的随机数)
- 写一个函数,生成一个圆形内均匀分布的点(初学者naive的写法:先生成一个正方形的均匀分布点,然后判定是否在半径1内,如果在则保留,否则重新生成,直到生成在半径内;小彭老师的写法:首先求圆形周长S=2pir的icdf,然后blahblah,最终是r=icdf(frand()),然后再随机一个theta=frand()*2pi,x=rcostheta,y=rsintheta)
- 假如我指定的函数不是一个解析式,而是一个离散的密度数组,怎么生成符合该分布的随机数?(首先求该密度数组的前缀和prefix sum,然后r=frand(),查找第一个大于r的下标位置,该下标即为要生成的随机数,如果有一个值列表,则用下标访问这个值列表,如果需要连续变化,可以在第一个大于等于r和第一个大于r的两个元素之间按r多出的余数插值)
- 可是遍历去查找第一个大于r的位置可能会很慢,怎么加速?(二分法搜索该离散的密度数组,可以用标准库的lower_bound)
事后
- 后来小彭老师在职期间,张剥士一顿吹捧:你是我们这里唯一一个通过硅谷级人才测试的员工,一见到新员工就吹小彭老师,你看看人家小彭老师。
- 张剥士还画出了期权大饼,讲了一系列我听不懂的虚拟期权概念金溶术语后,总之翻译一下就是鼓励小彭老师奋斗,奋斗的公司股价涨了就能分红,但直到最后都没有兑现,而且据了解上市公司才有期权……
- 小彭老师看到张剥士这么重用,以为毕业以后肯定给转正了,所以就没有考研,没有去看校招,也没有接受学校的对接,忘我地伺候zeno工作,打算一毕业就直接去深圳伺候张剥士。
- 小彭老师在 zeno 的工作基本完成了,找不到有什么需要做的,张剥士突然开始反复粗鄙语言羞辱小彭老师,pua小彭老师,曰“你是程序员钓丝思维”“根本不懂我们的实际需求”“你们钓丝程序员是最不懂美术素养的”,看在剥士还在发工资的份上,就没有理它。
- 小彭老师终于毕业了,表示可以转正,这时张剥士却出尔反尔,各种推脱,曰“资金困难”“请不动小彭老师”“等我9月拉到投资好不好”,然而至今没有反应,转头就看到朋友圈在发布外包jd。
- 小彭老师反复“哀求”张剥士“开眼”,张剥士反复推脱后,终于表示:看在你对zeno也有“感情”了,如果你还想为我们zeno继续“贡献”的话,可以开出5k低价重新雇佣小彭老师,等“拉到投资”“资金不困难”时,再给小彭老师“补贴”回原来的16k工资,同时看到张剥士在群里炫耀他船新的等身手办(射)。
- 张剥士自我感动演的惟妙惟肖,小彭老师考虑到张剥士期权大饼的前车之鉴,就没有相信剥士的“好意”。
- 小彭老师只好以本科应届生身份开始寻找工作,其中hr经常拷问“我看你1月到现在都是没有工作的状态?”“说一下离职原因”造成很大的麻烦。
- 小彭老师展示“工作经验”时,hr总是反复强调“我看你才刚毕业呀?”,认为“实习经验”不算,导致小彭老师在张剥士的经验几乎作废,而应届生优惠又被张剥士耗掉,然而,它只要看一下zeno贡献排行榜就知道,小彭老师的贡献是第一的。
- 所有泽森员工都需要熟悉小彭老师反复迭代过的节点系统,才能开始他的zeno开发,更何况小彭老师还贡献了包括节点编辑器、Python bindings、实时三维视窗、多进程通信、磁盘缓存、对象序列化、实时optix、shader节点、GLSL codegen、刚体仿真、Prim属性系统、ZFX编译器、PrimPrim 邻居查找、插件系统、ABC 加载器、几何节点、VDB 节点、流体子图、Blender 插件、OpenSubdiv 集成、libigl 集成、CI/CD 工作流等诸多功能。
- 张剥士发知乎文章爆论:我们不需要程序员!只需要偶尔招两个厉害一点的实习生,做个一两年,把软件做完以后,就不需要他们了,显然小彭老师就是这样一个一次性又特别好用的实习生。
某 Unity 小厂 (2024.08.12)
- 一个看起来可能是老板的人物出面迎接,进入一个独立的会议室开始面谈。
- 之前玩过哪些游戏?(主要是肉鸽和模拟经营,着重介绍了杀戮尖塔和KSP,因为他们是Unity游戏,还介绍了制作以撒的结合模组的经历,基于Lua API的)
- 面试官说现在 KSP2 没有卡顿了,因为 GC 优化了
- 问学过Unity吗?(了解一点8,之前做过KSP模组)
- 问那么你的Unity版本?(糟糕,只能装傻了,因为其实很久没打开过了,而且新电脑里也没有下Unity)
- 表示你应该多看看Unity官方文档,特别是英文的文档
- 问PBR流程?(分为Lambert和Cook-Torrance两个模型,合并起来,lambert很简单的全方向随机漫反射,介绍了NDF, GDF, FDF三个函数的物理意义)
- 问Cg着色语言?(Unity自己的着色器语言,不熟悉,表示之前写的都是GLSL,但我知道所有引擎都有一个钦定语言,在不同平台会翻译生成不同的目标语言比如GLSL,Unity也支持写GLSL,但部分功能特性API有所缺失,所以主要还是写Cg,在GL后端会自动翻译生成GLSL的)
- 小彭老师提起为了支持UE,特别做了HLSL后端(面试官锐评了煞笔DX不跨平台,现在游戏主要还是基于OpenGL在做,UE是因为微软给钱了才钦定HLSL为主语言,但实际上也有翻译到GLSL的OpenGL后端)
- 问到了PBR计算量大,移动端如何解决性能问题(小彭老师表示不熟悉,之前做的都是桌面端的渲染,只知道移动端好像是tile-based rendering的)
- 表示我们做的是Unity的微信小游戏,因为最近推出的WebGL后端(小彭老师:怪不得可以看到最近这么多微信小游戏)
- 问了 Lua(小彭老师表示熟悉,因为之前写的 Lua 脚本实现以撒模组)
- 我们不是要写 Lua,而是要调用 Lua 的 C 接口哦!(小彭老师表示也熟悉,因为之前参与的一个以撒模组项目 IsaacSocket 就是基于 C# 客户端,动态往以撒进程注入 C++ DLL,劫持以撒的各种回调,然后暴露出 Lua 接口,创建了一系列对游戏内 Lua 脚本可见的 C++ 函数,实现以撒 API 的扩展,需要 lua_tonumber 来获取参数,lua_pushnumber 来返回值等)
- 问了 lua_State 是什么(小彭老师:含有 lua 的堆栈全局变量等上下文信息,可以认为是一个线程,每个线程各自独立)
- 问如何创建一个 UI 界面?选择不同的服务器,登录(使用json+http实现rpc,获得一个列表,然后设置列表,即可利用MVC创建出界面)
- Unity 的 UI 系统会写吗?(不会,只知道Qt有QListView控件,Unity可能也有吧?)
蔚来小汽车 (2024.08.15)
1面(疼逊会议语音沟通)
- TODO 还在写
2面(疼逊会议语音沟通)
- TODO 还在写
没有3面了,原因不明。
其域科技 (2024.09.03)
1面(疼逊会议语音沟通)
- 要求自我介绍(主要介绍了泽森在渲染的工作,shader节点等,不仅负责了Qt+OpenGL实时可视化,后来还加入了OptiX实时光追支持)
- 小彭老师主动介绍zeno的shader节点支持输出为GLSL、HLSL、CUDA三种后端格式,都是codegen,其中CUDA后端采用NVPTX编译,供OptiX使用
- 介绍PBR流程(albedo, metallic, roughness 等,还有法线贴图的烘烤,IBL 光照等)
- 问到了PBR计算量大,移动端如何解决性能问题(小彭老师表示不熟悉,之前做的都是桌面端的渲染,只知道移动端好像是tile-based rendering的)
- 介绍了公司内部是有嵌入式(三维捕获设备)和电脑端(用于查看三维捕获结果的App),嵌入式设备只需要做初步的数据处理,基于国产GPU(但是CUDA接口),初步处理后回传用户电脑,电脑端可以假定用户是NVIDIA显卡(也是CUDA接口)用于更massive的后处理和渲染可视化
- 小彭老师主动介绍在taichi three中手搓软光栅渲染器的经历(支持ssr, ssgi, taa等)
2面(LeetCode在线面试,有共享代码编辑器)
- 要求小彭老师自我介绍(简历他已经拿到看着了,就不复读名字和学历了,所以主要介绍项目,在zeno中,小彭老师担任渲染和节点系统的开发,介绍Qt+OpenGL视窗可视化,介绍节点系统类似于低代码的优势,简要介绍taichi, nbodysolver, co_async……炫耀高效的协程,并安利小彭老师比站频道,cpp公开课)
- 面试官表示不熟cpp20,提出要以cpp17为基础(没关系,我们在zeno和taichi也都是用cpp17)
- cpp有哪四大cast(static_cast, dynamic_cast, const_cast, reinterpret_cast)
- static_cast vs dynamic_cast适用场景(子类转基类总是static_cast,一个基类指针,如果不能确定是不是子类,那就需要dynamic_cast,如果失败会返回nullptr,记得检查!如果dynamic_cast引用则失败抛出bad_cast异常)
- dynamic_cast背后RTTI原理(实际上是比较typeid是否兼容,typeid指针存在虚函数表里,只有带有至少1个虚函数的称为“多态类”的类型会生成RTTI信息,顺便推销了为什么llvm选择开启-fno-rtti和-fno-exception,是避免二进制膨胀)
- llvm关闭了RTTI,那他是如何变相实现dynamic_cast的?(我知道,是使用枚举类型!定义了getType虚函数,返回枚举,用于比较)
- 可是如果D2继承D1继承B,如何保证D2也可以cast为D1?(完了,llvm源码看的不仔细,只能用以撒的做法盲猜一个:是B有getD1和getD2两个虚函数!他们默认返回nullptr,只有D1会重写getD1,D2会重写getD2,内部都是简单的返回this)
- const_cast未定义行为(本来是const的不能去掉const后访问,展示了成员函数复用两个data的用法)
- const_cast后返回指针没问题,面试官改成返回引用,涉及解引用,问这样还安全吗?(迷惑性很强的问题,被小彭老师识破:非const地解引用const变量是未定义行为,即使没有读取,就和end迭代器不能解引用一样,即使没有读写访问)
- const vs constexpr变量区别(const可以有地址,而constexpr不一定有)
- reinterpret_cast用法,什么情况下安全(小彭老师写经典举出int和float之间bit-cast的案例,介绍值转换和按位转换的区别,勾引面试官上钩,他肯定认为小彭老师不知道reinterpret_cast不能做int和float的指针转换,于是发问:)
- 面试官写出int和float指针强转并解引用的代码,问这个安全吗?(上钩了,小彭老师即答:未定义行为,不能用reinterpret_cast转int和float指针,是因为strict-aliasing认为int和float不兼容,转换后解引用即UB,面试官说似乎只有GCC会用这个,小彭老师答:GCC默认开启,可以用-fno-strict-aliasing关闭,MSVC不利用此规则优化,小彭老师顺便介绍指针别名与优化的关系,即为什么提出strict-aliasing规则的原因)
- 小彭老师强调int和unsigned int是兼容,面试官就问为什么char可以?(reinterpret_cast有破格允许的特例,char,unsigned char,std::byte与所有类型都兼容,转为char指针后可以随意访问任意类型的内存,指出是cpp标准为了方便我们网络收发包时,解序列化的方便性)
- 如何实现真正安全的int和float转换?(使用memcpy或cpp20的bit_cast是安全的,并推销memcpy vs bit_cast区别:虽然cppref上bit_cast的“参考实现”是memcpy,但得益于其基于编译器开洞__builtin_bit_cast,让bit_cast是constexpr函数可编译期确定)
- inline关键字作用(non-odr external linkage,建议头文件中就地定义的函数都加inline,否则多个.cpp文件中产生多个定义,触发链接器odr报错,而inline则是非ndr的外部链接)
- 面试官问inline函数多个文件中看到的函数体定义不同,但函数参数列表相同,会怎么样?(未定义行为,标准要求必须保证所有.cpp翻译单元看到的inline函数定义一致,否则编译器往往并不报错)
- 小彭老师推销:这导致你在cpp文件中定义非POD类会被坑,因为类体内就地定义的成员函数,默认是inline的,包括默认构造函数,当时导致zeno调试了半天(面试官表示他之前集成一个开源库也遇到这个坑,小彭老师表示可以套匿名namespace解决)
- push_back vs emplace_back(2重载vs万能引用+变长参数,emplace可以就地带任意参数构造你的元素类型,emplace的额外好处是触发explicit的构造函数而无需显式写出类名,也带来了危险,所以我在课程中都不推荐使用,如需避免移动可以
vector<unique_ptr<T>>
,这还能使扩容时也不触发移动) - 什么情况下不会移动?面试官似乎在试探我是否了解 vector 扩容原理(只有当size>capacity时才会触发,每次触发扩容时gcc增加到2 x size,msvc则是1.5 x size,总共2n次操作,好处是保证了总体O(n)复杂度,我们建议知道长度的情况下,可以调用reserve提前预订100的capacity,这样只有推入第101个元素才会扩容到200,小彭老师顺便推销了vector, deque, list的区别,迭代器失效原因)
- 还问了万能引用和完美转发的原理(引用折叠)
- 什么是POD(基础类型、指针、无用户构造函数的纯基础类型组成的结构体)
- 小彭老师反向提问:vector超缓存大resize导致memset性能影响?(拿出我的tbb课程的parallel_filter案例,利用pod模板,面试官表示熟悉缓存,不用提问了)
- 面试官提问PIMPL模式(写C::Impl给他看,又问PIMPL的目的是什么,一开始答:分离定义,加速编译,问还有什么作用吗?保持abi稳定,不用重新编译依赖者,可用于插件热装载)
- 小彭老师反向提问:知道为什么C的构造函数里会需要unique_ptr类型析构函数的信息吗?(面试官:因为析构函数需要知道sizeof!=0,小彭老师:但是,C的析构函数被转移到了C.cpp,为什么默认构造函数初始unique_ptr为nullptr,仍然编译出错?面试官支支吾吾,小彭老师:因为构造函数可能抛异常,导致之前初始化过的成员析构,再次向他兜售cpp异常魅力时刻)
- q指针 vs d指针(Qt实现cow和pimpl的细节,因为平时没注意看qt头文件源码,小彭老师唯一栽跟头的题,说是看到简历写的Qt就问了,并表示之前zeno里主要是用PyQt)
- 小彭老师反向提问:c++11 string为什么打破abi(因为c++98 string采用cow不符合线程安全模型常识“共享读安全”)
- make_shared vs shared_ptr new的区别(只需一次性分配,无需再new SpCounter,原理是operator new+placement new)
- 小彭老师反向提问:知道为什么shared_ptr的构造函数没有noexcept吗?(就是因为要new SpCounter)
- 是operator new还是new_allocator?(默认是new_allocator,可用allocate_shared替换掉)
- 小彭老师反向提问:知道make_shared_for_overwrite的区别吗(采用default-init,new表达式后没有了括号,可避免POD类型0初始化,面试官表示这个我都没了解过)
- CRTP(奇异递归模板模式,最初用于取代虚函数,要实现获取子类指针self,推销了cpp模板类延迟实例化成员函数的机制,其实CRTP还能用于实现原型模式和visitor模式,因为时间关系没来得及说)
- 虚函数为什么低效?虚函数如何优化(因为需要call一个指针,cpu无法预知,使用final,不用读取虚函数指针表,然后说明了vecB拆成vecD1和vecD2更高效)
- shared_from_this实现(本来要我写的,因为时间来不及写,但是小彭老师表示出过手搓shared全家桶的视频,面试官只好放心)
- 你对哪个领域感兴趣?(当然是渲染,我从小做到大,并表示性能优化这一块也尽管请教小彭老师,有cuda和simd优化客户经验)
- 还有什么问题对本公司吗?(时间不多了,就问要不要现在开始学习三维重建,或者可以试试看客户端,说岗位选择可以之后和hr沟通,并向我推销了3dgs,球鞋函数,小彭老师:好多paper呀?看了几个效果图,典中典之Ours永远是最好的)
3面(疼逊会议语音)
又介绍了zeno和taichi,问了点云相关问题,很快结束了。
4面(拉投资的合伙人亲自线下见面)
- 商场全部关闭了,星巴克还开着
- 介绍一下你自己8(又是 zeno)
- 问了上次的面试官怎么样呀(我看了推荐的 3dgs,发现是把扫描出来的点云,逐步转换为椭圆球渲染)
- 你知道,现在主流图形学都是三角形网格,那么这种点云要如何渲染呢?(点云的话可以先用 marching cube 转三角形面,zeno 的流体就是这样的)
- 但是性能不够,不能保证实时(可以用屏幕空间流体,但是效果一般,我们做电影的需要高质量的离线渲染,不太注重实时性,实时椭球的话,也可以用光追,算射线与椭球表面求交即可,不过英伟达的硬件加速只有三角形的,但是 GPU Gems 上的 BVH 加速求交代码用于椭球也是可以拿来参考的)
- 拿出一台 3D 扫描机器,说你知道我们这个测绘机器是如何定位的吗(惯性制导,里面有加速度计,求二阶积分就可以得到位置)
- 但是这样时间长会有累计误差,如何消除误差?(可以用 GPS 定位,也可以通过光学摄像头扫描的结果,确定自己的相对位置,必要时可以贴几个识别纸片在墙上方便程序检测)
- 是的,实际上我们在户外会用 GPS 定位,矿洞里就会用光学的定位方法,消除惯性制导的累计误差。
- 那么 GPS 卫星定位的原理你知道吗?(三颗 GPS 卫星发出不同相位的电磁波,因为光速有限,移动设备通过检测相位差,就知道自己距离三颗卫星的距离,然后三个距离就能唯一确定一个点)
雅科贝思 (2024.09.03)
- 介绍一下自己(又介绍zeno是一款CAD类的项目)
- 哈希表(介绍unordered_map基于链表法,标准库的hashint是恒等函数,absl的实现基于开放地址法更高效,java也是链表法,但链表过长会转换为红黑树等)
- 红黑树(五大规则,为什么这五个规则能保证不超过2倍深度,同时比二叉平衡树高效)
- 红黑树左旋右旋操作(右儿子替换父亲,父亲变成左儿子)
- 面试官透露:实际上红黑树就是一个4阶树,你想想看(确实,如果把红黑两层看作一层的话,那么实际上是一个4阶平衡树)
- OpenGL 渲染管线(3d顶点数据 -> vert shader (矩阵变换) -> 光栅化+插值+深度测试 -> frag shader (前向着色) -> G-buffer -> 延迟渲染 (后向着色) -> 后处理 -> 屏幕)
- 来面试的人中,你是我见过技术最好的一个,之前一个硕士,上来哈希表就支支吾吾挂掉。
事后:已录取,正在上班ing……又是做 Qt + OpenGL 的项目