爱吃苹果的鱼 發表於 2025-9-6 19:33:00

敏感词性能提升14倍优化全过程 v0.29.0

<h1 id="敏感词性能调优系列">敏感词性能调优系列</h1>
<p>v0.29.0 敏感词性能优化提升 14 倍全过程</p>
<p>v0.29.1 敏感词性能优化之内部类+迭代器内部类</p>
<p>v0.29.2 敏感词性能优化之基本类型拆箱、装箱的进一步优化的尝试</p>
<p>v0.29.3 敏感词性能优化之繁简体转换 opencc4j 优化</p>
<h1 id="背景">背景</h1>
<p>有一天,群里收到小伙伴提的一个问题,为什么程序 sensitive-word 第一次执行这么慢?</p>
<blockquote>
<p>sensitive-word-131</p>
</blockquote>
<h1 id="初步验证">初步验证</h1>
<p>自己本地用 v0.27.1 验证了一下,确实很奇怪,第一次明显很慢。</p>
<p>为了排除一些干扰项,我们把一些配置先关闭。</p>
<p>最简单的我们用 System.nanoTime 输出一下耗时,用 mills 也行。</p>
<pre><code class="language-java">public static void main(String[] args) {
      final List&lt;String&gt; allWord = Arrays.asList("敏感","最强","定制", "81", "医疗器械");
      String demo1 = "产品尺寸参数§60mn§50mm§210枚/包§160枚/包§名称A4银色不干胶§规格60mm*40mm 送配套模板§规格70mm*50mm 送配套模板§数量每大张21枚一包10张总计210枚§数量每大张16枚一包10张总计160枚§适用激光打印机打印油性笔书写§95mm§100mn§55mm§100枚/包§80枚/包§名称 A4银色不干胶§规格95mm*55mm 送配套模板§规格100mm*70mm 送配套模板§数量每大张10枚一包10张总计100枚§数量 每大张8枚一包10张 总计80枚§100mm§120枚/包§140枚/包§规格80mm*50mm 送配套模板§规格100mm*40mm 送配套模板§数量每大张12枚一包10张总计120枚§数量§每大张14枚包10张总计140枚§适用 激光打印机打印油性笔书写§40mm§65mm§70mm§35mm§200枚/包§240枚/包§规格70mm*40mm送配套模板§规格§65mm*35mm 送配套模板§数量 每大张20枚一包10张总计200枚§每大张24枚包10张总计240枚§适 激光打印机打印油性笔书写§适用§激光打印机打印油性笔书写§40mn§280枚/包§360枚/包§规格50mm*40mm 送配套模板§规格40mm*30mm 送配套模板§数量每大张28枚一包10张总计280枚§数量每大张36枚一包10张总计360枚§45.7mm§38.1mm§400枚/包§650枚/包§45.7mm*25.4mm送配套模板§38.1mm*21.2mm 送配套模板§每大张40枚一包10张总计400枚§数量每大张65枚一包10张总计650枚§30mm§25mr§20mm§840枚/包§1260枚/包§规格 30mm*20mm 送配套模板§规格25mm*13mm 送配套模板§数量每张84枚包10张总计840枚§数量每大张126枚一包10张总计1260枚§46mm§意制§任§1000枚/包§定§名称定制A4内割银不胶§规格46mm*11.1mm送配套模板§任意规格定制§每大张100枚包10张总计1000枚§包10张满5包送专属模板§适激光打印机打印油性笔书写§产品实拍§8格打印实拍展示(100mm*70mm)§上海荠骞文化用品固定资产标识卡§资产编号:§规格型号:§资产名称:§使用状态:§资产类别:§资产原值§存放地点§生产厂家:§使用人§备§注:§*请爱护公司财产,不要随意撕毁此标签§16格全内容打印实拍展示§固定资产标识卡§资产名称§四层货架(平板)§资产编号§3F跑菜区§规格型号§1800×500×1500§使用部门§财务部§使用时间§2019-04-26§李强§21格手写款打印展示 (60mm*40mm)§固定资标识卡§36格打印实拍展示(40mm*30mm)§固定资产标签§名称:§编号:§部门:§40格打印实拍展示(45.7mm*25.4mm)§固定资§名称:电脑§编号:20210§部门:财务部§20210201§使用人:我最强§八:找最强§编号:20210201§65格打印实拍展示(38mm*21mm)§名称:§编号:§数量:§数量:§100格打印实拍展示(46mm*11.1mm)§客服电话:159 9569 3815§: 159 9569 3815§.§客服电话:159 9569§客服电话:1599§客服电话§服电话:159 9569 3815§话:159 9569 3815§客服电话:1599569 3815§电话:159 9569 3815§9569 3815§159 9569 3815§客服电话:§低值易耗品标识牌(70mm*50mm)§购买日期§保管部门§责任人§生产厂家§不要随意撕毁此标牌*§*请爱护公司财产,不要随意撕导§品标识牌§低值易耗品标识牌§随意撕毁此标牌*§*请爱护公司财产,不要随意撕毁此标牌*§三人沙发§行政酒廊§2200*860*900§2018-07-23§应用范围§多用于产品信息固有资产登记航空仓库管理 医疗政府机构等§Mainly used for product information inherent assets registration, aviation warehouse management, medi§cal government institutions, etc§政府单位§企业办公§仓储行业§医疗器械§教育单位§耐用品§电子产品包装§商城卖场";

      // 初始化敏感词库
      SensitiveWordBs sensitiveWordBs = SensitiveWordBs.newInstance()
                .wordFailFast(true)
                .wordAllow(WordAllows.empty())
                .wordDeny(new IWordDeny() {
                  @Override
                  public List&lt;String&gt; deny() {
                        return allWord;
                  }
                })
                .ignoreChineseStyle(false)
                .ignoreCase(false)
                .ignoreEnglishStyle(false)
                .ignoreNumStyle(false)
                .ignoreRepeat(false)
                .ignoreWidth(false)
                .wordTag(WordTags.none())
                .init();

      costTimeTest(sensitiveWordBs, demo1);
    }

    private static void costTimeTest(SensitiveWordBs sensitiveWordBs, String demo1) {
      int count = 5;
      for (int i = 0; i &lt; count; i++) {
            long startTime = System.nanoTime();
            List&lt;String&gt; emitWord1 = sensitiveWordBs.findAll(demo1);
            long costTime = System.nanoTime() - startTime;
            System.out.println("cost=" + costTime);
      }
    }
