基于Kaldi实现语音识别时,需要引入一款名为OpenFST的开源软件,本文中提到的内存问题,即和这款软件相关。

考虑到过程比较曲折,内容相对比较长,因此先说结论。

在做长时间的语音识别时,集成了Kaldi和OpenFST的进程将会占用远超出预期的内存,这个现象可能和OpenFST、glibc的实现相关,未必是内存泄漏。

进程占用超出大量内存的原因,简单说一下:

  • OpenFST在工作过程中,申请了很多内存,同时产生了很多内存碎片。
  • 语音识别进程默认使用的glibc无法合并相关的碎片,因而即便相关的内存已经被释放,但glibc仍然无法向操作系统释放内存。
  • 因此,在使用top观察进程的虚拟内存时,发现进程占用的内存会时间增长而一直增长,进而会被判定为疑似内存泄漏。

当然了,经过分析后,现在可以确认前述现象为非问题,只需要调整机器规格即可解决问题,但如前所述,整个过程比较曲折,这里记录下来,以备后察。

测试同事反馈,在性能环境上,执行压测过程中,算法服务出现了重启的现象。这是一个大问题,于是在第一时间联系我进行定位。

观察测试同事的压测环境,发现确实如测试同事所说,压测开始后,算法服务占用的虚拟内存以肉眼可见的速度缓慢增长。通过操作系统的硬件资源监控平台,观察进程一段时间内虚拟内存的占用趋势,发现没有进入平稳状态的迹象。最终观察的结果是算法服务占用的虚拟内存一直在增长,最终随着进程异常退出而结束。

我们的算法服务由业务代码、算法推断代码和机器学习模型组成。

  • 业务代码使用Java开发,编译、构建成jar文件,运行时由JVM加载并运行。
  • 算法推断代码使用C++开发,基于JNA规范,Java代码暴露接口,编译、构建成动态库,运行时由JVM加载。
  • 机器学习模型,其实是几个数据文件,运行时由算法推断代码读取并使用。

考虑到当前版本中,算法推断代码和数据模型并没有引入新的变动点,因此重启现象的定位工作从算法服务的业务代码入手。

检查业务流程

首先分析业务流程。

本版本引入了长语音文件转写的特性,因此业务代码有比较大的变动。前期在实现时,为了简化实现方案,在文件转写的过程中,内存里缓存了很多数据。通过分析这部分实现,没有发现对象生命周期超长的现象,但仍然做了改进,将内存中缓存的数据交给数据库来缓存。

这时在开发环境中复现操作,观察内存增长的曲线,发现增长趋势有所减缓,但算法服务占用的虚拟内存,仍然在涨,没有收敛的迹象,因此仍然需要继续分析。

检查JVM配置

算法服务使用的Java堆的参数中,堆的最大值,比较大。本质上讲,经过上一环节的优化后,算法服务的业务代码中不涉及大量Java对象的生成,因此运行时,Java堆可以使用较少的内存。

修改算法服务Java堆的参数后,在开发环境中复现操作,基本功能正常。此外,使用jstat -gcutil <pid> 1000 1000观察,确认JVM的GC操作运行正常,未发现异常现象。

长时间观察内存增长的曲线,发现没有明显改进,算法服务占用的虚拟内存,仍然在涨,没有收敛的迹象,因此仍然需要继续分析。

分析Java堆内存

内存问题分析到现在, 光靠看代码已经不解决问题,是时候召唤专业工具上场了。

对于Java应用的内存,jmapMAT是一对完美的组合。

执行如下命令,导出Java应用进程的堆。

jmap -dump:live,format=b,file=dump001.bin <pid>

为了方便对比分析,一般至少需要导出四次堆。

  • Java应用进程启动完毕。导出的堆文件命名为dump001.bin
  • 压力测试持续一段时间之后。假如可以准确的控制执行的压力测试的用例数量,则可以使用用例数量来衡量。导出的堆文件命名为dump002.bin
  • 在上次导出操作后,压力测试的TPS保持稳定,继续持续一段时间或者执行完毕一部分用例之后,再提取一次堆。导出的堆文件命名为dump003.bin
  • 停止压力测试,等待一段时间,此时再提取一次堆。导出的堆文件命名为dump004.bin

