遗留问题
上一小节说到,sdscat函数不仅对SDS进行了拼接而且还为SDS分配了13字节的未使用空间,而拼接后的字符串恰好为13字节。这里涉及到了修改字符串的内存重分配的问题。
修改字符串的内存重分配次数
前提:C字符串长度为n则需要n+1个字符长的数组。
因为此前提,在每次修改C字符串时就需要对这个数组进行一次内存重分配操作。
为什么要进行内存重分配?
1.如在拼接字符串时,如果数组空间不足,则会导致缓冲区溢出,在执行拼接之前,先进行内存重分配,则可以避免此问题。
2.如在截断字符串时,如果直接截断,则数组会产生无用的空间而无法释放,产生内存泄露。在截断之前,进行内存重分配释放掉数组不用的空间。
内存重分配的影响
1.复杂的算法
2.执行系统调用
所以这是一个耗时的操作。
Redis是用于速度要求严苛、数据频繁被使用的场景。如果修改一次字符串就要进行一次内存重分配,会对其性能产生恶劣的影响。
所以在Redis中需要减少内存重分配的次数。
SDS中的解决办法
进行内存重分配的原因就是C字符串长度和底层数组长度之间的关联。故以此为突破点来设计。
在SDS中buf数组长度不一定是字符串长度加一,因为数组里可以包含未使用的字节,free就可以记录。
主要有两种设计
空间预分配和惰性空间释放
空间预分配
用于SDS的字符串增长操作:当SDS的API对一个SDS进行修改并需要拓展空间时,程序不仅会分配修改所必要的空间,同时会分配额外未使用的空间。
在遗留问题中,就是因为空间预分配,SDS中会有13字节的未使用空间。
分配未使用空间数量公式:
1.若SDS长度(len)小于1mb,则程序分配和len属性同样大小的空间,这时,len==free。(遗留问题中分配13字节的原因)
2.若SDS长度(len)大于等于1mb,则程序分配1mb的未使用空间。如,进行修改之后,SDS的len将变成30mb,那么程序会分配1mb的未使用空间。数组实际长度为30mb+1mb+1byte。
空间预分配的好处
通过预分配策略,可以使SDS将连续增长n次字符串所需的内存重分配次数从必定n次减少为最多n次。
惰性空间释放
优化SDS的字符串缩短操作。当SDS的API需要缩短SDS保存的字符串长度,程序不会立即用内存重分配来回收缩短多出来的字节,而是用free来将这些字节数量记录下来,并等待使用。
这样当再次对字符串修改时,会有预留的空间,如果足够则不需再次进行内存重分配。
惰性空间释放造成内存浪费
SDS提供了相应的API可以真正释放未使用空间,故无需担心造成内存浪费。