</code></pre>
<p>输出:</p>
<pre><code>cost=27687800
cost=3623000
cost=2764000
cost=4456500
cost=6652700
</code></pre>
<p>这里是纳秒,看比例也看得出第一次比较慢。</p>
<pre><code>long ns = 1_000_000_000L;// 1 秒 = 1e9 纳秒
long us = 1_000_000L;      // 1 秒 = 1e6 微秒
long ms = 1_000L;          // 1 秒 = 1e3 毫秒
</code></pre>
<h2 id="排除问题">排除问题</h2>
<p>确认了问题之后,就要找到到底慢在哪里。</p>
<p>有一些方法:</p>
<p>1)每个方法加耗时日志,适用性广,但是比较麻烦。如果没有源代码的话,也无法直接修改。</p>
<p>2) 用 Profiling 工具更方便一些。</p>
<p>Java Flight Recorder (JFR)(JDK 自带,jcmd 或 jfr 启动)</p>
<p>VisualVM(免费 GUI,适合初步分析)</p>
<p>Async Profiler + 火焰图(性能瓶颈定位神器)</p>
<p>YourKit / JProfiler(商业工具,功能更全)</p>
<h2 id="初步猜想">初步猜想</h2>
<p>一些初步的猜想:</p>
<ol>
<li>
<p>第一次执行,初始化加载了一些信息比较慢。</p>
</li>
<li>
<p>后续执行被 jvm 编译优化了,性能提升。</p>
</li>
<li>
<p>因为后续执行耗时明显下降,一些场景的比如 IO、锁之类的可以暂时排除。</p>
</li>
</ol>
<h1 id="idea-中使用-profile">idea 中使用 profile</h1>
<h2 id="说明">说明</h2>
<p>本地使用的是 idea 免费社区版本。</p>
<h2 id="idea-ultimate">IDEA Ultimate</h2>
<p>IDEA Ultimate 自带了对 Java Flight Recorder (JFR) 的支持。启动应用时,点 Run → Profile…,选择 Java Flight Recorder。</p>
<h2 id="idea--async-profiler-插件">IDEA + Async Profiler 插件</h2>
<p>IDEA 提供了 Async Profiler 集成(从 2020.3 开始支持)。</p>
<p>用法:右键 Run 旁边选择 Profile with Async Profiler。</p>
<p>输出直接是 火焰图,更直观地看哪一层方法耗时最多。</p>
<p>当然,这两个方法直接用用一个不足,那就是前面的初始化信息也会被记录,有一定的干扰性。</p>
<p>所以需要次数多一些,比如1W次</p>
<h2 id="编码-profile">编码 profile</h2>
<p>JDK 11+ 提供了 jdk.jfr API,可以在代码里手动控制录制</p>
<pre><code class="language-java">import jdk.jfr.Recording;
import java.nio.file.Path;

