Google protocol buffer多线程锁问题

最近项目中使用到了google protocol buffer作为数据传输格式,google protocol buffer(PB)相对于json来说序列化和反序列化速度都比较快。google protocol buffer的介绍详见:点击打开链接

项目中的使用场景是存在一批数量比较多的数据pair,key为string,value为一个结构体,使用protocol buffer进行存储。使用的时候可预先将所有数据load到内存中,给的一个string类型的key后,需要快速的判断出是否能找到该key,如果找到需要返回该protocol buffer value的指针。

实现的时候,首先建立一个string类型key到size_t类型的正数的隐射关系,存储到double array trie(DA)结构中,这样可实现对key的较快查找,同时将所有的protocol buffer数据存储到一个大数组中,数组下标和DA的value值对应,这样如果找到对应的key,根据DA中的sizt_t类型的value快速得到PB类型的value。

1、数据制作

在实现数据存储时,将所有数据均以二进制的形式存储到文件中。

每一个PB对象调用SerializeToString方法将对象序列化为一个二进制string,将所有序列化后的二进制string写入文件的连续位置,我们还需要记录每一个string在文件中偏移位置以及string的长度存储在两个数组中在反序列化时使用。左后将偏移量和长度数组的内容也写入文件中。同时记录下每一个PB对象在string序列中的位置(从0开始),该位置作为DA结构中的value。

索引的制作代码如下:

