当前位置:首页 > 技术 > 正文内容

COMSE项目技术笔记

访客 技术 2026年6月1日 1

1.2017/1/9 head成员变量与指针的选择问题

代码修改

修改前结构体定义:

typedef struct index_hash_value  
{  
    uint32_t my_pos; // 用于锁定  
    uint32_t del_data_num; // 用于检查是否需要收缩  
    uint32_t use_data_num; // 用于检查是否需要收缩  
    uint32_t sum_node_num; // 用于遍历,仅递增  
    struct index_node head; // head计数为1  
}INDEX_HASH_VALUE;

修改后结构体定义:

typedef struct index_hash_value  
{  
    uint32_t my_pos; // 用于锁定  
    uint32_t del_data_num; // 用于检查是否需要收缩  
    uint32_t use_data_num; // 用于检查是否需要收缩  
    uint32_t sum_node_num; // 用于遍历,仅递增  
    struct index_node *head_p; // head指针,计数为1  
}INDEX_HASH_VALUE;

问题原因:

当仅存在head和last节点时,发现last节点的prev_p和next_p指针与head不一致,导致段错误。

分析:

1.在shrink_index函数中:

1.2.生成新的hash_values

index_hash_value new_one_hash_value = {0};//初始化为0
index_hash_value * p_hash_value = &new_one_hash_value;

1.3.删除旧值并插入新值

{index_map.insert( std::pair<std::string,index_hash_value>(index_hash_key,new_one_hash_value) );}

当使用成员变量时,在index_map.insert过程中,value及其内部的head内存地址会发生改变,而新分配的节点new_one_hash_value中的last_node仍然指向局部变量new_one_hash_value中的head,导致后续遍历时出现问题。

总结:

指针隐式指向了局部变量,使用时导致段错误。

2.2017/1/10 代码审查

2.1 _hash_now_num 在shrink和clear时的递减问题

不能递减,因为insert时是递增,而clear和shrink不是从最后一个元素开始,如果递减会造成my_pos值重复的问题。

2.2 shrink中可能发生的内存泄漏

if (!hit_hash_key) {return false;}

这会导致新创建的2.generate new hash_values产生的内存不被释放吗? 不会,因为AutoLock_Mutex auto_lock0(&index_update_lock)确保了对index_map的更新操作都是串行化的,而之前已经有判断 if (query_out.size() <= 0 ) {return false;},所以hit_hash_key应该为true。

2.3代码审查要点

  • shrink_index
  • clear_all_index
  • clear_index
  • delete_index
  • insert_index
  • all_query_index
  • cross_query_index

3.内存泄漏检查和多线程业务验证

3.1内存泄漏检查代码

int main()
{
    while(1)
    {
        Index_Core idx_core(128,3);

        for (int i = 1 ; i < 100;i++)
            for (char j = 'a';j<='z';j++)
            {
                std::string str(i,j);
                for (int k = 0 ;k < 1000;k++)
                {idx_core.insert_index(str,k);}
                printf("insert %s\n",str.c_str());
            }

        printf("==================\n");

        for (int i = 1 ; i < 60;i++)
            for (char j = 'a';j<='m';j++)
            {
                std::string str(i,j);
                for (int k = i ;k < 300;k++)
                {idx_core.delete_index(str,k);}
                printf("delete %s\n",str.c_str());
            }

        for (int i = 1 ; i < 80;i++)
            for (char j = 'a';j<='z';j++)
            {
                std::string str(i,j);
                idx_core.shrink_index(str);
                printf("shrink %s\n",str.c_str());
            }

        for (int i = 1 ; i < 90;i++)
            for (char j = 'a';j<='z';j++)
            {
                std::string str(i,j);
                idx_core.clear_index(str);
                printf("clear %s\n",str.c_str());
            }

        fflush(stdout);
        sleep(1);

    }

}

结论:运行12小时后,内存使用RSS保持稳定。

3.2多线程业务验证

#define THREAD_NUM 1

Index_Core idx_core(8,64);

void *worker_thread(void *arg)
{
    while(1)
    {
#if 1
        for (int i = 1 ; i < 15;i++)
            for (char j = 'a';j<='z';j++)
            {
                std::vector<uint32_t> query_out;
                std::string str(i,j);

                idx_core.all_query_index(str,query_out);
                if (query_out.size() > 0)
                {
                    printf("%s[%d]:",str.c_str(),query_out.size());    
                    for (int k = 0 ; k < query_out.size(); k++)
                    {printf("%d ",query_out[k]);}
                    printf(":%s\n",str.c_str());    
                }
            }
#endif
        fflush(stdout);
        sleep(1);

    }
}