将上述导出的三个文件,dump001.bindump002.bindump003.bin一起导入至MAT。MAT基于eclipse开发,在配置文件中指定了Java堆的最小值和最大值,可以视堆文件的大小,酌情修改MAT的JVM参数。

使用MAT的histogram功能,对这三个文件进行对比。

  • 对比dump001.bindump002.bin,可以确认业务启动后,堆中出现的Java对象的类型和数量。结合业务用例和代码,可以确认对象的类型和数量,是否符合预期。
  • 对比dump002.bindump003.bin,可以确认业务运行平稳后,堆中出现的Java对象的类型和数量,是否稳定。假如压力测试的TPS保持稳定,则从理论上讲,Java堆中出现、湮灭的对象的数量应当是稳定的,对象的数量不会有太大的变化。
  • 对比dump003.bindump004.bin,确认Java堆中业务相关的对象的类型和数量,是否有较大的下降。一般而言,运行过程中的Java对象,应当在压力测试结束后,在JVM的垃圾回收操作中被回收掉,不应存在大量的残留。
  • 对比dump001.bindump004.bin,由于压力测试已经结束,Java堆中对象的类型和数量,二者之间的差异应当比较小。

使用jmap命令,导出堆,使用MAT分析。

反复拨测业务,使用jstat命令观察GC情况。

修改代码的实现,降低内存占用。

问题仍然存在。

算法同事参与分析,使用valgrind分析,memcheck和massif,未发现内存泄漏点。

使用pmap观察,Java进程的内存空间,发现很多64MB的块。在网上找到很多文章。

缩小变量的值

关闭线程分配器,均无效

使用tcmalloc分配器,内存仍然会涨,并且偶发性的进程异常退出,因此本方案不能在生产环境使用。

最终,定期调用malloc_trim,定期向操作系统释放内存。

总结

无法更新GPU驱动的版本,流程操作比较复杂,时间和技术上均不允许。

参考资料

Kaldi

glibc

JVM

valgrind

malloc

pmap

ASR项目实战-交付过程中遇到的疑似内存泄漏问题的更多相关文章

  1. 深入理解Node.js中的垃圾回收和内存泄漏的捕获

    深入理解Node.js中的垃圾回收和内存泄漏的捕获 文章来自:http://wwsun.github.io/posts/understanding-nodejs-gc.html Jan 5, 2016 ...

  2. 在Activity中使用Thread导致的内存泄漏

    https://github.com/bboyfeiyu/android-tech-frontier/tree/master/issue-7/%E5%9C%A8Activity%E4%B8%AD%E4 ...

  3. 如何让使用create-react-app构建的项目在build过程中如何不生成.map文件

    避免create-react-app的项目在build的过程中生成 .map 文件的方法:主要是更改 package.json 里面的 build 命令!正式进入修改步骤前,推荐安装 cross-en ...

  4. JavaScript中的垃圾回收和内存泄漏

    摘要: JS内存管理. 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有. 前言 程序的运行需要内存.只要程序提出要求,操作系统或者运行时就必须供给内存.所谓的内存泄漏简单来说是不再用到的 ...

  5. iOS中滤镜处理及相关内存泄漏问题的解决

    最近工作之余在做一个美图秀秀的仿品 做到滤镜这块的时候  自己就参考了网上几位博主(名字忘了记,非常抱歉)的博客,但是发现跟着他们的demo做的滤镜处理,都会有很严重的内存泄漏,于是就自己按照大体的思 ...

  6. 面试官:小伙子,你给我说一下Java中什么情况会导致内存泄漏呢?

    概念 内存泄露:指程序中动态分配内存给一些临时对象,但对象不会被GC回收,它始终占用内存,被分配的对象可达但已无用.即无用对象持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间浪费. 可达 ...

  7. Python全栈工程师之从网页搭建入门到Flask全栈项目实战(5) - Flask中的ORM使用

    1.理解ORM ORM是MTV模型里面的Model模型 ORM(Object Relational Mapping),对象关系映射 举例:学生选课 学生和课程这两个实体,一个学生可以选择多门课程,一个 ...

  8. vue中解决three.js出现内存泄漏丢失上下文问题

    在跳转页面时添加以上代码即可. 在spa项目中,跳转页面并不会清楚已经创建的webgl实例,需要手动清楚.

  9. 教程-在Delphi中怎么查看是否有内存泄漏(Delphi2007)+WIN7

    相关资料:1.http://bbs.csdn.net/topics/390630932?page=1 PS:1.本实例D2007及以上版本支持.2.检测内存工具 EurekaLog fastmm 实例 ...

  10. Python工业互联网监控项目实战3—websocket to UI

    本小节继续演示如何在Django项目中采用早期websocket技术原型来实现把OPC服务端数据实时推送到UI端,让监控页面在另一种技术方式下,实时显示现场设备的工艺数据变化情况.本例我们仍然采用比较 ...