bool HotelDoubleArray::buildFromFile(const string& info_file,const string& save_file,const string& md5_file)
{

    //info_dic.clear();
    ifstream fin(info_file.c_str());
    if (!fin)
    {   
        _INFO("ERROR: open file fail:%s",info_file.c_str());
        return false;
    }   

    FILE *fout;
    fout = fopen(save_file.c_str(),"wb+");

    if (!fout)
    {   
        _INFO("ERROR: open file:%s]",save_file.c_str());
        return false;
    }   

    ofstream md5_fout(md5_file.c_str());
    if(!md5_fout)
    {
        _INFO("[Open md5 file fail. file name = %s]",md5_file.c_str());
        return false;
    }

    fseek(fout,0,SEEK_SET);


    char tmpCh[1024 * 1024];
    memset(tmpCh,'\0',sizeof(tmpCh));

    vector<size_t> offset_vec;
    vector<size_t> binary_length_vec;

    size_t char_offset = 0;
    size_t file_offset = 0;

    fwrite(&char_offset,sizeof(size_t),1,fout);
    file_offset += sizeof(size_t) * 1;

    string line;
    int lineNum = 0;
    map<string,int> key_map;
    vector<string> strVec;
    while(getline(fin,line))
    {
        if (line.empty())
        {
            //cerr << "empty line\n";
            continue;
        }
        strVec.clear();
        tokenize(line,strVec,"\t");

        if (strVec.size() != 21)
        {
            //cerr << "line " <<lineNum <<  " error: " << line << endl;
            continue;
        }

        const string& key_str = strVec[0];
        const string& room_id = strVec[1];
        const string& real_source = strVec[2];
        const string& room_type = strVec[3];
        const int occupancy = atoi(strVec[4].c_str());
        const string& bed_type = strVec[5];
        int size = atoi(strVec[6].c_str());
        int floor = atoi(strVec[7].c_str());
        bool is_extrabed = false;
        bool is_extrabed_free = false;
        bool has_breakfast = false;
        bool is_break_free = false;
        bool is_cancel_free = false;
        if("Yes" == strVec[8])
            is_extrabed = true;
        if("Yes" == strVec[9])
            is_extrabed_free = true;
        if("Yes" == strVec[10])
            has_breakfast = true;
        if("Yes" == strVec[11])
            is_break_free = true;
        if("Yes" == strVec[12])
            is_cancel_free = true;

        const string& room_desc = strVec[13];
        const string& ori_room_type = strVec[14];
        const string& norm_room_type = strVec[15];
        const string& pay_method = strVec[16];
        const string& extrabed_rule = strVec[17];
        const string& return_rule = strVec[18];
        const string& change_rule = strVec[19];
        const string& others_info = strVec[20];

        bool contain_dorm = false;

        if (string::npos != room_type.find("宿舍")
                || string::npos != room_type.find("个床位")
                || string::npos != room_type.find("客房床位")
                || string::npos != bed_type.find("宿舍")
                || string::npos != room_desc.find("宿舍")
                && string::npos != room_desc.find("床位")
                || string::npos != room_type.find("dorm")
                || string::npos != room_type.find("Dorm")
                || string::npos != room_type.find("hostel")
                || string::npos != room_type.find("Hostel")
                || string::npos != room_type.find("8 bedded room"))
        {
            contain_dorm = true;
        }

        if (key_map.end() != key_map.find(key_str))
            continue;

        Hotel_Info hotel_info;
        hotel_info.set_room_id(room_id);
        hotel_info.set_real_source(real_source);
        hotel_info.set_room_type(room_type);
        hotel_info.set_bed_type(bed_type);
        hotel_info.set_room_desc(room_desc);
        hotel_info.set_ori_room_type(ori_room_type);
        hotel_info.set_norm_room_type(norm_room_type);
        hotel_info.set_pay_method(pay_method);
        hotel_info.set_extrabed_rule(extrabed_rule);
        hotel_info.set_return_rule(return_rule);
        hotel_info.set_change_rule(change_rule);
        hotel_info.set_others_info(others_info);
        hotel_info.set_occupancy(occupancy);
        hotel_info.set_size(size);
        hotel_info.set_floor(floor);
        hotel_info.set_is_extrabed(is_extrabed);
        hotel_info.set_is_extrabed_free(is_extrabed_free);
        hotel_info.set_has_breakfast(has_breakfast);
        hotel_info.set_is_break_free(is_break_free);
        hotel_info.set_is_cancel_free(is_cancel_free);
        hotel_info.set_contain_dorm(contain_dorm);

        cout << key_str
            << "\t" << room_id
            << "\t" << real_source
            << "\t" << room_type
            << "\t" << bed_type
            << "\t" << ori_room_type
            << "\t" << norm_room_type
            << "\t" << pay_method
            << "\t" << extrabed_rule
            << "\t" << return_rule
            << "\t" << change_rule
            << "\t" << others_info
            << "\t" << occupancy << endl;

        string cand_info_str;
        hotel_info.SerializeToString(&cand_info_str);

        const size_t char_count = cand_info_str.length() + 1;

        offset_vec.push_back(char_offset);
        binary_length_vec.push_back(char_count);

        char_offset += char_count;

        fwrite(cand_info_str.c_str(),sizeof(char),char_count,fout);

        key_map[key_str] = lineNum;

        md5_fout << key_str << endl;

        ++lineNum;
    }
    fin.close();
    fclose(fout);
    fout = NULL;
    file_offset += char_offset;
    if(offset_vec.size() != binary_length_vec.size())
    {
        _INFO("[Error] [offset size != binary_length_vec size]");
        return false;
    }

    fout = fopen(save_file.c_str(),"rb+");

    if(!fout)
    {
        _INFO("[Open file fail. file = %s]",save_file.c_str());
        return false;
    }

    fseek(fout,0,SEEK_SET);

    _INFO("[char size: %ld offset:0]",char_offset);
    fwrite(&char_offset,sizeof(size_t),1,fout);
    fclose(fout);
    fout = NULL;

    fout = fopen(save_file.c_str(),"ab+");
    if (!fopen)
    {
        _INFO("[Open file fail. file = %s]",save_file.c_str());
        return false;
    }

    const size_t offset_size = offset_vec.size();
    _INFO("[offset size:%ld offset:%ld]",offset_size,file_offset);

    fwrite(&offset_size,sizeof(size_t),1,fout);

    file_offset += sizeof(size_t) * 1;

    //write offset info
    _INFO("[offset_vec offset:%ld]",file_offset);
    fwrite(&offset_vec[0],sizeof(size_t),offset_size,fout);
    file_offset += sizeof(size_t) * offset_size;

    _INFO("[binary length offset:%ld]",file_offset);

    fwrite(&binary_length_vec[0],sizeof(size_t),offset_size,fout);
    file_offset += sizeof(size_t) * offset_size;

    size_t m_size = key_map.size();
    char** m_key = new char*[m_size];
    size_t* m_keylen = new size_t[m_size];
    int* m_val = new int[m_size];
    if(!(doubleArray && m_key && m_keylen && m_val))
    {
        _INFO("[Memory error]");
        return false;
    }

    int count = 0;
    for(std::map<string,int>::iterator it = key_map.begin();it != key_map.end();it++)
    {
        const string cand_key = it->first;
        const int cand_value = it->second;

        m_key[count] = new char[cand_key.length() + 1];
        strcpy(m_key[count],cand_key.c_str());
        m_keylen[count] = cand_key.length();
        m_val[count] = cand_value;

        count++;
    }

    if(count != m_size)
    {
        _INFO("[Error] [data count fail]");
        return false;
    }

    int ret = doubleArray->build(m_size,m_key,m_keylen,m_val);

    if(0 != ret)
    {
        _INFO("[Make info DA fail]");
        return false;
    }

    size_t da_size = doubleArray->size() * doubleArray->unit_size();
    fwrite(&da_size,sizeof(size_t),1,fout);

    file_offset += sizeof(size_t) * 1;
    _INFO("[DA size:%ld offset:%ld]",da_size,file_offset);

    fclose(fout);

    if(doubleArray->save(save_file.c_str(),"ab+",file_offset) < 0)
    {
        _INFO("[Error] [save file fail]");
        return false;
    }

    if(m_val)
    {
        delete[] m_val;
    }

    if(m_keylen)
    {
        delete m_key;
    }
    if(m_key)
    {
        for(size_t k = 0;k < m_size;k++)
        {
            if(m_key[k])
                delete[] m_key[k];
        }

        delete[] m_key;
    }

    return true;
}