public class JFRDemo {
    public static void main(String[] args) throws Exception {
      try (Recording recording = new Recording()) {
            recording.start();
            
            // 运行你要分析的代码
            MyUtil.someMethod();
            
            recording.stop();
            recording.dump(Path.of("app.jfr")); // 保存到文件
      }
    }
}
</code></pre>
<p>我们略微调整</p>
<pre><code class="language-java">   private static void costTimeTest(SensitiveWordBs sensitiveWordBs, String demo1) throws IOException {
      int count = 5;

      try (Recording recording = new Recording()) {
            recording.start();

            for (int i = 0; i &lt; count; i++) {
                long startTime = System.nanoTime();
                List&lt;String&gt; emitWord1 = sensitiveWordBs.findAll(demo1);
                long costTime = System.nanoTime() - startTime;
                System.out.println("cost=" + costTime);
            }

            recording.stop();
            recording.dump(Path.of("app.jfr")); // 保存到文件
      }
    }
</code></pre>
<p>执行后可以看到根目录下 app.jfr,发现这个生成 jfr 有问题。</p>
<h1 id="jfr-文件如何文件如何打开分析呢">jfr 文件如何文件如何打开分析呢?</h1>
<h2 id="jmc">jmc</h2>
<p>JDK 11+ 一般自带 JMC(或者单独下载安装 JMC)</p>
<p><code>jmc</code> 然后选择打开,发现自己的 jdk11 并没有,应该和 jvisual 一样,后续被单独拆开了。</p>
<p>官网下载: https://www.oracle.com/java/technologies/jdk-mission-control.html</p>
<p>或者 OpenJDK 社区版的 JMC: https://github.com/openjdk/jmc</p>
<p>下载后直接运行 JMC GUI,然后打开 .jfr 文件进行分析。</p>
<h3 id="下载">下载</h3>
<p>可以在 https://www.oracle.com/java/technologies/javase/products-jmc9-downloads.html 页面选择合适自己的安装包。</p>
<h2 id="firefox-profiler">firefox profiler</h2>
<p>看了一下 Async Profiler 用的应该就是https://profiler.firefox.com/ 这个页面分析文件的。</p>
<p>如果可以的话,你也可以直接用这个网页。</p>
<p>可以选择本地的 JFR 文件,或者是 URL。</p>
<h1 id="整体耗时优化">整体耗时优化</h1>
<h2 id="1万次">1万次</h2>
<p>这是一个循环调用 1W 次的例子,可以看到整体的耗时:</p>
<p>可以直接打开这个链接查看: https://share.firefox.dev/4lZljPd</p>
<p>整体耗时:7890ms</p>
<pre><code class="language-java">      long time = System.currentTimeMillis();
      costTimeTest(sensitiveWordBs, demo1);
      long cTime = System.currentTimeMillis() - time;
      System.out.println("---DONE"+cTime);
</code></pre>
<h2 id="慢的点">慢的点</h2>
<p>可以看到比较慢的2个点</p>
<ol>
<li>
<p><code>String.toCharArray(): char[]</code> 54%</p>
</li>
<li>
<p><code>InnerWordFormatUtils.formatCharsMapping(String, IWordContext): Map</code> 11%</p>
</li>
</ol>
<h2 id="优化方案">优化方案</h2>
<p>针对1,我们尝试优化一下,toCharArray 看 String 源码会重新创建 chars,占用内存,我们尽可能的避免。</p>
<pre><code class="language-java">    /**
   * Converts this string to a new character array.
   *
   * @returna newly allocated character array whose length is the length
   *          of this string and whose contents are initialized to contain
   *          the character sequence represented by this string.
   */
    public char[] toCharArray() {
      return isLatin1() ? StringLatin1.toChars(value)
                        : StringUTF16.toChars(value);
    }
