时间仓促没有时间排版
公司内部大型case回归中出现了内存增加的问题,最初怀疑是代码变动导致,根据tcmalloc内存池的实时占用打印最终分析出来是tcl命令string first单次操作增加了GB级别的内存,一起来看一下是怎么个事
测试条件准备
一个1G的字符串和一个2G的字符串


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

测试脚本,此脚本读入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。
打完收工