2、数据的反序列化

对数据进行发序列化时,首先将所有二进制string从文件中一次性load到内存中,然后将string的长度以及偏移量信息load到内存中,剩下的工作就是对二进制string进行反序列化,得到PB对象。

因为数据量比较大,并且所有数据均是在内存中进行操作,不存在IO限制,所以加快反序列化最直接的方法就是利用多线程,每个线程单独的对一部分数据进行反序列化操作,各线程之间不存在通信和资源共享操作,理论上来说应该可以大大加快反序列化速度。

反序列化所线程代码:

const int max_thread_count = 6;

struct ParsePara
{
    char* str_buffer;
    size_t* buffer_offset;
    size_t* binary_string_length_array;
    size_t start_idx;
    size_t end_idx;
    Hotel_Info* info_buffer_array;
};

void parseHotelInfoForMany(const char* str_buffer,Hotel_Info* info_buffer_array,const size_t* buffer_offset,
        const size_t start_idx,const size_t end_idx,size_t* binary_string_length_array)
{
    for(size_t k = start_idx;k < end_idx;k++)
    {
        const size_t cand_offset = buffer_offset[k];
        const size_t binary_length = binary_string_length_array[k];

        info_buffer_array[k].ParseFromArray(&str_buffer[cand_offset],binary_string_length_array[k] - 1);
    }
}

void* parseHotelInfoForMultiThread(void* void_para_ptr)
{
    ParsePara* para_ptr = (ParsePara*)void_para_ptr;

    parseHotelInfoForMany(para_ptr->str_buffer,para_ptr->info_buffer_array,para_ptr->buffer_offset,
            para_ptr->start_idx,para_ptr->end_idx,para_ptr->binary_string_length_array);
}