</code></pre>
<p>针对2,用 char[] 数组替代肯定是最好的,但是字符比较复杂,暂时还是 map 适用性更强。</p>
<p>如果不指定格式转换,可以考虑 map 为空,取不到用原始值,减少这一份消耗。</p>
<h2 id="innerwordformatutilsformatcharsmappingstring-iwordcontext-map-优化">InnerWordFormatUtils.formatCharsMapping(String, IWordContext): Map 优化</h2>
<p>优化方案:新增一个针对整体字符串 format 的处理类 IWordFormatText,如果 IWordFormat 是系统默认的 none,直接返回 emptyMap</p>
<p>限制场景:仅针对不做任何优化的场景有作用。</p>
<p>内存优化:只有映射 c 和 mc 不同,才放入映射 map</p>
<p>实现:</p>
<pre><code class="language-java">/**
* 默认实现
*
* @author d
* @since 0.28.0
*/
public class WordFormatTextDefault extends AbstractWordFormatText {

    @Override
    protected Map&lt;Character, Character&gt; doFormat(String text, IWordContext context) {
      // 单个字符串里信息
      final IWordFormat wordFormat = context.wordFormat();
      // 不需要处理的场景
      if(wordFormat.getClass().getName().equals(WordFormatNone.class.getName())) {
            return Collections.emptyMap();
      }

      Map&lt;Character, Character&gt; map = new HashMap&lt;&gt;();
      for(int i = 0; i &lt; text.length(); i++) {
            char c = text.charAt(i);
            char mc = wordFormat.format(c, context);

            if(c != mc) {
                map.put(c, mc);
            }
      }
      return map;
    }
   
}
</code></pre>
<p>JFR 对比效果:JFR 为 7571</p>
<p>严谨起见,我们加一下额外项目的测试对比,对比5次</p>
<p>v0.27.1 直接运行1W次,5 次均值:7255 ,明细如下:</p>
<pre><code>7613
7166
7156
7176
7164
</code></pre>
<p>新代码直接运行1W次,均值 7139.4,明细如下:</p>
<pre><code>7650
7074
7002
6979
6992
</code></pre>
<p>看来这个 cpu 火焰图和时间耗时不是严格等价。</p>
<p>这里只提升了 1% 左右的性能。</p>
<p>罢了,看在内存的面子上,我们先发布一个版本。</p>
<h2 id="发布">发布</h2>
<p>此代码发布,放在 v0.28.0 版本。</p>
<h1 id="针对-tochararray-的改进">针对 toCharArray 的改进</h1>
<h2 id="思路">思路</h2>
<p>我们尽量避免 toCharArray,使用原始的字符串 string.charAt 替代。</p>
<p>不过这个 charAt 有一点不太好:</p>
<pre><code class="language-java">public char charAt(int index) {
    if ((index &lt; 0) || (index &gt;= value.length)) {
      throw new StringIndexOutOfBoundsException(index);
    }
    return value;
}
</code></pre>
<p>如果 jdk 能提供一个直接访问的方法将完美,可惜去不得。</p>
<h2 id="好处">好处</h2>
<p>带来的好处就是节省了 toCharArray 带来的方法+内存消耗。</p>
<h2 id="修改点">修改点</h2>
<p>修改点比较多,涉及到的地方够改掉了。</p>
<p>遗憾的是破坏了两个带 chars 的接口,接口本身设计的不够好。</p>
<p><code>ISensitiveWordCharIgnore</code> 和 <code>IWordReplace</code></p>
<p>从原始的 chars-&gt;text</p>
<h2 id="效果">效果</h2>
<p>新代码,同样1w次循环,耗时 508.8ms,明细:</p>
<pre><code>792
456
449
410
437
</code></pre>
<p>和 v0.28.0 对比提升了多少倍呢?大概 14 倍</p>
<p>7139.4 ÷ 508.8 ≈ 14.03</p>
<h2 id="反思">反思</h2>
<p>这个大概率是每次 case 都一样,导致 jvm 优化效果很不错。</p>
<h2 id="随机测试">随机测试</h2>
<p>我们来用随机,对比测试一下</p>
<h3 id="测试-case">测试 CASE</h3>
<pre><code class="language-java">    public static void main(String[] args) {
      for(int k = 0; k &lt; 5; k++) {
            // 1W 次
            long start = System.currentTimeMillis();
            for(int i = 0; i &lt; 10000; i++) {
                String randomText = "产品尺寸参数§60mn§50mm§210枚/包§160枚/包§名称A4银色不干胶§规格60mm*40mm 送配套模板§规格70mm*50mm 送配套模板§数量每大张21枚一包10张总计210枚§数量每大张16枚一包10张总计160枚§适用激光打印机打印油性笔书写§95mm§100mn§55mm§100枚/包§80枚/包§名称 A4银色不干胶§规格95mm*55mm 送配套模板§规格100mm*70mm 送配套模板§数量每大张10枚一包10张总计100枚§数量 每大张8枚一包10张 总计80枚§100mm§120枚/包§140枚/包§规格80mm*50mm 送配套模板§规格100mm*40mm 送配套模板§数量每大张12枚一包10张总计120枚§数量§每大张14枚包10张总计140枚§适用 激光打印机打印油性笔书写§40mm§65mm§70mm§35mm§200枚/包§240枚/包§规格70mm*40mm送配套模板§规格§65mm*35mm 送配套模板§数量 每大张20枚一包10张总计200枚§每大张24枚包10张总计240枚§适 激光打印机打印油性笔书写§适用§激光打印机打印油性笔书写§40mn§280枚/包§360枚/包§规格50mm*40mm 送配套模板§规格40mm*30mm 送配套模板§数量每大张28枚一包10张总计280枚§数量每大张36枚一包10张总计360枚§45.7mm§38.1mm§400枚/包§650枚/包§45.7mm*25.4mm送配套模板§38.1mm*21.2mm 送配套模板§每大张40枚一包10张总计400枚§数量每大张65枚一包10张总计650枚§30mm§25mr§20mm§840枚/包§1260枚/包§规格 30mm*20mm 送配套模板§规格25mm*13mm 送配套模板§数量每张84枚包10张总计840枚§数量每大张126枚一包10张总计1260枚§46mm§意制§任§1000枚/包§定§名称定制A4内割银不胶§规格46mm*11.1mm送配套模板§任意规格定制§每大张100枚包10张总计1000枚§包10张满5包送专属模板§适激光打印机打印油性笔书写§产品实拍§8格打印实拍展示(100mm*70mm)§上海荠骞文化用品固定资产标识卡§资产编号:§规格型号:§资产名称:§使用状态:§资产类别:§资产原值§存放地点§生产厂家:§使用人§备§注:§*请爱护公司财产,不要随意撕毁此标签§16格全内容打印实拍展示§固定资产标识卡§资产名称§四层货架(平板)§资产编号§3F跑菜区§规格型号§1800×500×1500§使用部门§财务部§使用时间§2019-04-26§李强§21格手写款打印展示 (60mm*40mm)§固定资标识卡§36格打印实拍展示(40mm*30mm)§固定资产标签§名称:§编号:§部门:§40格打印实拍展示(45.7mm*25.4mm)§固定资§名称:电脑§编号:20210§部门:财务部§20210201§使用人:我最强§八:找最强§编号:20210201§65格打印实拍展示(38mm*21mm)§名称:§编号:§数量:§数量:§100格打印实拍展示(46mm*11.1mm)§客服电话:159 9569 3815§: 159 9569 3815§.§客服电话:159 9569§客服电话:1599§客服电话§服电话:159 9569 3815§话:159 9569 3815§客服电话:1599569 3815§电话:159 9569 3815§9569 3815§159 9569 3815§客服电话:§低值易耗品标识牌(70mm*50mm)§购买日期§保管部门§责任人§生产厂家§不要随意撕毁此标牌*§*请爱护公司财产,不要随意撕导§品标识牌§低值易耗品标识牌§随意撕毁此标牌*§*请爱护公司财产,不要随意撕毁此标牌*§三人沙发§行政酒廊§2200*860*900§2018-07-23§应用范围§多用于产品信息固有资产登记航空仓库管理 医疗政府机构等§"
                        + RandomUtil.randomString("1234567890bcdefghiJKLMNOPQRSTUVWXYZ", 100);
                SensitiveWordHelper.findAll(randomText);
            }
            long end = System.currentTimeMillis();
            System.out.println(end-start);
      }
    }
