导语 | 本文通过介绍实现Protobuf共享字段Guard,并将其应用于中控/召回场景,并获得了显著CPU/时延收益。即使不使用Guard,希望本文的经验和思路也能为读者带来一些帮助和参考。

引言

在推荐系统中,用户级的字段常常需要贯穿整条链路,例如,实验参数,行为序列,用户画像等等。

召回/过滤/排序等模块都需要用户特征,此时最好的方法自然是从请求开始时一次性获取,然后一路透传下去。此前笔者的写法常常是:

const GetRecommendReq & oReq;//from rpc
RankReq oRankReq;
oRankReq.mutable_user_portrait()->CopyFrom(oReq.user_portrait());

这样的透传自然有好处,例如,下游如果需要用户特征,不需要再每个请求去请求一次。尤其是上游发起分包时,透传用户级别特征能够显著减少下游获取用户特征的RPC开销。

然而,RPC开销减少了,再得陇望蜀想一想,是否能直接省去这个CopyFrom的开销呢

我们知道,protobuf提供了Allocated/Release系列接口,通过直接转移指针所有权的方式消除Copy或Swap的开销。

换个思路,如果不是转移指针所有权,而是借出指针所有权,就能够实现共享字段了。所谓借,其实就是在使用前把字段指针转移,但在使用结束后立刻收回(收回所有权以防被delete)。而这正是经典的Guard抽象。

当然,即使不使用Guard,相信上面这个思路已经足够提供一些帮助了。我们可以直接使用pb的接口实现:

const GetRecommendReq & oReq;//from rpc
GetRecommendReq & oMutableReq =  const_cast<GetRecommendReq &>(oReq);
RankReq oRankReq;
oRankReq.set_allocated_user_portrait(oMutableReq.mutable_user_portrait());
Client.Rank(oRankReq);
oRankReq.release_user_portrait();

对于一些更复杂的操作,例如我想要拷贝部分字段,共享部分字段,修改部分字段(分包的场景),我们在下文给出了我们的解决方案。

设计

我们的Guard提供了两个接口,分别是Attach和Detach,接口如下。实现通过pb的反射机制,使得release和set_allocated能够相互绑定,实现Guard析构时回滚。

void AttachField(Message* pMessage, int iFieldId, Message* pFieldValue);Message* DetachField(Message* pMessage, int iFieldId);
  • AttachField:先把字段set_allocted借给pMesage,Guard析构后回滚释放,以防双重delete。

  • DetachField:先把pMessage的字段release借出,Guard析构后回滚归还,以防内存泄漏。

回滚的顺序是FILO,也就是严格按照相反的顺序(因为release和set_allocated并非严格对称,如果在成环的情况下可能会有问题)。

