zoukankan      html  css  js  c++  java
  • protobuf中会严重影响时间和空间损耗的地方

    http://blog.chinaunix.net/uid-26922071-id-3723751.html

    当前项目中普遍用到GOOGLE 的一个开源大作PROTOBUF,把它作为网络应用层面的传输协议,至于它的诸多优势这里不作多说了!直接入正题!
    前几日在PROTOBUF上面有严重的效率和空间开销的问题,没想到这两大难题一下子都来了,来得真是“迅雷不及掩耳之势”!跟踪后发现问题出在了“嵌套的MESSAGE个数过多,时间开销基本上全花在了ADD message上面”,例如:
    message B{
    标记 类型 变量 = 序列号;
    ...
    }
    message A{
    repeated B f = 1;
    }
    A a;
    这里message A中有上百万个message B,时间开销基本花在了 a.add_f()上面,而且空间上面开销也比较大。
    针对这个头疼的问题,头这几天也在着急,项目都进行到这儿出了这等问题!之前我一直怀疑我们的策略问题,这几天在头儿的指示下,研究了一下PROTOBUF的源码,了解其中部分实现体制。这里简单地跟大家分享一下,大家尽管把砖头拍过来,嘿嘿!^_^
    protobuf针对required 标记的字段分了两类,对每类都有相应的处理方式。其一:MESSAGE STRING;其二:非MESSAGE 和STRING,即原始数据类型,如INT32 INT64 FLOAT 等。我们把它们称为:repeated message(或者string)和repeated raw(原始数据类型)两种。PROTOBUF针对前者对内存的管理是,每次ADD时都会NEW一次;而后者先预分配了4个空间,随后成倍地动态增长空间(这个过程包括分配新空间,把原先的数据挪过来,再释放之前的旧空间三个操作);
    虽然前都也有预先分配空间和动态增长空间,但分配的是用来存放MESSAGE或者STRING对象地址的空间,每次ADD的时候仍然要为数据分配空间。
    下面把其中的一些实现细节贴出来,对比看一下:
    repeated message(或者string)类的定义:

    点击(此处)折叠或打开

    1. template <typename Element>
    2. class RepeatedPtrField : public internal::RepeatedPtrFieldBase {
    3.  public:
    4.   RepeatedPtrField();
    5.   RepeatedPtrField(const RepeatedPtrField& other);
    6.   ~RepeatedPtrField();

    RepeatedPtrField继承了RepeatedPtrFieldBase类的一些比较重要的成员如下:

    点击(此处)折叠或打开

    1. static const int kInitialSize = 4;
    2.   void** elements_;
    3.   int current_size_;
    4.   int allocated_size_;
    5.   int total_size_;
    6.   void* initial_space_[kInitialSize];


    上面的几个成员的定义在RepeatedPtrFieldBase类里面。

    repeated raw(原始数据类型)类的定义:

    点击(此处)折叠或打开

    1. template <typename Element>
    2. class RepeatedField {
    3.  public:
    4.   RepeatedField();
    5.   RepeatedField(const RepeatedField& other);
    6.   ~RepeatedField();
    7. 。。。。

    8. 点击(此处)折叠或打开

      1. private:
      2.   static const int kInitialSize = 4;
      3.   Element* elements_;
      4.   int current_size_;
      5.   int total_size_;
      6.   Element initial_space_[kInitialSize];
      。。。


    elements_就是据说的预分配数组,初始数组元素的个数是kInitialSize,就是4.
    current_size_表示当前已经占用的元素个数
    total_size_表示当前数组总大小
    注意:这两个类中的成员 elements_的类型的区别,前者,用来存放的是message或者string对象空间的地址;后者用来存放的是真正的数据地址。

    repeated message(或者string)对ADD的处理方式:

    点击(此处)折叠或打开

    1. template <typename Element>
    2. inline Element* RepeatedPtrField<Element>::Add() {
    3.   return RepeatedPtrFieldBase::Add<TypeHandler>();
    4. }



    点击(此处)折叠或打开

    1. template <typename TypeHandler>
    2. inline typename TypeHandler::Type* RepeatedPtrFieldBase::Add() {
    3.   if (current_size_ < allocated_size_) {
    4.     return cast<TypeHandler>(elements_[current_size_++]);
    5.   }
    6.   if (allocated_size_ == total_size_) Reserve(total_size_ + 1);
    7.   ++allocated_size_;
    8.   typename TypeHandler::Type* result = TypeHandler::New();
    9.   elements_[current_size_++] = result;
    10.   return result;
    11. }



    点击(此处)折叠或打开

    1. void RepeatedPtrFieldBase::Reserve(int new_size) {
    2.   if (total_size_ >= new_size) return;
    3.   void** old_elements = elements_;
    4.   total_size_ = max(total_size_ * 2, new_size);
    5.   elements_ = new void*[total_size_];
    6.   memcpy(elements_, old_elements, allocated_size_ * sizeof(elements_[0]));
    7.   if (old_elements != initial_space_) {
    8.     delete [] old_elements;
    9.   }
    10. }

    别看这里有动态增长内存空间,它这是做戏给人看的!它这里动态增长的是对象(习惯把message string称作“对象”,把原始类型称作“数据”,其实在C++眼里都是对象,只是本人癖性用C的眼光去看,^_^)地址空间,并不是真正的数据空间!

    repeated raw(原始数据类型)对ADD的处理方式:

    点击(此处)折叠或打开

    1. template <typename Element>
    2. inline Element* RepeatedField<Element>::Add() {
    3.   if (current_size_ == total_size_) Reserve(total_size_ + 1);
    4.   return &elements_[current_size_++];
    5. }



    点击(此处)折叠或打开

    1. template <typename Element>
    2. void RepeatedField<Element>::Reserve(int new_size) {
    3.   if (total_size_ >= new_size) return;
    4.   Element* old_elements = elements_;
    5.   total_size_ = max(total_size_ * 2, new_size);
    6.   elements_ = new Element[total_size_];
    7.   MoveArray(elements_, old_elements, current_size_);
    8.   if (old_elements != initial_space_) {
    9.     delete [] old_elements;
    10.   }
    11. }


    这里才是真正的动态增长数据空间。也就是说,只有第5、9、17、33。。。第N次%(2的M次方)==1时,才会重新去分配内存。
    到这里,问题已经找到了,那么只能针对这里的两种特性来对我们的“应用协议”(PROTO文件中体现出来的应用架构)做一下协调、更改,可以避免repeated message(或者string)当repeated次数比较多的时候。!
    我们相应地修改了一下PROTO文件,最终测试,发现时间上的开销果真缩小到了1/10左右,空间倒是没有减少多少,但多少还是少了一点儿!^_^ 唉!毕竟鱼和熊掌不可兼得,就时间和空间在软件这方面的地位而言,自古就是“难全”的,往往有一个要做出牺牲的!!!
    其实去年就PROTOBUF的应用学习过一个月,还学习过一些比较高级的用法,但是并没有深入源码去理解它的实现机制,也没有去过多的去考虑它的性能问题!之后以为,PROTOBUF这个东西比较高级、比较好用,到现在知道再高级实用的东西也会有点瑕疵的,但瑕疵一般不会贴在脸上,还等着我们去发现,去适应它或者索引丢弃它不要!

  • 相关阅读:
    Algebra, Topology, Differential Calculus, and Optimization Theory For Computer Science and Machine Learning 第50章 读书笔记(待更新)
    Algebra, Topology, Differential Calculus, and Optimization Theory For Computer Science and Machine Learning 第49章 读书笔记(待更新)
    Algebra, Topology, Differential Calculus, and Optimization Theory For Computer Science and Machine Learning 第48章 读书笔记(待更新)
    Spring Boot 中使用 Quartz 实现任务调度
    实战 FastDFS Java 客户端上传文件
    分布式文件系统之 FastDFS
    Java 持久层框架之 MyBatis
    C语言实现贪吃蛇
    [转载]分享三篇非常好的学习心得
    selenium加载cookie报错问题:selenium.common.exceptions.InvalidCookieDomainException: Message: invalid cookie domain
  • 原文地址:https://www.cnblogs.com/fvsfvs123/p/4431543.html
Copyright © 2011-2022 走看看