随机推荐

  1. 解读Redis常见命令

    Redis数据结构介绍 Redis是一个key-value的数据库,key一般是String类型,不过value的类型多种多样: 贴心小建议:命令不要死记,学会查询就好啦 Redis为了方便我们学习, ...

  2. FastGPT 接入飞书(不用写一行代码)

    FastGPT V4 版本已经发布,可以通过 Flow 可视化进行工作流编排,从而实现复杂的问答场景,例如联网谷歌搜索,操作数据库等等,功能非常强大,还没用过的同学赶紧去试试吧. 飞书相比同类产品算是 ...

  3. 小札 Combinatorics & Inclusion-Exclusion Principle 1

    「codeforces - 340E」Iahub and Permutations link. 把 \(1,\dots,n\) 中剩下没被固定的数的数量记作 \(s\),再把这其中不担心有会填到自己身 ...

  4. Mybatis-Plus 系列:简介和基本使用

    目录 一.简介 二.特性 三.基本使用 1.初始化数据库 2.初始化工程 3.精简 SpringBoot 相关日志 一.简介 官网:https://www.baomidou.com MyBatis-P ...

  5. Vue源码学习(八):生命周期调用

    好家伙,   Vue源码学习(七):合并生命周期(混入Vue.Mixin) 书接上回,在上一篇中,我们已经实现了合并生命周期 现在,我们要在我们的初始化过程中,注册生命周期 1.项目目录  红框为本篇 ...

  6. Python并发编程——协程、Greenlet模块、Gevent模块、Gevent之同步与异步、Gevent应用

    文章目录 一 引子 二 协程介绍 三 Greenlet 四 Gevent介绍 五 Gevent之同步与异步 六 Gevent之应用举例一 七 Gevent之应用举例二 一 引子 本节的主题是基于单线程 ...

  7. FragmentStatePagerAdapter

    public abstract class FragmentStatePagerAdapter extends PagerAdapter java.lang.Object    ↳ android.s ...

  8. 用阿里云镜像Centos7通过rpm和源码编译方式安装MySQL5版本

    这里只说明安装和注意事项,更具体的配置如端口号.cnf文件配置等就不写了. 阿里云开源镜像站资源目录 (aliyun.com) 我用的是基础版本. 基础版本镜像是默认不联网的,可以用下面的命令ping ...

  9. 一个Docker仓库问题的思考

    近期项目有云上部署要求,产品要打包成docker镜像.之前产品已经发布过docker版本本次只需要需要更新下,于是交代组内另外一个同学更新下镜像,想着应该很简单: 中间经过熟悉docker知识点搭建环 ...

  10. 从 SQL 查询优化技巧去看 h2 数据库查询原理

    本文目标是:了解查询的核心原理,对比 SQL 查询优化技巧在 h2database 中的落地实现. 前提:为了贴近实际应用,本文 Code Insight 基于 BTree 存储引擎. 数据查询核心原 ...