</code></pre>
<h3 id="新代码">新代码</h3>
<p>实际测试发现这个在文本长的时候,效果更显著。应该是 toCharArray 的代价更高</p>
<p>5次均值 1785.2:</p>
<pre><code>2308
1621
1595
1664
1738
</code></pre>
<h3 id="v0280">v0.28.0</h3>
<p>5 次均值:7636.4</p>
<pre><code>8438
7463
7404
7436
7441
</code></pre>
<h3 id="总结">总结</h3>
<p>这个测试文本量,效果大概提升 4 倍</p>
<p>文本越长,效果越显著。</p>
<h1 id="查看一次耗时">查看一次耗时</h1>
<h2 id="说明-1">说明</h2>
<p>无论是整体跑,还是单个跑,都会发现第一次明显比较慢。</p>
<p>多次跑应该是 jvm 优化,我们来看一下单词的</p>
<p>我们回到问题的最开始,看的出来平均耗时优化了,但是初始化耗时还是这么慢。</p>
<h2 id="跑5次">跑5次</h2>
<p>5 次效果如下:</p>
<pre><code>21
1
1
1
1
</code></pre>
<p>单词跑有个问题,前面的 wordBs 初始化干扰太大,我们暂时用加耗时的方法来处理下。</p>
<p>第二个问题:mills 不够精确,可以用 nanoTime 替代。</p>
<h2 id="我们改为-nanotime-跑5次">我们改为 nanoTime 跑5次</h2>
<p>看起来 ms 差不多,实际上还是差很多的。</p>
<pre><code>13518800
2854600
1836900
1503900
925400
</code></pre>
<p>我们重点看一下第一次为什么这么慢。</p>
<h2 id="子方法耗时拆分">子方法耗时拆分</h2>
<p>演示一下,二分法:</p>
<pre><code class="language-java">public &lt;R&gt; List&lt;R&gt; findAll(final String target, final IWordResultHandler&lt;R&gt; handler) {
//      ArgUtil.notNull(handler, "handler");

      long s1 = System.nanoTime();
      List&lt;IWordResult&gt; wordResults = sensitiveWord.findAll(target, context);
      System.out.println(System.nanoTime()-s1);

      long s2 = System.nanoTime();
      List&lt;R&gt; res = CollectionUtil.toList(wordResults, new IHandler&lt;IWordResult, R&gt;() {
            @Override
            public R handle(IWordResult wordResult) {
                return handler.handle(wordResult, context, target);
            }
      });
      System.out.println(System.nanoTime()-s2);
      return res;
    }