bool HotelDoubleArray::load(const string& file_name)
{
    FILE* fin;
    fin  = fopen(file_name.c_str(),"rb");

    if(!fin)
    {
        _INFO("[Open file fail]");
        return false;
    }

    fseek(fin,0,SEEK_SET);

    size_t file_offset = 0;
    size_t char_size;

    fread(&char_size,sizeof(size_t),1,fin);
    _INFO("[char size:%ld   0]",char_size);

    cout << "1 " << char_size << endl;
    file_offset += sizeof(size_t) * 1;
    cout << "2 " << char_size << endl;

    cout << file_offset << endl;
    cout << "3 " << char_size << endl;

    char* m_valdata = new char[char_size];
    cout << "4 " << char_size << endl;

    if(!m_valdata)
    {
        _INFO("[get memory fail]");
        return false;
    }

    fread(m_valdata,sizeof(char),char_size,fin);
    cout << "5 " << char_size << endl;

    file_offset += sizeof(char) * char_size;

    cout << char_size << endl;
    cout <<  sizeof(char) << endl;
    cout << sizeof(char) * char_size << endl;
    cout << file_offset << endl;

    size_t offset_size;
    fread(&offset_size,sizeof(size_t),1,fin);
    _INFO("[offset size:%ld offset:%ld]",offset_size,file_offset);

    file_offset += sizeof(size_t) * 1;

    size_t* offset_array = new size_t[offset_size];
    size_t* binary_length_array = new size_t[offset_size];

    if(!offset_array || !binary_length_array)
    {
        _INFO("[Get memory fail]");
        return false;
    }

    fread(offset_array,sizeof(size_t),offset_size,fin);

    file_offset += sizeof(size_t) * offset_size;

    fread(binary_length_array,sizeof(size_t),offset_size,fin);
    file_offset += sizeof(size_t) * offset_size;

    size_t da_size;
    fread(&da_size,sizeof(size_t),1,fin);
    fclose(fin);

    file_offset += sizeof(size_t) * 1;
    _INFO("[DA size:%ld offset:%ld]",da_size,file_offset);

    if(doubleArray->open(file_name.c_str(),"rb",file_offset,da_size) < 0)
    {
        _INFO("[Load DA fail]");
        return false;
    }

    info_array = new Hotel_Info[offset_size];

    if(!info_array)
    {
        _INFO("[Malloc memory fail]");
        return false;
    }

    pthread_t pthread_id_vec[max_thread_count];
    vector<ParsePara*> para_vec;

    const size_t each_count = ceil(float(offset_size) / max_thread_count);
    for (size_t k = 0;k < max_thread_count;k++)
    {
        size_t start_idx = each_count * k;
        size_t end_idx = each_count * (k+1);

        if (start_idx >= offset_size)
            break;

        if (end_idx >= offset_size)
            end_idx = offset_size;

        ParsePara* cand_para_ptr = new ParsePara();

        if (!cand_para_ptr)
        {
            _ERROR_EXIT(0,"[Malloc memory fail.]");
        }

        cand_para_ptr->str_buffer = m_valdata;
        cand_para_ptr->buffer_offset = offset_array;
        cand_para_ptr->start_idx = start_idx;
        cand_para_ptr->end_idx = end_idx;
        cand_para_ptr->info_buffer_array = info_array;
        cand_para_ptr->binary_string_length_array = binary_length_array;

        para_vec.push_back(cand_para_ptr);
    }


    for(size_t k = 0;k < para_vec.size();k++)
    {
        int ret = pthread_create(&pthread_id_vec[k],NULL,parseHotelInfoForMultiThread,para_vec[k]);

        if(0 != ret)
        {
            _ERROR_EXIT(0,"[Create thread fail]");
        }
    }

    for(int k = 0;k < para_vec.size();k++)
    {
        pthread_join(pthread_id_vec[k],NULL);
    }

    if(m_valdata)
    {
        delete[] m_valdata;
    }

    if(m_valdata)
    {
        delete[] m_valdata;
    }

    if(offset_array)
    {
        delete[] offset_array;
    }

    if(binary_length_array)
    {
        delete[] binary_length_array;

    }

    for(size_t k = 0;k < para_vec.size();k++)
    {
        if(para_vec[k])
        {
            delete para_vec[k];
        }
    }

    return true;

}

代码逻辑比较简单,实现起来也很快。感觉任务就这么愉快地结束了。

当然需要比较一下多线程提高了多大的性能。见证奇迹的时刻就要到了么,然而并不是:

相同的模型文件、单线程耗时37124995us;开启6个线程(机器为8core)多线程耗时128721575us。妈蛋,多线程变慢了。

一定是我打开的方式不对,运行多线程发现在反序列化时,只有1到2个CPU处于运行状态,剩余的CPU总是处于等待状态,这说明各线程之间存在某种资源竞争,但是代码里各线程是完全独立的啊,而且protocol buffer文档也说了不存在任何的lock操作。

遇到问题了,剩下的就是解决问题。各种查资料,完全木有有帮助的东东,之后求组万能的Stack Overflow,问题链接点击打开链接,第二天上去一看,有人回复了。竟然是google protocol buffer 2.0版本的主要作者Kenton Varda。越是大牛越是没架子啊,膜拜。在回复中给我列了3个可能的原因,分析以后觉得只能是其中一个原因:PB对象只有在实际真正的使用时才会分配内存,也就是所new一个很大的PB数组,其实做的工作很少,只有在调用ParseFromArray的时候才会对对象进行内存的分配。而所有数据都是使用new进行分配的,new操作符实际上是调用malloc函数,而malloc函数不是thread-freiendly的,各线程之间存在锁操作。

问题到这明了了,就是因为线程使用调用malloc到这多线程之间互相等待。

3、解决方法

Kenton Varda给出的建议是使用google的tcmalloc代替系统默认的malloc。

3.1 tcmalloc安装:

直接从google官网下载,官网链接:点击打开链接然后解压、编译。第一次编译出现错误,查了以下资料,configure的时候需要加上一个参数:

./configure --enable-frame-pointers
make && make install
然后在使用索引的Makefile.am的libHotelDoubleArray_la_LIBADD中加上“/usr/local/lib/libtcmalloc_minimal.la”,原来的代码不做任何修改,在操作内存的时候就会使用tcmalloc代替malloc。

使用相同的数据从新测试,开6个线程,耗时9400488us,时间明显加快了,达到了多线程的目的。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值