int main(int argc,char *argv[])
{
    pthread_t tid[THREAD_NUM];
    int thread_ids[THREAD_NUM] = {0};

    for (int i = 0; i < THREAD_NUM; i++)
    {
        thread_ids[i] = i;
        if (pthread_create(&tid[i],NULL,&worker_thread,(void*)&thread_ids[i]) != 0)
        {
            fprintf(stderr,"线程创建失败\n");
            return -1;
        }
    }

    while(1)
    {
        for (int i = 1 ; i < 10;i++)
            for (char j = 'a';j<='z';j++)
            {
                std::string str(i,j);
                for (int k = 0 ;k < 30;k++)
                {idx_core.insert_index(str,k);}
            }

#if 1
        for (int i = 1 ; i < 10;i++)
            for (char j = 'a';j<='m';j++)
            {
                std::string str(i,j);
                for (int k = (i+2);k < 10;k++)
                {idx_core.delete_index(str,k);}
            }
#endif
#if 1
        for (int i = 1 ; i < 10;i++)
            for (char j = 'a';j<='z';j++)
            {
                std::string str(i,j);
                idx_core.shrink_index(str);
            }
#endif
#if 1
        for (int i = 3 ; i < 10;i++)
            for (char j = 'a';j<='z';j++)
            {
                std::string str(i,j);
                idx_core.clear_index(str);
            }
#endif
    }

    for (int i = 0 ;i < THREAD_NUM; i++)
        pthread_join(tid[i],NULL);
}

结论:运行至少12小时,打印的vector检查均为升序,且内存保持稳定。

4.单元测试

5.待办事项

1.使用type或view分离检索数据

2.请求时携带归并数量,返回时携带分词结果。

2017/3/3

发现jsoncpp的可能bug,内存被破坏

且jsoncpp占用太多内存

187字节数据循环生成100万次,占用1GB内存。

每个数据约耗1073字节。

2017/3/7

AWS EC2环境测试不同JSON库性能:

rapidjson:

  • 空对象:96字节
  • 示例JSON(100字节),10万次,内存占用440MB

jsoncpp:

  • 空对象:40字节
  • 示例JSON(100字节),50万次,内存占用700MB

cjson:

  • 空对象:64字节
  • 示例JSON(100字节),50万次,内存占用500MB

2017/3/14

解决了2017/3/3的bug,并非jsoncpp引起,而是之前调用std::sort导致。

这也解释了为什么sort函数之前jisuan_score时没问题,sort之后取breif会崩溃。

由于std::sort中重载<或>符号时,==情况必须返回false,否则会崩溃,详见 http://blog.sina.com.cn/s/blog_79d599dc01012m7l.html

导致程序其他部分问题,引发了jsoncpp的崩溃。

分析:

1.崩溃时除了注意发生位置,还要关注代码上下文或相关"代码环境"

2.问题除了直接产生,可能是其他问题影响了"代码环境",比如公用的堆、栈等

总结:

1.外部集成的库最好经过集成和压力测试,至少备注为潜在风险点

2.参考外界代码时,尽量不要更改重要代码,如果不知道什么是重要的,在完成功能的基础上,最好保持原样

2017/3/15

CLOSE_WAIT过多问题

原始Reactor模型:

[图示]

分析:

netstat观察到CLOSE_WAIT过多

1.CLOSE_WAIT是被动关闭时,服务端未调用close导致,说明服务端accept处理成功

2.服务端未关闭连接

原因:

线程1accept成功时向sock0发送了4字节的建连sock的fd

线程2异步调用接收sock1的信息,如果接收全是4字节没问题,但如果有一次收到的buffer < 4字节,会导致数据不以4字节对齐,接收到的fd不是有效的连接fd。

修改:

多个线程使用同一个listenfd,并accept(加互斥锁)之后的nfd,加入本线程的异步事件调度。

[图示]

线程自适应处理任务,任务处理不过来就accept不过来不建连,直接丢弃。

处理性能提高,减少了一个事件的注册和触发。

解决了CLOSE_WAIT堆积问题。

2017/3/16

编译时需要升级gcc到4.8版本

Makefile时要加上-lrt库,否则执行时找不到clock_time函数?

2017/3/17

1.每达到1万就输出一次max_index_num

2.load_from_file时内容出错时打印日志

待办事项:

max_index_num达到最大值时如何处理?

下一版本解决,dump出全部di,用另一个search_engine加载,然后双buffer交换。

1.日志格式化(完成于2017/5/4)

git:在service中生成log文件夹

