Using MAKE during slice creation


(Roman Roma) #1

I am struggling with Array and Slices, and how you have to define the size of the array and/or the underline array of the slice; however, I still have reading to complete on the ‘A Tour of Go’ website.

But what is the benefit of using ‘make’ when defining/creating a slice?

Source
https://tour.golang.org/moretypes/13

I thought slices were already dynamic undefined sized references to the array, which is the same size. So if I would use the ‘append(array, “add-value”)’ the array would get larger. Does that mean the array is getting destroyed and recreated, but larger?

I am coming from a Perl and (some) Python scripting background, so I think some array memory management/allocation skills were not gained due to using those languages that make array/list assignment trivial.

Thank you
RR


(Johan Dahl) #2

Hi. A slice is “just” a pointer to an array with a length (the slice’s capacity) and the number of used items is the slice’s length. If you append items to the slice will its length increase and then you exceed the length of the underlying array will a new one be created (usually double the size) and all elements be copied to the new one. This is now the new underlying array and its length is the new capacity. Using make is useful for two purposes.

  1. You want to make a empty slice of a certain size
  2. You know or have a hunch on how big the slice will be and can avoid unnecessary allocations if the underlying array is big enough from the beginning.

Hope I answered your question. Been driving all day so I’m a bit tired.


(Jay Ts) #3

Hi Roman,

This is long, but I hope it helps you:

https://blog.golang.org/slices

Basically, slices in Go may appear similar to slices in Perl or Python, but in Go, they are a fundamental data type, not just a way of taking a piece out of an array. Programmers who are new to Go may not appreciate that immediately and try to program mainly with arrays, as they do in some other language.

Arrays exist in Go and are the underlying means of storing elements of slices, but it’s slices, not arrays, that are usually used in Go. Slices are a reference type, which makes them far more efficient to work with. Instead of passing around or copying a multi-element array, slices allow you to work on arrays using a data structure that is like this:

// pseudo-code!
type slice struct {
    array *slice_type
    int len
    int cap
}

The first part is a pointer to an array of elements. That’s the underlying array. And in addition, there is the length of the slice (how many elements are in the slice) and the capacity (how many elements can be used before it needs to be reallocated).

So learn how slices work, and train yourself to think in slices rather than arrays, using arrays only when it’s appropriate.

Arrays are still useful for working on data sets of fixed sizes. For example, an array containing the names of the months. It would be limited to a size of 12, and the array never has to grow.

I hope that helps!


(Roman Roma) #4

Hi Johan,
When you say “pointer” is this like C pointers to memory location? I do appreciate your help… struggling with this concept for odd reason.


(Roman Roma) #5

HI Jay,
Thank you for the details. I am struggling with this concept for some odd reason; however, I really like GO, and very excited to port my code over to it.

Give me some time to read the link you provided, and I am sure I will have more questions. I feel I am sort of getting there; however, not sure (HAHAHA).


(Jay Ts) #6

Hi Roman,

If your background includes only high-level interpreted languages, you may need to become more familiar with how actual hardware functions. In the hardware, memory is just a sequence of storage locations. In compiled languages like C or Go, arrays are just a humanized way to refer to a section of memory. Memory holds binary data, and the memory locations are accessed by addresses, which is what the C and Go notions of pointers is about.

Consider this photo of a Micron MT4C1024 1-Megabit memory chip, from about 1989:


Full resolution: https://upload.wikimedia.org/wikipedia/commons/9/9b/MT4C1024-HD.jpg
(It’s cool because you can actually see each bit.)

You can see that in hardware, memory is a huge physical array of bits. (Or course, huge here means by numbers not physical size.)

The way the chip works is that when a binary value is placed on its address pins, it returns the bit at that address. The address is split up (or you could say “decoded”) to select a particular row and column out of the entire array of bits, and at that row and column is one bit of storage. This particular example is bit-addressable. The chip returns just one bit, but by using 8 of them in parallel, you can store 8-bit bytes. Or 64 in parallel can store a 64-bit word (like an int64, float64, or pointer). (It’s also possible to store 8 or more parallel bits in the same chip, but this particular product was not designed that way. The reason you see rectangular segments is for electrical reasons, not to organize the memory into bytes. It’s to distribute power, ground, and signals to smaller areas, which results in faster operation.)

At a higher level in the design of the hardware, memory is handled as a consecutive block of bytes, with each byte addressed by consecutive integer numbers called addresses.

To efficiently store an array of equal-sized data elements in hardware memory, it’s a simple matter of using consecutive areas of physical memory. For example, if you had an array of 64-bit integers (which are 8 bytes each), then store it with the integers next to each other, 8 bytes apart. No space is wasted, and the computer’s hardware can access the next element in the array by adding 8 to the address of the previous one. Or to get the Nth element, take the address of the first element, and add to it N*8 to get the address of the Nth element. These calculations are simple and are done by the CPU while it’s executing its low-level machine code instructions. So having arrays implemented like this in a language is “natural” for the (artificial) hardware.

Resizing or copying arrays is not really efficient simply because it takes many machine code instructions, and therefore many clock cycles, to copy all of the bytes. So that’s one good reason why slices are a good way to process array-like data in Go.

