在大二下的那个学期,我开始找实习、投简历、约面试。对于大多数浙江大学计算机学院的同学来说,大二就开始找实习确实很早,但当时我的想法是,人需要去尝试,才知道我以后想要过怎么样的人生。这也是我大一暑假就选择去CMU交流,大二就参加科研的原因。我想尽量早接触到业界的项目,通过实习,一方面提升自己的能力,另一方面更早接触到人生的选择支。
机缘巧合的Offer
一开始我只投递了杭州的岗位,因为考虑到假如大三开学后要继续实习的话,可能偶尔需要返校,地理位置太远的话不利于往返。
我投递的次数并不多,但是范围却很广,有前端、后端、机器学习、量化开发,甚至还面了我以前没接触过的嵌入式(甚至还拿到Offer了),有阿里、华为、字节等大厂、也有一些小厂和创业公司。
一个很难受的地方是,大厂很多岗只收2026届的实习,2027届可选的岗位很少。还有的联系到HR后,HR了解到我大二不能转正的背景后,根本就不给我面试机会。
面试有次面完一家杭州的量化私募后,HR在微信上给我发了这样的一段话:
Hello,昨天技术负责人跟你聊完对你的评价很高,说你已经达到了优秀毕业生的水平,非常难得,但是你毕竟大二马上大三,只有暑期可以实习,后续很难保证实习时间,我们目前实习生 hc比较少只打算招一个同学,面试官还是更倾向于大三暑假或者研二暑假的,因为可以一直实习,后续就留用了,但是我们面试官再三跟我说,你真的很优秀,如果明年你还打算找实习,还考虑我们的话,到时候可以直接给你发 offer。
他们表示由于HC有限,只考虑能转正的大三和研二,所以收了另外一位候选人,但是认为我很优秀,如果之后还考虑的话可以直接发Offer。
我的第一反应是受宠若惊,但是也有点遗憾,自己由于非技术的原因错失Offer。
后面那个HR表示,要给我内推到另外一家在上海的百亿量化私募,稳博投资,我抓住了这个机会,顺利通过了面试。对比了其他几个Offer的薪资水平和工作内容,决定还是选择了稳博投资。
于是,在大二暑假的时候,我从杭州前往上海,在浦东找了个房子租下,开始了我的第一次实习生活。
在稳博投资的这些日子

公司里带我的mentor是一个很好的领导,为人真诚好沟通,无论是技术上的知识还是在公司里处事的方法,都愿意告诉我。
同事的相处氛围融洽,每次和mentor还有几个同组的同事一起吃午饭的时候,我们会聊投资,聊公司的八卦,聊AI,聊职业发展。
时间过得很快,在last day的那天下午,我办完了离职流程,和mentor道别的时候,他说送我一下,结果跟着我下电梯、过马路,一直送我到地铁站闸机口,路上和我说他了解到的,未来如何在这个行业发展的一些见解。
我一直以为工作上的朋友的交情都会比较淡,但是离别了才发现我的mentor是这样一个让我珍惜的朋友。
技术成长
出于保密原因,本部分有些内容经过修饰。
内部日志库替换
在公司里面做的第一个活是替换公司内部使用的日志库,由于之前的日志库缺少人维护,并且性能不好,因此mentor让我调查一下有哪些低延迟的C++日志库。
这个任务本身并不算困难,但由于刚刚进入公司,我花了大量的时间了解公司的编译构建流程,包括虚拟环境,CMake脚本,monorepo的依赖构建等。
之后我对比了fmtlog、spdlog、quill等日志库在性能、线程安全方面的特点,写了一些benchmark代码,测试了调用延迟和吞吐量。最后决定使用quill代替原本的旧日志库。
为了方便公司内遗留代码的快速迁移,我还开发了与公司内部库具有相同接口的quill的兼容层,只需要替换头文件,做少量改动即可更换到新的logger。
高频因子parquet存储
公司高频策略在盘中会产生海量的Tick级别因子数据(每日百GB量级)。原有的存储方案将不同类型的数据写入。不仅存储占用巨大,而且不利于后续的读取、分析和回测带来了极大的不便。
我采用Parquet库的流式写入API,避免将整个文件内容在内存中构建,降低了内存占用。
新的Parquet存储方案相比原有的.csv,存储空间减少了90%以上,降低了磁盘存储成本。统一的数据格式和列式存储特性,便于下游的数据分析任务。
引入Parquet读写功能后,又遇到了新的问题,整个动态库(.so)强制依赖libparquet.so。公司部分使用这个模块的环境没有libparquet.so,虽然也没有用到这个新增的功能,但是因为动态库依赖问题,导致无法运行。
解决方案是引入一个中间层xxx_parquet.so,然后让xxx.so使用dlopen引入xxx_parquet.so,通过xxx_parquet.so间接依赖libparquet.so,这样就可以在安装了parquet的环境下使用新增的parquet因子存储功能,在没有安装parquet的环境也能正常使用原有的功能。
共享内存延迟优化
有多个writer进程和多个reader进程,需要跨进程通信收发因子,这个部分在全链路上的延迟占比较高,因此需要优化。目前来说,因子数据在共享内存里的排列方式是这样的:
合约0:[因子0] [因子1] [因子2] ... [因子N-1]合约1:[因子0] [因子1] [因子2] ... [因子N-1]合约2:[因子0] [因子1] [因子2] ... [因子N-1]...reader采用轮询合约的方式,检测因子是否更新,这样延迟很高。和mentor讨论下,有两种方法,一种是直接用消息队列,手写个ringbuffer,然后收发消息。但是这里面有个问题是消息的长度是不固定的,因为每个writer写的因子数量不同。
后来使用的方案是,在不改变原本因子内存布局的情况下,给每个writer开辟一块空间用做通信,然后reader进程不直接扫因子,而是扫WriterMetaData,这样虽然依然有轮询,但是延迟大大降低了。
之后还发现,有很多MQ库支持基于共享内存的可变长消息,如Aeron,但是没有继续基于这个方案开发了。
总结
回顾在稳博的这几个月,可以说是我人生中一段极其宝贵和充实的经历。
我不仅在技术上得到了快速锻炼,更重要的是,我不仅仅是学会了如何“写代码”,更是学会了如何“解决问题”。我学会了如何在庞大而复杂的系统中定位瓶颈,如何权衡不同的技术方案,以及如何写出稳定、高效且易于维护的代码。
但对我影响更深的,是“人”。我非常幸运能遇到一位如此真诚和悉心指导我的mentor,和几位可爱又有趣的同事。
这次实习也完美回答了我最初的困惑。这段在上海的经历,它让我明白了优秀工程师的标准,也让我对自己未来的道路更加笃定。