2.增加日志记录状态:

ret1 = parse_in_json(); ret2 = get_out_json();

3.加快OR检索(完成于2017/5/4)

检查每个term重要性,即term倒排数量,超过阈值认为不重要,跳过不参与召回

4.search_engine.h中policy_compute_score函数声明和定义不一致(完成于2017/5/4)

5.load进去再dump数量不一致

1282887 dump.json.file 1284625 load.json.file

load时部分数据未加载进去

cat comse.log | grep "json parse error" | wc -l  
1739

应该是load入口和服务的add入口不一致,add进去但load没进去

6.dump日志时格式有问题(完成于5/12)

把之前query的脏数据也输出了

recv和send发送数据时buff还有些脏数据未被清理

解决方式:在recv和send后面追加\0

7.max_ret_num确定实际返回数量(完成于2017/5/10)

8.生成md5的json内容,字段颠倒有无问题(完成于2017/5/16)

字段颠倒后用json_writer.write输出的字符串都是按字母序排列的

10.记录各过程耗时(完成于2017/5/12)

11.增加view或type过滤(在打分时匹配type打0分,在search_filter阶段过滤0分,type:0代表所有)

12.传递参数过多,考虑封装成struct传递?

13.All_time打日志记录时间时,增加query和召回数量打印

增加一个logid?

14.加快search过程

A:对每个term进行重要性分析,过滤掉重要性低的term?

B:在召回过程中实现文本相关性打分?不符合程序原始架构和策略一致性,召回、打分...

C:把词为key的term变成数字为key的term?

数字为key的term:测试1ms执行500次,使用时需记录term长度,使用map?

#include <iostream>      
#include <algorithm>     
#include <set>        
#include <vector>        
#include <string>        
#include <sys/time.h>

#define TIMER(FUNC) { \
        struct timeval prev_time,cur_time; \
        int count_time = 0; \
        gettimeofday(&prev_time,NULL); \
        gettimeofday(&cur_time,NULL); \
        while(1) \
        { \
                    gettimeofday(&cur_time,NULL); \
                    FUNC; \
                    count_time++; \
                    if (cur_time.tv_sec - prev_time.tv_sec >= 1) { \
                                    prev_time = cur_time; \
                                    printf("output timer count %d\n",count_time); \
                                    fflush(stdout); \
                                    count_time=0; \
                                } \
                } \
}

int test_int_key(std::vector<int> & term_list,std::set<int> & term4se_set )
{
    int or_terms_length = 0;
    int or_terms_num = 0;

    if ( term4se_set.size() > 0)
    {
        std::set<int>::iterator term_hash_it;

        for (int i = 0 ; i < term_list.size();i++)
        {
            term_hash_it = term4se_set.find(term_list[i]);
            if (term_hash_it != term4se_set.end()) 
            {
                or_terms_length += term_list[i];
                or_terms_num++;
            }
        }
    }

    int ret = (or_terms_num * 1000 + or_terms_length);
    return ret;
}

int main () {
    std::vector<int> term_list;
    term_list.push_back(1);
    term_list.push_back(8);
    term_list.push_back(16);
    term_list.push_back(3012);
    term_list.push_back(18);
    term_list.push_back(7);
    term_list.push_back(462);
    term_list.push_back(129992);
    
    std::set<int> term4se_set;
    term4se_set.insert(1);
    term4se_set.insert(16);
    term4se_set.insert(7);
    term4se_set.insert(8);
    term4se_set.insert(1234);
    term4se_set.insert(34111111);
    TIMER(test_int_key(term_list,term4se_set));
    return 0;
}

字符串为key的term:测试1ms执行311次

#include <iostream>      
#include <algorithm>     
#include <set>        
#include <vector>        
#include <string>        
#include <sys/time.h>

#define TIMER(FUNC) { \
        struct timeval prev_time,cur_time; \
        int count_time = 0; \
        gettimeofday(&prev_time,NULL); \
        gettimeofday(&cur_time,NULL); \
        while(1) \
        { \
                    gettimeofday(&cur_time,NULL); \
                    FUNC; \
                    count_time++; \
                    if (cur_time.tv_sec - prev_time.tv_sec >= 1) { \
                                    prev_time = cur_time; \
                                    printf("output timer count %d\n",count_time); \
                                    fflush(stdout); \
                                    count_time=0; \
                                } \
                } \
}