If you continue to have difficulty with slices, I suggest you take a look at William Kennedy’s book Go in Action (https://www.manning.com/books/go-in-action), or his video course Ultimate Go (2nd ed.) (https://www.oreilly.com/library/view/ultimate-go-programming/9780135261651/) . In the video course, he is very good at explaining that Go is all about working directly with hardware, which he calls “mechanical sympathy”.


(Roman Roma) #7

Jay,
Thank you so much for the detailed post. As you pointed out - my back ground is only in: Perl, TCL and some Python. However, I always felt these were great for what I do - Network Engineer, yet I always wanted to learn a true compiled programming language. I always knew there were “things” going on in the background, yet at the time I did not really care… I just needed my scripts to work, so I can do my job.

But recently, I have taken a greater interest in programming, and did look at C. However, I sort of chickened out of C once I found GO. In addition, I saw the same man who had created C also did GO. Also, I need to be able to compile my code these days for my target machines. Perl Packer and PyInstaller are great tools; however, I still feel like I am missing something within the programmer skill set.

I have the time now to learn and study… so I thought it was time to use a REAL programming language to learn and to port my apps over to a ecosystem that has native compiled code for target systems.

Thank you again for the details… I will read later tonight, and have more questions tomorrow.

RR


(Jay Ts) #8

Hi Roman,

You are making an excellent choice by deciding to learn Go. Actually, Go was designed partly by Rob Pike and Ken Thompson. Thompson created the first version of Unix and the B language, which led to Dennis Ritchie (who is no longer with us) creating C. Rob Pike joined Bell Labs later, but worked with Ken on the Plan 9 operating system and the design of the UTF-8 Unicode encoding.

At some point, you might be interested in watching some of Rob Pike’s talks on Go, which can be found on YouTube. He explains things I write about below, but in more detail.

Go is designed not just for now, but for the future. A problem with less efficient interpreted languages is that computer CPU cores are not getting much faster anymore. Moore’s Law is basically over, and we are not getting a doubling of speed every couple of years. You can make transistors and wires only so small, where they are no longer enough atoms wide to carry current, and that’s about where things are now. (Approximately 7 nm. Aside from the topic, when I was designing VLSI circuits in the early 1980s, our feature size was about 4 µm, which seemed very small at the time!)

For earlier generations, in which Perl and Python were new, it seemed like processor speed was not a real limitation because if your program ran slowly, you could just wait a couple of years until you upgraded your computer. But now, to get things to run faster, we need efficient code and more CPU cores.

Go is designed primarily to target the actual hardware, rather than some programming paradigm like OOP or functional programming, and has really good built-in support for concurrent programming. For that and other reasons, it’s now becoming a popular language for network programming of web services and cloud computing applications. So if you want to upgrade your skills in network programming and be future-ready, learning Go is a very smart decision.


(Roman Roma) #9

Hi Jay,
I purchased the ‘Go in Action’ book, and started working through the chapter for “array and slices”; however, still struggling with some concepts, but just need to push through.

What also attracted me to Go is the fact that it is NOT OOP. I do not get OOP concepts, and much rather stick with structures and organize my code along those lines.

Thank you again for the help and suggestions. Do you mind if I post some code for us to talk about?


(Jay Ts) #10

Sure, no problem. Just observe the methods of posting code discussed here:
How to Post Code in this Forum


(Roman Roma) #11

This is just simple code I put together to play with slices.

package main
import(
        "fmt"
)

func printSlice(s []int) {
        fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
func main(){
        var slice []int
        //slice := []int{}
        slice = append(slice,10)
        slice = append(slice,20)
        printSlice(slice)
        
       // remove items in array/slice
        slice = slice[:0]
        printSlice(slice)
        
        // null out the cap of the slice
        slice = nil
        printSlice(slice)
        
        slice = append(slice,45)
        slice = append(slice,77)
        printSlice(slice)
}

This is what I have so far to test and play with slices. Question, I noticed when I used slice[:0] it removed the items in the array; however, it did not shrink the array size. I dug around and found slice = nil, which enabled to array to shrink.

Did I do this right and is this best practice?


(Lucas B Bayer) #12

Hi Roman,

The low level of go is still somewhat elusive to me, but let me try to explain what happened.

With the sentence slice[:0] you are not modifying or destroying the underlying array of the slice, you are just giving the array the values of the new slice. In this case it is an empty slice so the len of the slice goes down to zero because the slice you are assigning is an empty slice.

The difference with the slice = nil sentence is that you are giving the slice what its called its zero value (more information on zero values can be found here: https://golang.org/ref/spec#Program_initialization_and_execution). Since the zero value does not initialize an underlying array this sentence destroys the array which was previously created.

I took the liberty of modifying your code and adding to each line the memory address of the corresponding array.
https://play.golang.org/p/f9rkHDmKAw-
As you can see the slice[:0] sentence does not destroy the underlying array since the address to the memory was not changed by it. The values that were contained in it were the only modifications.

On the contrary the slice = nil sentence destroys the underlying array which can be seen by the fact that there is now no memory address for this array.
I added one final line where I create an array but do not initialize it to show you that the memory address in this case is the same as your previous example.

To answer your question manual manipulation of an array is not really the best practice in go unless you are extremely sure of what you intend to do with it. The safe option is to create slices without indication of capacity and length and let the program determine that at runtime.

Regards.


(Johan Dahl) #13

Nice example :slight_smile:


(Inanc Gumus) #14

“the slice = nil sentence destroys the underlying array which can be seen by the fact that there is now no memory address for this array.”

It doesn’t destroy the memory nor the array. It just detaches the slice from that array.

Also, you’ve declared the slice2 variable that still refers to the array of the slice variable. So, the array of the first slice never gets destroyed.


(Roman Roma) #15

Hi Lucas:
Thank you so much for adding the memory location to the script. This is great, because this is why I am looking into programming languages that need to deal with memory management.

I do remember memory location concepts from C classes, which I really enjoyed and this is great! Thank you.