Tcl8.6-Tcl9.0在大型EDA项目下内存增加的问题

时间仓促没有时间排版

公司内部大型case回归中出现了内存增加的问题,最初怀疑是代码变动导致,根据tcmalloc内存池的实时占用打印最终分析出来是tcl命令string first单次操作增加了GB级别的内存,一起来看一下是怎么个事

测试条件准备

  1. 一个1G的字符串和一个2G的字符串

  1. 两个标准的tclsh,tcl8.6与tcl9.0.

  2. 测试脚本,此脚本读入case,从/proc/self/status获取真实内存消耗,分别计算处理后的内存占用。

# mem_test_default.tcl
proc rss {} {
    set f [open /proc/self/status r]
    set data [read $f]
    close $f
    foreach line [split $data "\n"] {
        if {[string match "VmRSS:*" $line]} {
            return [string trim $line]
        }
    }
}

set path [lindex $argv 0]

puts "==== start ===="
puts [rss]
puts "tcl_platform(encoding): $tcl_platform(byteOrder) / default system encoding: [encoding system]"

# 完全默认方式:不指定 -encoding,不用 rb,不手动 convert
set fh [open $path]
set s [read $fh]
close $fh

puts "==== after default read ===="
puts [rss]
puts "string length (chars): [string length $s]"
puts "repr: [tcl::unsupported::representation $s]"

set idx [string first "THIS_PATTERN_PROBABLY_NOT_IN_FILE_XYZ" $s]
puts "==== after string first (idx=$idx) ===="
puts [rss]
puts "repr: [tcl::unsupported::representation $s]"

使用方法

tclsh8.6 mem_test.tcl /path/to/1g_file.txt
tclsh9.0 mem_test.tcl /path/to/1g_file.txt

tclsh8.6 mem_test.tcl /path/to/2g_file.txt
tclsh9.0 mem_test.tcl /path/to/2g_file.txt

测试结果

tcl8.6-2g

==== start ====
VmRSS:      2560 kB
tcl_platform(encoding): littleEndian / default system encoding: utf-8
==== after default read ====
VmRSS:   1912320 kB
string length (chars): 1957780176
repr: value is a string with a refcount of 2, object pointer at 0x1599f5c0, internal representation 0x159b45b0:0x1599f260, string representation "This software..."
==== after string first (idx=-1) ====
VmRSS:   5734400 kB
repr: value is a string with a refcount of 2, object pointer at 0x1599f5c0, internal representation 0x7f47bcc0a020:0x1599f260, string representation "This software..."

tcl9.0-2g

==== start ====
VmRSS:      3840 kB
tcl_platform(encoding): littleEndian / default system encoding: utf-8
==== after default read ====
VmRSS:   1916160 kB
string length (chars): 1957780176
repr: value is a string with a refcount of 2, object pointer at 0x6ac8310, internal representation 0x6af3200:0x0, string representation "This software..."
==== after string first (idx=-1) ====
VmRSS:   9561600 kB
repr: value is a string with a refcount of 2, object pointer at 0x6ac8310, internal representation 0x7fc96b1ba020:0x0, string representation "This software..."

可以看到明显内存的增加,增加的量
9561600 -5734400 =3,827,200

好巧不巧
3,827,200 / 1000000 /2 =1.9136 G

等于文件的大小

研究开始-真相只有一个

此处省略一万个流程

直接看罪魁祸首
Tcl Improvement Proposals: TIP 497: Full support for Unicode planes 1-16.
tcl9.0为了全面支持Unicode:

Modify the Tcl_UniChar type, such that - by default - == 4 (was: 2).

查看文件编码形式,好像完美符合情况

重点:string first这种精确匹配的形式会将字符串先从UTF-8转换为Unicode,因此在tcl9中占用的字节数量相比于tcl8.x翻倍,因为 Tcl_UniChar type 由2字节变为了4字节,为什么?因为要支持emoji~!!!

这是tcl9头文件中直接证据

因此,原来的utf8编码的字符串转到unicode之后相比于tcl8.x的内存消耗刚好加上,内容x2的大小。

对1g文件进行验证:

结果完全符合,刚好增加越1.9g,也就是1g的两倍。

大型项目中做大型字符串匹配应当小心!

证毕。

解决思路

在tcl官方wiki中有这么一项:Using string Functions for Binary data

https://wiki.tcl-lang.org/page/string

当中说明了一些命令在运行时”可能“可以不用转换成unicode,而另一些命令一定会触发转换

基于此我们进行测试

  • 第一项,将其转码为binary-配合stringmatch

实际测试没有好转

  • 第二项,将其转码,并使用string match官方文档中表明可能不会进行转码

测试结果

实测结果表明只有文件同大小的结果被写入内存,这种情况甚至比原来好一倍!

因此第一个思路就是用适当的转码方式以及适当的命令,这会让tcl在处理超大文件时显著降低内存。

第二个思路就是从代码层面增加匹配的逻辑,不走tclsh。

打完收工

LICENSED UNDER CC BY-NC-SA 4.0