Jieba 问题:占用内存过高

版权声明:所有博客文章除特殊声明外均为原创,允许转载,但要求注明出处。

在前面的文章 2020 新版网站发布 中我提到过,本次的新版本在上线以后发现了比较严重的 MemoryError 问题,当时简单地跟踪了一下,似乎是由于 jieba 库引起的,但并未深入研究。这两天花了点时间仔细测试这个问题,终于有了一个比较详细的结论,在此记录一下,也可以给其他使用 jieba 库的同学一点参考。

为了重现此现象并检查问题的来源,我使用 memory_profiler 库写了一个简单的测试程序,结果如下:

Jieba Memory Profiler

请注意,这里我还没有创建任何对象的实例,仅仅是导入 jieba 库,就已经占用了超过 120 MB 的内存。这可以证明内存错误确实是由于 jieba 库导致的。之所以在开发过程中没有发现,是因为开发环境下是都以单个 Flask dev server 程序实例运行的,而生产环境使用 gunicorn 的多 Worker 模式,且同时运行了 4 个实例,于是内存占用问题就特别明显了。

但是 jieba 是一个分词库,本身不应该有多少数据,为何会占用如此多的内存?大概浏览了一下代码,问题似乎出在其中一个名为 idf.txt 的文件。该文件有 27 W 行以上的记录,jieba 将其加载后放在一个字典中,这个字典会占用相当大的内存。但从另一方面说,idf.txt 原始大小也不过 6 MB 多一点,但加载到内存中却用掉了 100 MB 以上,这也说明 Python 在数据空间的使用上确实比较浪费。

我也考虑了一下是否有优化的可能。由于字典的键是字符串、且基本上都是中文,因此一种可能的方案是把键以 gbk 编码存储,这比默认的 UTF-8 编码可以节约 1/3 的空间。而 dict 是 Python 的内置数据结构,恐怕本身没有什么好的办法去优化。对于 dict,也有网络资料建议以其他结构去存储(比如 Trie),但这样对源码的修改会相当大,不太现实。

在 Web Server 中,搜索是一个有现实需要、但是消耗较高,因此应该限制使用的功能。为了避免多进程模式下爆内存,可能更加现实的方案是把全文搜索这部分独立成单独的程序,需要的时候再去调用。这样有点类似于 SolrElasticsearch 的外部方案了。虽然我没有特别关注过 Solr/Elasticsearch 的内存占用如何,但 Java 同样是吃内存的大户,在内存占用方面恐怕好不到哪里去。

总之,jieba 库占用内存过大的问题是用户需要了解的,尤其是对于类似我这样的小型 VPS 更加如此。对于小型网站来说,不管采用那种方案,全文搜索似乎都是一个比较“奢侈”的功能。或许我应该考虑其他一些网站那样比较取巧的方法,直接把搜索委托给 Google 吧。