首页 > 其他分享 >【学到一个新名词】String interning(字符串驻留/字符串内部化)

【学到一个新名词】String interning(字符串驻留/字符串内部化)

时间:2023-11-23 15:59:06浏览次数:33  
标签:内部化 String sync VictoriaMetrics 字符串 string

作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!


在阅读 VictoriaMetrics v1.95.1 的命令行手册的时候,发现这样一段:

  -internStringCacheExpireDuration duration
     The expiry duration for caches for interned strings. See https://en.wikipedia.org/wiki/String_interning . See also -internStringMaxLen and -internStringDisableCache (default 6m0s)

什么是 String interning 呢?我通过了 wiki 链接学习了一下。
并且,我还找到了一个使用 String interning 技术的 golang 项目:https://github.com/josharian/intern . 作者还写了 blog: Interning strings in Go 来进一步介绍细节。

String interning 可以翻译为 字符串驻留 或者 字符串内部化。这个技巧用于节约频繁出现的字符串的空间占用,还可以用于频繁出现的字符串的比较的加速。
它的处理思路如下:

  1. 首先有一个全局的线程安全的键值对的字符串池;
类似于: map[string]string

然后把出现频率超级高的字符串存储在其中。

  1. 当出现新的字符串的时候,要先去字符串池中匹配。
    匹配到以后,程序就可以引用字符串池中的对象,而把当前引用的对象释放掉。
    当存在大量的这样内容相同的字符串的时候,这样做无疑是可以节省空间的。
    在这样的场景下,相当于时间换空间。

  2. 当字符串都来自字符串池,且需要频繁比较的时候,直接比较指针就可以确定是否是同一个字符串,而无需逐个字符比较。
    在这样的场景下,相当于空间换时间。

让我们再看看那个简单的 golang 实现的字符串内部化的源码:
see: https://github.com/josharian/intern/blob/master/intern.go

package intern

import "sync"

var (
	pool sync.Pool = sync.Pool{   // 作者想用 sync.Pool 来解决不引用时候的释放问题。但是并发环境下可能导致分配了多个键值对的字符串池。
		New: func() interface{} {    // sync.Pool 能够在并发环境下工作,不管怎么说,并发情况下使用不会出错。
			return make(map[string]string)
		},
	}
)

// String returns s, interned.
func String(s string) string {
	m := pool.Get().(map[string]string)
	c, ok := m[s]  // 这里要经过  1.计算字符串 hashcode; 2.hash 查找; 3.字符串内容比较。时间换空间的成本还是挺高的。
	if ok {
		pool.Put(m)
		return c  // 如果字符串池中存在,就置换为字符串池中的对象
	}
	m[s] = s  // 这里不会发生并发问题
	pool.Put(m)
	return s
}

// Bytes returns b converted to a string, interned.
func Bytes(b []byte) string {
	m := pool.Get().(map[string]string)
	c, ok := m[string(b)]  // string(b) 这里有个隐含的知识点:这种情况下编译器不会分配新的字符串对象。
	if ok {
		pool.Put(m)
		return c
	}
	s := string(b)
	m[s] = s
	pool.Put(m)
	return s
}

// todo: 这里还缺乏一个内容:当字符串都来自字符串池的时候,可以提供按照指针比较的方法
// 类似于:
//  isSmae := &str1==&str2 || str1==str2

看完了源码,这个字符串内部化似乎也没有很复杂很高深。或许某个存在大量重复字符串的场景中,我们很能用上这个技术。

最后,ChatGPT 还给了我一段 VictoriaMetrics 中使用 String interning 技术的讲解:

// 以下内容由  ChatGPT 4.0 产生:
VictoriaMetrics采用了字符串内部化技术来优化其时间序列数据库的性能,尤其在处理度量指标的元数据时。这些元数据通常由标签-值对组成,并且在VictoriaMetrics中以字符串的形式表示。下面是VictoriaMetrics中字符串内部化实现的详细信息:

1. 字符串内部化的目的和效果:在VictoriaMetrics中,由于元数据字符串在实践中变化不大,因此在数据收集过程中会产生大量重复。例如,`go_info` 度量指标的元数据包含Go版本的标签-值对。这些版本并不经常变化,但每次从应用程序中收集此度量时,都需要解析其元数据并在内存中分配,直到它被垃圾回收。为了避免多次存储相同的字符串,VictoriaMetrics通过内部化每个独特字符串一次,并在需要时引用它,从而显著节省内存​​​​。

2. 实现方法:在单线程应用中,字符串内部化的一种简单实现方式是使用Go的map类型。但在vmagent这样的多线程应用中,这种方法由于多核系统上访问此映射时可能产生大量争用,因此不太适合。为了在多线程环境下实现内部化,VictoriaMetrics使用了Go标准库中的sync.Map,这是一个线程安全的实现​​。

3. sync.Map的优化用途:sync.Map特别适用于以下两种情况:
  * 当一个给定的键只被写入一次但多次使用时,即缓存命中率高的情况。
  * 当多个goroutine为不同的键集合读取、写入和覆盖条目时,即每个goroutine使用不同的键集​​。

