↓ 跳过正文
  1. Posts/

用 DAT 重实现 CppJieba 中文分词算法,降低 99% 内存消耗

·995 字·2 分钟·

一,问题背景
#

中文分词应用比较广泛的开源算法,是 jieba 结巴分词,结巴分词较高性能的实现是 C++ 版本的 CppJieba : https://github.com/yanyiwu/cppjieba

在实际使用 CppJieba 的过程中,我们发现 CppJieba 的内存占用比较高。

比如对一个 76W 词 大小 11MB 的词典 ,加载 2份 (比如为了支持平滑改动用户词典)就需要耗费 505MB内存。

这对一些多进程的后台服务,浪费大量内存,难以接受,因此这里希望削减内存耗费。

经过初步调查,确定改进方法,然后动手改造,最终把 505MB 缩减到了 4.7MB ,实现了 99% 内存降低。

此处也有 issue 讨论 https://github.com/yanyiwu/cppjieba/issues/3

代码在 https://github.com/byronhe/cppjieba 。

二,实现过程
#

二.1 查内存分布
#

第一步先用 jemalloc 的 memory profiler 工具查看内存耗费在哪里,

  1. 改一下 CppJieba 的 test/demo.cpp, 链接 jemalloc,编译成二进制 jieba_test
  2. 然后设置环境变量 export MALLOC_CONF="prof:true,prof_prefix:mem_prof/mem_profile_je.out,lg_prof_interval:20,lg_prof_sample:20"
  3. 然后 mkdir mem_prof, 并运行测试程序
  4. jeprof –pdf ./jieba_test mem_prof/mem_profile_je.out.xxx.heap > mem_profile.pdf

打开 mem_profile.pdf ,就可以看到内存分布了

二.2 优化方案
#

显而易见,内存主要耗费在:

  1. Trie.hpp 中的 Trie 树构建
  2. KeywordExtractor.hpp 加载 idf 词典文件。

因此方案:

1. Double Array Trie 代替 cppjieba::Trie
#