</code></pre>
<p>耗时:</p>
<pre><code>17042800#findAll
2316800#handle
22018600#total
</code></pre>
<p>不过有一个问题,这个不太稳定。只能看比例。</p>
<p>很离谱的一个点:</p>
<p>中间只隔了下面的方法,耗时 3ms。</p>
<pre><code class="language-java">public List&lt;String&gt; findAll(final String target) {
    return findAll(target, WordResultHandlers.word());
}
</code></pre>
<h2 id="原因">原因</h2>
<p>所以第一次请求,涉及到了</p>
<p>JVM 类和方法还没加载:第一次调用 sensitiveWord.findAll 可能会触发类加载、静态初始化。</p>
<p>JIT(即时编译)没起作用:第一次运行是解释执行,速度慢。</p>
<p>热点优化没完成:JIT 会把频繁调用的方法编译成本地代码,但需要多次调用才会触发。</p>
<h2 id="解决方案">解决方案</h2>
<p>其实也不难,可以提前调预热一下。</p>
<p>在 init() 的时候,指定预热策略,简单的触发一下。</p>
<p>避免不太懂的性能测试的伙伴执着于第一次的问题。</p>
<p>策略支持用户自定义。</p>
<h2 id="效果-1">效果</h2>
<p>优化后的效果,还是会有一些,不过可以接受。</p>
<pre><code class="language-java">4520900
2804500
2493600
976000
1011100
</code></pre>
<p>和 v0.28.0 对比</p>
<pre><code>20762000
3903400
3401200
8672000
7852000
</code></pre>
<p>第一次峰值从 20ms=&gt;5ms 左右。</p>
<h2 id="反思-1">反思</h2>
<p>jvm 的内置优化过于强求暂时意义不大,还是推荐性能压测先做预热。</p>
<h1 id="开源地址">开源地址</h1>
<blockquote>
<p>https://github.com/houbb/sensitive-word</p>
</blockquote>
<h1 id="小结">小结</h1>
<p>性能优化是一个支持以恒的过程,每次的改动都可能会导致性能有所影响。</p><br><br>
来源:https://www.cnblogs.com/houbbBlogs/p/19077276
頁: [1]
查看完整版本: 敏感词性能提升14倍优化全过程 v0.29.0