4. 内部化的注意事项:
  * 内存增长问题:随着时间的推移,map会逐渐增长,无限制的内存增长是危险的。为了防止map无限增长,需要定期旋转map,通过删除并重建它,或为键实现某种形式的生存时间(TTL)逻辑。
  * 参数检查:intern函数在处理常规字符串时表现良好,但除非锁定接口,否则可能会有人尝试传递可能破坏函数的内容。例如,在Go中,字节切片是可变的,不适合作为映射中的键使用​​。

5. 内部化的通用建议:在实现字符串内部化时,克隆传递给函数的字符串是一个好主意。这可以帮助消除一些微妙的错误。VictoriaMetrics使用字符串内部化来减少解析度量元数据时的内存使用。这是一种对于读取密集型工作负载效果最佳的强大技术,尤其适用于潜在字符串变体较少的情况​​。

总的来说,VictoriaMetrics通过字符串内部化有效地优化了内存使用和性能,特别是在处理大量重复且不经常变化的度量元数据时。这一技术不仅提高了性能,还降低了对垃圾收集器的负担,是处理大规模时间序列数据的有效方法。

Have fun.

标签:内部化,String,sync,VictoriaMetrics,字符串,string
From: https://www.cnblogs.com/ahfuzhang/p/17851725.html

相关文章

  • 字符串hash相关
    哈希c++里常用的hash是map和unordered_map前者是平衡树实现的,O(logn)的插入和搜索,后者是O(1)的插入和搜索但是前者有序,后者无序本文讲的是后者关于实现基本类型可以视所需空间大小选择不同的hash办法而我着重讲一下字符串的hash在字符串hash里DJBhashSDBMhash......
  • js 数组、字符串常用方法
    JavaScript数组的常用操作增:push()向数组的末尾添加一个或更多元素,并返回新的长度unshift()在数组开头添加任意多个值,然后返回新的数组长度splice()传入三个参数,分别是开始位置、0(要删除的元素数量)、插入的元素,返回空数组concat()首先会创建一个当前数组的副本,然后再把它......
  • 电脑网站支付报错“验签出错,建议检查签名字符串或私钥与应用公钥是否匹配”问题解决记
    在对接支付宝电脑网站支付的时候,遇到如下报错:“错误代码invalid-signature错误原因:验签出错,建议检查签名字符串或签名私钥与应用公钥是否匹配”。但展示的报错内容跟实际原因有所出入(在下文中有解答),这里记录下问题的解决排查过程。问题复现在对接电脑网站支付时,生成form表单......
  • js如何计算字符串的字节数
    如果计算字符长度只需要使用length,letstr="hello世界";console.log(str.length)//7如何计算所占用的字节数呢?functiongetByteLength(str){letlength=0;for(leti=0;i<str.length;i++){letcharCode=s......
  • Python中列表和字符串常用的数据去重方法你还记得几个?
    (Python中列表和字符串常用的数据去重方法你还记得几个?)1关于数据去重关于数据去重,咱们这里简单理解下,就是删除掉重复的数据;应用的场景比如某些产品产生的大数据,有很多重复的数据,为了不影响分析结果,我们可能需要对这些数据进行去重,删除重复的数据,提高分析效率等等。2字符串......
  • C#.NET 循环字符串 V20231123
    C#.NET循环字符串V20231123 publicstaticboolIsIllegalOutTradeNo(stringOutTradeNo){foreach(chariteminOutTradeNo){if(item=='('||item==')'||item==','||item=......
  • 字符串之多种个性化格式处理
    此文重点讲述:字符串之个性化格式处理。个性化字符串工具类importjava.util.List;importjava.util.Random;importjava.util.regex.Matcher;importjava.util.regex.Pattern;/***字符串工具类*/publicfinalclassStringUtils{privateStringUtils(){......
  • (字符串)01-字符串变形
    1importjava.util.*;23publicclassSolution{4/**5*@paramsstring字符串6*@paramnint整型7*@returnstring字符串8*/9publicStringtrans(Strings,intn){10//校验字符串长度11if......
  • android.content.res.Resources$NotFoundException: String resource ID #0x1
    在Android开发中如果出现android.content.res.Resources$NotFoundException:StringresourceID#0x1这样的错误,你想也不用想,一定是Textview控件显示数据出了问题:mTextview.setText(这里的传入的数据一定写成int类型了)。我们需要做的是eg:mTextview.setText(1+""),也就是参数转......
  • 浅谈字符串哈希 入门
    基本介绍字符串哈希的主要思路是这样的:首先选定一个进制\(P\),对于一个长度为\(N\)的字符串\(S\)的所有\(i(1\leqi\leqn)\)的\(S_1,S_2,...,S_i\)子串表示成\(P\)进制的值预处理记录下来。这样判断\(S_i,S_{i+1},...,S_{i+m-1}\)和\(T_1,T_2,...,T_m\)是否相等......