Go's slice capacity expansion mechanism - gongzong No.: stack future
Please move here to have a better reading experience
Capacity expansion
To tell you the truth, I read other articles that slice's capacity expansion is very simple. If it is less than 1024, double it; If it is greater than or equal to 1024, the capacity shall be expanded according to 1.25; I'm very upset that such irresponsible articles mislead beginners. Today I'll bring you a slice expansion mechanism at the source level. Don't be afraid. Everything is so simple.
1. Take a look at an example (< 1024)
package main import "fmt" func main() { s1 := make([]int, 1023) s2 := make([]int, 1) s1 = append(s1, s2...) fmt.Println(cap(s1), len(s1)) } The output is: 2048 1024 It can be seen from the results that the capacity of less than 1024 is indeed expanded by twice, which can be calculated as: f(x) = 2x (x<1024)
Do you know why it is 1024? Someone asked the go author why it wasn't 2048. Guess what the author said. A very simple sentence: because the number 1024 is very special in the computer world. It is a number with 2 as the bottom and 10 as the index. It's so simple. So sometimes when you look at the source code, don't tangle with the details. Once you get into trouble, you have to ask the expert next to you to save yourself.
2. Take another example (> = 1024)
package main import "fmt" func main() { s1 := make([]int, 1024) s2 := make([]int, 258) s1 = append(s1, s2...) fmt.Println(cap(s1), len(s1)) } The output is: 1696 1282 It can be seen from the results that if it is greater than or equal to 1024, it is not according to 1.25(1+1/4)So: f(x) = x + x/4 (x>=1024)It suddenly doesn't work. Why on earth is this? Do you want to explore it all? Next, analyze the source code.
3. Two functions
f(x) = 2x (x<1024) f(x) = x + x/4 (x >= 1024)
Confirm: function 1 holds and function 2 holds under some conditions, huh? Why is it true under some conditions? Take another example:
package main import "fmt" func main() { s1 := make([]int, 1024) s2 := make([]int, 1) s1 = append(s1, s2...) fmt.Println(cap(s1), len(s1)) } The output is: 1280 1025
We can take function 2 to calculate: f(1024) = 1024 + 1024/4 = 1024 + 256 = 1280. You can see that this can satisfy function 2. Next, analyze the above examples that do not satisfy function 2.
4. Source code analysis: reasons for not meeting the growth rate of 1.25
In the source code, we mainly look at the function of growslice.
func growslice(et *_type, old slice, cap int) slice { ... ... ... //Omit the previous unimportant code newcap := old.cap doublecap := newcap + newcap if cap > doublecap { newcap = cap } else { if old.len < 1024 { //Less than 1024 double newcap = doublecap } else { for 0 < newcap && newcap < cap { //If the capacity is larger than before, it will increase at the rate of 1.25 times newcap += newcap / 4 } } } ... //Omit some codes var lenmem, newlenmem, capmem uintptr switch { ... //ellipsis case et.size == sys.PtrSize: lenmem = uintptr(old.len) * sys.PtrSize newlenmem = uintptr(cap) * sys.PtrSize //Here, the {core function roundupsize} is aligned according to memory bytes capmem = roundupsize(uintptr(newcap) * sys.PtrSize) overflow = uintptr(newcap) > maxAlloc/sys.PtrSize newcap = int(capmem / sys.PtrSize) //1696 ...//Omit some codes } }
OK, follow the code flow first:
-
First, if our length is greater than or equal to 1024, we can calculate according to the above logic: f(1024)=1280. It is found that the space is not enough, so continue to expand the capacity, f(1280)=1280+1280/4=1600.
-
Then enter the switch case, where sys Ptrsize = 8, and our et.size is also 8 because it is stored in int. it just hits this case and enters.
-
Then calculate the new element capmem = roundupsize (uintptr (newcap) * sys Ptrsize), then capmem=roundupsize(1600*8)=roundupsize(12800).
-
In the roundupsize function, size = 12800<_ Maxsmallsize (32768), and size < = smallsizemax (1024) - 8 is not satisfied, so it finally enters the else code segment.
func roundupsize(size uintptr) uintptr { if size < _MaxSmallSize { if size <= smallSizeMax-8 { return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]]) } else { //Enter here return uintptr(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]]) } } if size+_PageSize < size { return size } return alignUp(size, _PageSize) } // divRoundUp returns ceil(n / a). func divRoundUp(n, a uintptr) uintptr { // a is generally a power of two. This will get inlined and // the compiler will optimize the division. return (n + a - 1) / a }
- Look at class_to_size and size_to_class128
const ( _MaxSmallSize = 32768 smallSizeDiv = 8 smallSizeMax = 1024 largeSizeDiv = 128 _NumSizeClasses = 67 _PageShift = 13 ) var class_to_size = [_NumSizeClasses]uint16{ 0, 8, 16, 24, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768} var size_to_class128 = [(_MaxSmallSize-smallSizeMax)/largeSizeDiv + 1]uint8{ 32, 33, 34, 35, 36, 37, 37, 38, 38, 39, 39, 40, 40, 40, 41, 41, 41, 42, 43, 43, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 48, 48, 48, 49, 49, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67}
-
The function divroundup (size smallsizemax, largesizediv) calculates: divRoundUp(12800-1024, 128) = divRoundUp(12800-1024, 128) = (12800-1024+128-1) / 128 = 11903/128=92
-
We are based on 92 in size_ to_ Find 57 in class128, and then in class_to_size is 13568 according to index 57.
-
Therefore, the roundupsize function returns 13568, and newcap = int(capmem / sys.PtrSize) = 13568/8 = 1696. So far, we have calculated that the final capacity of cap is 1696.
-
Note that my source code version of go is go1 sixteen point seven
5. Summary
After reading the source code, you feel comfortable. I believe you will be more confident. No matter what your colleagues ask you or the interviewer asks you, you can proudly analyze it from the perspective of the source code, and others can judge that you have read the source code and are full of thirst for knowledge!
If you think this article is very helpful to you, please forward, share, pay attention and like it. It's not easy to write, and I hope you can support it.
Gongzong No.: future
So that many coder s in the confused stage can find light from here, stack creation, contribute to the present and benefit the future
125 original content