由于C++的构造和析构也是FILO(https://isocpp.org/wiki/faq/dtors#order-dtors-for-locals),一定要在pb初始化后再初始化Guard

这两个接口已经足够满足在我们的业务中存在的几种抽象:

(一)主调透传/分包

把上游传递的某个字段,零拷贝传入下游的请求。此时直接Attach字段即可。

//usecase:const AReq & oAReq;BReq oBReq;SharePbFieldGuard guard;guard.AttachField(&oBReq, BReq::BigFieldId, const_cast<AReq &>(oAReq).mutable_bigfield());

(二)被调分包

控制某些字段不同,而其他字段共享/相同。为了避免拷贝大字段,我们可以在拷贝前先释放这些重的字段;拷贝结束后,把重字段共享给所有的分包。使用CopyFrom好处在于,我们不需要为所有新增的字段都手动判断,只需要特殊处理重的字段即可。

//usecase:Req & oReq;std::vector<Req> vecMultiReq(n);SharePbFieldGuard guard;auto* pField = guard.DetachField(&oReq, Req::BigFieldId);for(auto && oSingleReq: multiReq){oSingleReq.CopyFrom(oReq);oSingleReq.set_field(...);guard.AttachField(&oSingleReq, Req::BigFieldId, pField);}

(三)多字段共享写法(以下是一段脱敏的实际代码)

由于操作的指针都是Message*类型,可以直接用容器存储pb index到字段指针的映射关系。通过循环即可共享所有重字段。

std::vector<uint32_t> vecHeavyField{};//初始化为一组fieldIdSharePbFieldGuard oGuard;std::unordered_map<uint32_t, ::google::protobuf::Message*> mapIndex2Message;for(auto uField: vecHeavyField){mapIndex2Message[uField] = oGuard.DetachField(&oReq, uField);}for (auto && oSingleReq: vecReq){oSingleReq.CopyFrom(oReq);//shared filedfor(auto uField: vecHeavyField){oGuard.AttachField(&oSingleRecallReq, uField, mapIndex2Message[uField]);}}

展望

安全性:因为回滚时set_allocated会delete掉原本的字段,假如成环可能会很危险,如何侦测这种情况。

性能:是否存在不使用反射,就能自动绑定set_allocated和release的方法?

Repeated字段支持:怎样处理Repeatd字段不同的反射接口?

(https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message#repeated-field-getters)

 作者简介

朱文杰

腾讯后台开发工程师

腾讯后台开发工程师,毕业于上海交通大学,知乎笔名朝闻君,目前负责微信公众平台推荐系统后台的开发和优化。

基于Protobuf共享字段的分包和透传零拷贝技术,你了解吗?相关推荐

  1. 系统在此应用程序中检测到基于堆栈的缓冲区_Linux 中的零拷贝技术

    转载:Linux 中的零拷贝技术,第 1 部分 引言 传统的 Linux 操作系统的标准 I/O 接口是基于数据拷贝操作的,即 I/O 操作会导致数据在操作系统内核地址空间的缓冲区和应用程序地址空间定 ...

  2. 蓝牙透传实验_蓝牙模块比较常见的通讯方式透传是什么

    说到低功耗蓝牙模块,少不了要说说低功耗蓝牙模块中很简单.很常见的通讯方法--透传.透传也叫串口透传,即是通明传输的意思,透传是一种工作方法,不是一种性能,通常出现在串口模块中(蓝牙串口透传模块是为了让 ...

  3. android盒子光纤音频,电视盒子经常看到的“音频透传”以及背后的技术

    原标题:电视盒子经常看到的"音频透传"以及背后的技术 现在市面上流行的电视盒大部分都是Android,"音频透传"是一个经常见到的词,那到底什么是音频透传.音频 ...

  4. 蓝牙透传模块HC-08使用教程与简单应用

    蓝牙透传模块HC-08使用教程与简单应用 前言(文章末尾附STM32 源码) 一.AT指令 二.使用测试 1.使用到的工具如下 2.示意图 3.开始测试 三.远程控制点亮MCU板上的LED 1.管脚配 ...

  5. android x86 GPU透传,显卡虚拟化方案之GPU透传(一)背景介绍篇

    之前的文章提到使用conda来进行AI开发环境的搭建,可以看出在主机(host)上搭建复杂的AI环境,有时非常耗费时间和精力.其实我们可以采用一种精简的方式,那就是使用虚拟化技术. 比如我们可以使用d ...

  6. 产品 电信nb接口调用_基于NB-IoT平台数据透传模式的应用接入平台设计方法与流程...

    本发明涉及互联网.物联网和软件开发技术领域,具体的说,是一种基于NB-IoT平台数据透传模式的应用接入平台设计方法. 背景技术: NB-IoT支持低功耗设备在广域网的蜂窝数据连接,聚焦于低功耗广覆盖( ...

  7. 基于Protobuf的分布式高性能RPC框架——Navi-Pbrpc

    基于Protobuf的分布式高性能RPC框架--Navi-Pbrpc 二月 8, 2016 1 简介 Navi-pbrpc框架是一个高性能的远程调用RPC框架,使用netty4技术提供非阻塞.异步.全 ...

  8. 基于ESP32实现一个WIFI透传模块demo

    esp32作为一个热门芯片,网上的文章很多,sdk里的例子和官网的教程也是比较详细.不过作为新玩家,还是要先做一些小改动才更容易入门.所以这里就综合几个example代码,写一个简单的透传demo. ...

  9. pyspark dataframe数据连接(join)、转化为pandas dataframe、基于多个字段删除冗余数据

    pyspark dataframe数据连接(join).转化为pandas dataframe.基于多个字段删除冗余数据 目录 pyspark dataframe数据连接(join).转化为panda ...

最新文章

  1. ping 丢包 网络摄像头_视频监控系统的摄像头掉线看交换机连接注意事项
  2. Py修行路 python基础 (九)作用域 函数嵌套 闭包
  3. 计算机界名人榜-----约翰·冯·诺依曼
  4. Java不是true值不变_Java语言中String a=a;String b=a; 为什么 a==b 值为 true?
  5. 非递归分治法排序 MergeSort without recursion
  6. docker学习之-什么是docker
  7. concurrentbag 删除_你知道吗?这样删除iPhone中的APP腾出的空间会更大
  8. 二叉树类图_设计模式前言——UML类图
  9. 《硬件接入》海康威视接入及CPU性能优化思路
  10. 在pytorch中实现十折交叉验证
  11. C语言 斐波那契数列
  12. leaflet地图鼠标移动画线
  13. vs2015安装与配置
  14. MySQL实战45讲——MySQL是怎么保证数据不丢的?
  15. 参数use_sim_time
  16. 关于nignx老是报错404的问题
  17. 2021年氯化工艺考试内容及氯化工艺考试试卷
  18. 论文阅读笔记《Robust Point Matching via Vector Field Consensus》
  19. 大数据之“用户行为分析
  20. 学分,选够了吗? Alpha冲刺阶段

热门文章

  1. linux shell输出数字小数点前少了0_南京课工场IT培训:SHELL 超详细基础知识,适合新手小白(一)
  2. docker保护python源码_Tensorflow在Docker中运行和源码编译
  3. MySQL 中的共享表空间与独立表空间如何选择
  4. CSS表格中的一些属性
  5. python大作业 学生管理系统 以Excel(xls)格式导入文件
  6. 线段树分治 ---- CF1217F - Forced Online Queries Problem(假离线 可撤销并查集 + 线段树分治)详解
  7. 树上启发式合并问题 ---- D. Tree and Queries[树上启发式合并+树状数组]
  8. 贪心 ---- Codeforces Global Round 8,B. Codeforces Subsequences[贪心,贪的乘法原理]
  9. Codeforces1600数学[CodeForces - 958E1[平面几何+暴力]CodeForces - 888D [组合数+错排问题]]
  10. 计算机及网络应用基础思维导图_思维导图在生物教学中的应用