int test_string_key(std::vector<std::string> & term_list,std::set<std::string> & term4se_set )
{
    int or_terms_length = 0;
    int or_terms_num = 0;

    if ( term4se_set.size() > 0)
    {
        std::set<std::string>::iterator term_hash_it;

        for (int i = 0 ; i < term_list.size();i++)
        {
            term_hash_it = term4se_set.find(term_list[i]);
            if (term_hash_it != term4se_set.end()) 
            {
                or_terms_length += term_list[i].size();
                or_terms_num++;
            }
        }
    }

    int ret = (or_terms_num * 1000 + or_terms_length);
    return ret;
}

int main () {
    std::vector<std::string> term_list;
    term_list.push_back("book");
    term_list.push_back("apple");
    term_list.push_back("zoo");
    term_list.push_back("school");
    term_list.push_back("action");
    term_list.push_back("gogogogo");
    term_list.push_back("computer");
    term_list.push_back("machine");
    
    std::set<std::string> term4se_set;
    term4se_set.insert("abcd");
    term4se_set.insert("action");
    term4se_set.insert("computer");
    term4se_set.insert("book");
    term4se_set.insert("zzzzzzzzzzz");
    term4se_set.insert("abcd");
    TIMER(test_string_key(term_list,term4se_set));
    return 0;
}

已上线,效果提升约1倍,符合测试预期,但内存占用较大。

优化后性能数据:

[2017-06-08 15:42:51]cal_time recall=5488|compute=64173|sort=5711|package=373 search_mode=2:recall_num=17417:query=《三生三世十里桃花》终极预告花絮首发
[2017-06-08 15:42:51]Thread[a1bb7700] Client[20]All_time[accept2recv_event=211,recv=4,parse_recv=53,do_policy=84612,send=213]:Send=HTTP/1.1 200 OK
20913 video 20 0 2393m 2.0g 1748 S 0.0 2.2 4:00.27 comse_test

优化前性能数据:

[2017-06-08 16:56:17]cal_time recall=5489|compute=127615|sort=5730|package=463 search_mode=2:recall_num=17417:query=《三生三世十里桃花》终极预告花絮首发
[2017-06-08 16:56:16]Thread[4f0ae700] Client[18]All_time[accept2recv_event=144,recv=4,parse_recv=46,do_policy=145616,send=188]:Send=HTTP/1.1 200 OK
23150 video 20 0 2979m 2.6g 1712 S 0.0 2.8 3:13.20 comse_test

15.加快召回过程

使用bitmap节省空间,求交使用bit求交

16.title示例:

<我们的爱>第十二集看点

召回的关键词是"我们"

问题原因:一个作品名称的term由许多常见单词构成。

解决:term合并,n-gram,多层次检索,关键是各层次之间的打分体系如何融合。

16.ret_num 和max_ret_num 过大

导致中间数据占用过多内存,output_buff有限制不会吐出,浪费中间过程的内存和计算。

17.排序不稳定

展现数据偶现乱序

解决:使用stable_sort,缺点是时间会长些

18.优先级队列归并

两个有序数组,merge成一个有序数组

两个有序数组,merge成一个满足数量为n的有序数组

两个有序数组,merge成一个满足数量为n的,且第一个数组元素优先存在的有序数组

解决方案:

利用一个空有序数组,查找第二个有序数组中不存在第一个数组的元素,查找m个。

归并第一个有序数组和m,和正好为n。

19.大数据量add

add超过索引数量(如20万)时不会插入,那么数据的dict也不应该插入,md5值可以插入(方便以后去重)。

20.发现有时会出现abort

已解决,发现是jsoncpp解析错误导致

更改config.h的异常设置:

#ifndef JSON_USE_EXCEPTION  
#define JSON_USE_EXCEPTION 0  
#endif

注释jsoncpp的 assertions.h

//# define JSON_ASSERT(condition) assert(condition)  
# define JSON_ASSERT(condition) ;

// The call to assert() will show the failure message in debug builds. In  
// release builds we abort, for a core-dump or debugger.  
#if 0  
# define JSON_FAIL_MESSAGE(message) \\  
 { \\  
 JSONCPPOSTRINGSTREAM oss; oss << message; \\  
 assert(false && oss.str().c_str()); \\  
 abort(); \\  
 }  
#endif  
# define JSON_FAIL_MESSAGE(message) \\  
 { \\  
 ; \\  
 }

21.解析非预期结构体时glibc报free错误(时间20171012)

//parse query
std::string query = json_in["cmd_info"]["title4se"].asString();

 json_in["cmd_info"]["title4se"]如果是个object,返回的string会很大。

解决A:

对请求接口解析时,按要求的格式解析,不符合则返回错误

解决B:

封装一个接口,在调用asxxx时判断是否isxxx,比如

asString之前调用isString

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。