引入 Double Array Trie (简称 DAT ,https://github.com/s-yata/darts-clone) , 代替 Trie.hpp 中的简单内存 Trie,并把 darts 生成的 DAT 保存到文件中,在启动时,如果已经有和词典对应的 DAT ,直接 mmap() attach 上去,即可启动。

经过实测发现,75万词词典,dart-clone 生成的 DAT 文件,大小只有 24MB,而且可以 mmap 挂载,多进程共享。

2. KeywordExtractor
#

KeywordExtractor 是个不常用功能,直接改成支持传入空的 idfPath 和 stopWordPath, 此时不加载数据即可。

二.3 其他问题
#

1. 支持热更新,保证词典和DAT一致
#

这里一个问题是,词典可能热更新,那怎么知道 DAT 文件和当前词典的内容对应?

我的做法是,对 默认词典文件+自定义词典文件,用文件内容算 MD5,写入 DAT 文件头部,这样打开 DAT 文件发现 MD5 不一致,就知道 DAT文件过时了,即可重建 DAT 。

实测发现算 MD5 还是很快的,启动时间都在 1秒 左右。

2. 代码清理
#

另外,清理了一下代码,删掉了 Unicode.hpp 中的无用代码。 清理了 FullSegment.hpp HMMSegment.hpp MixSegment.hpp MPSegment.hpp QuerySegment.hpp 等中的重复代码。

3. 不兼容改动
#

  • 由于 Double Array Trie 无法支持动态插入词,删除 InsertUserWord() 方法
  • FullSegment.hpp 中 maxId 的计算有 bug,做了 fix。
  • 为了节省内存,改成允许传入空的 idfPath 和 stopWordPath 。
  • 会生成 Double Array Trie 临时文件,临时文件名默认会自动生成,也可以传 dict_cache_path 指定
  • 改成自定义词典中重复的词,保留权重最大的。
  • 删除 Unicode.hpp 中的无用代码

整体改造后,代码量比原来减少 100 多行。

上线后效果显著。

当内存降低到 2-3MB 的水平后,这意味着 75W 词这种规模的大词典,可以用在手机环境。

比如可以在 ios 或者 Android 上做 中文/英文的切词, 这意味着可能在客户端实现体验相当良好的搜索引擎。

ios 上也有可用于中文的分词器 CFStringTokenizer ,但貌似不开源。

相关文章

GB 规模语料上的高性能新词发现算法

·398 字·1 分钟
分词是中文搜索的重要环节,目前分词算法已经比较成熟,分词错误的主要是由于未登录词。 因此发现业务领域语料库中的新词,减少未登录词,对改善搜索引擎的用户体验有重要意义。 新词发现的一种常用算法,是 matrix67 大神 2012 年提出的 《互联网时代的社会语言学:基于SNS的文本数据挖掘》 https://www.matrix67.com/blog/archives/5044 其主要思路,是统计语料中出现的所有 ngram 子字符串的凝固度,自由度,信息熵。 算法中需要统计所有 ngram 子字符串的 左熵右熵,实现该算法时,一般以子字符串为 key,用哈希表来存。 但随着语料库变大时,内存消耗变大, 比如之前的 python 版本实现,对 200MB 的语料,就需要约 30G 内存来存哈希表, 导致单机内存不足无法运行,而且对这样规模的语料库,算法需要跑一两天才能出结果。 这里我应用一些工程实现方面的技巧, 把用哈希表统计左熵右熵的计算,拆分成多个子哈希表,分批计算,并利用多核并行,大幅度优化了算法的性能。

借助 ext4 文件系统的 打洞 功能实现消息队列

·217 字·1 分钟
借助 ext4 文件系统的 打洞 功能,可以实现一个消息队列 https://gist.github.com/CAFxX/571a1558db9a7b393579 fallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, offset, length); Punching holes in files https://lwn.net/Articles/415889/ 怎样对大文件的中间部分进行增删? 首先,如果复制/写文件的后半段的话,肯定是可以实现的,但是这样不“高效”。 所以这里的需求是 “怎样在大文件中间 insert 或者 delete 一段数据而不用读写文件的后半部分”。

Linux LoadAvg 编程比赛

·547 字·2 分钟
问题背景 # https://timyang.net/programming/load-average/ 2015.08.13 高可用架构群 Load 编程比赛,Tim 在群征集一段代码使 load average 最高 多线程写几个 while 循环的方法太 trivial 了,就不提了。 下面是 byronhe 的两个解:

现代密码学实践指南[2015年]

·5739 字·12 分钟
本文介绍目前现代密码学的最先进技术, 前半部分主要翻译自 《Cryptographic Right Answers》,附上收集的资料,和byron个人的理解。 密码学理论艰深,概念繁多,本人知识水平有限,错误难免,如果您发现错误,请务必指出,非常感谢! 下文分类介绍在各种适用场景下,你应该使用的现代密码学算法 # 1. 加密数据 : # 按照优先级,应该选择: (1) 首选 NaCl库,或者libsodium库,使用里面的crypto_secretbox()/crypto_secretbox_open() 函数 (2) Chacha20-Poly1305 算法 (3) AES-GCM 算法 适用场景:当你需要避免把明文数据在网络上传输的时候。 以上3种算法,都是AEAD类的算法,AEAD是2015年最好的选择。 其中的(2)和(3)在结构上类似:一个流加密模式的算法,配合一个多项式结构的MAC。 (2)是一个流加密算法,配合一个为通用cpu优化的MAC算法, 对密码学库的实现者来说,Poly1305也比GCM更容易安全地实现。 AES-GCM是工业标准(TLS目前主要用的就是AES-GCM),现代CPU通常都有专门为AES-GCM设计的硬件指令,但是在没有硬件指令支持的CPU上(比如32位的arm),(3)性能低于(2)。

nginx下配置高性能,高安全性的https TLS服务

·1433 字·3 分钟
下文以nginx为例,介绍如何部署一个高性能,高安全性的https服务器。 并附送一个优化出来的openssl编译脚本,可以编译出一个高性能,高安全性的openssl库,您可以直接复制粘贴使用。 此处直接给出实践指导,后续再写文章解释tls协议的这些原理细节。 nginx下https配置的优化点,主要有: session ticket session id cache ocsp stapling http KeepAlive ECDHE等ciphersuite优化 openssl 编译优化 一, nginx https的配置 # 先贴一下nginx配置,如下。 是根据mozilla的权威文档 ,和生成工具(选择 nginx,intermediate ) 生成的配置为基础,加入session ticket等配置的结果