├── LICENCE.txt ├── Makefile ├── nim-arc.md ├── nim-memory.md └── style.css /LICENCE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 narimiran 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | HTML := nim-memory.html nim-arc.html 3 | 4 | all: $(HTML) 5 | 6 | %.html: %.md style.css 7 | asciidoctor $< 8 | 9 | clean: 10 | rm -f $(HTML) 11 | 12 | pub: nim-memory.html 13 | scp nim-memory.html style.css ico@pruts.nl:/home/ico/websites/www.zevv.nl/nim-memory 14 | 15 | lazy-bastard: 16 | @while true; do sleep 0.5; make; done 17 | -------------------------------------------------------------------------------- /nim-arc.md: -------------------------------------------------------------------------------- 1 | 2 | = Nim ARC 3 | :toc: left 4 | :toclevels: 4 5 | :icons: font 6 | :doctype: book 7 | :stylesheet: style.css 8 | :nofooter: 9 | 10 | 11 | This is a work in progress. The goal of this document is to make a friendly 12 | explanation of ARC and its implications for the programmer. The target audience 13 | is mixed: 14 | 15 | - experience Nim users that have heard of ARC but did not yet try or switch because 16 | they are not quite sure what all the fuzz is about 17 | 18 | - not-yet expert Nim programmers coming from languages like Python or C++ and 19 | want to know how stuff is done in Nim 20 | 21 | The source of this document can be found at https://github.com/zevv/nim-memory. I'm 22 | very interested to know what is plain wrong, what I missed, what could be better, or 23 | what's great. Let me know on #nim or PR you changes at the source repo. 24 | 25 | 26 | == Introduction 27 | 28 | Blah blah ARC 29 | 30 | `--gc:arc` 31 | 32 | 33 | == Garbage collection 34 | 35 | part I of this document, "The Nim Memory Model", introduced the two pointer 36 | types available in Nim: 37 | 38 | - The `ref` type: This is a _managed pointer_, which points to memory that Nim 39 | will free for you when it is no longer needed. This is almost always the kind 40 | of pointer you want to use, unless you have good reasons not to. The only way 41 | to create a pointer of the `ref` type is by using `new()` to construct an 42 | object or a variable of a given type on the heap. 43 | 44 | - The `ptr` type: This is an _unmanaged pointer_, pointing to data that the 45 | user needs to allocate and deallocate manually, as if you were programming C 46 | using `alloc()` and `free()` 47 | 48 | Refs are originally managed in Nim by a _garbage collector_, or _GC_ in short. 49 | 50 | There are many types of garbage collectors, but basically they all operate in a 51 | similar way: every now and then your program is briefly interrupted, and the 52 | garbage collector takes control. It will then look at allocated blocks in 53 | memory and try to find which of these are no longer in use, e.g, there are 54 | no longer any pointers referencing that block. When a block is found that 55 | no one is pointing to, the GC knows it is safe to free it. 56 | 57 | GC's are cool because they release the programmer of the burden of keeping 58 | track of memory, typically leading to safer code (no use-after-free) and less 59 | memory leaks. 60 | 61 | There are some downsides to using a GC however: 62 | 63 | - Garbage collection is a process that has to be run periodically, and takes 64 | time to do so. This might interfere with your application - you probably do 65 | not want the GC to start doing its work for 20msec when you are drawing video 66 | frames at 60fps in your video game. 67 | 68 | - Garbage collectors are often non-deterministic: you as a programmer are not 69 | in control of exactly when a resource is cleaned up, you can not say "I want 70 | this memory to be freed _now_" 71 | 72 | - The code for doing the garbage collection needs to be part of your program. 73 | This is usually not a problem for desktop or server applications, but for 74 | small embedded targets this can be considerable overhead. 75 | 76 | - Garbage collectors do not always play nice with threads, making it impossible 77 | or hard to share managed data between different threads. 78 | 79 | 80 | == Introducing: ARC 81 | 82 | With Nim version 1.2, a new model for memory management was introduced, called 83 | _ARC_. With ARC, the compiler got much smarter about managing memory - so 84 | smart even, that garbage collection is no longer needed. 85 | 86 | Instead of periodically interrupting the program and scan for unreferenced 87 | memory, ARC builds on a number of other concepts: reference counting and 88 | destructors. 89 | 90 | === Reference counting 91 | 92 | With ARC, Nim will keep track of some additional data for each managed memory 93 | block. Part of this data is the _reference count_ (also called _refcount_, or 94 | _rc_) of the memory block. The refcount simply is a integer which keeps track 95 | of _how many pointers to the memory block_ exist. Nim will make sure this 96 | counter is increased every time a pointer is copied, and decreased every time a 97 | pointer is "lost". You can lose a pointer for example when it goes out of 98 | scope, or when it was stored in another object that is itself destroyed. 99 | 100 | When the last pointer to an object goes away, the reference counter will drop 101 | to 0. This means that no one is referencing this memory block anymore, so it 102 | Nim knows it can now free this block. It does this with the help of a 103 | _destructor_. 104 | 105 | 106 | === Destructors 107 | 108 | A _destructor_ is a proc that gets called by Nim when the reference counter of 109 | a ref drops to zero. Every type has its own destructor, the compiler will 110 | generate default destructors for you that do the right thing if you do not 111 | define one. 112 | 113 | Destructors are implemented by a proc called `=destroy` and look like this: 114 | 115 | ---- 116 | proc `=destroy`(x: var T) 117 | ---- 118 | 119 | While the default destructors generated by the compiler will take care of 120 | deallocating memory, custom destructors can be used for freeing or closing 121 | other kinds of resources like file descriptors, sockets, or handles provided by 122 | external libraries. 123 | 124 | 125 | == Move semantics 126 | 127 | The above two concepts - reference counting and destructors - are basically all 128 | that is needed for managing memory: keep track of how many pointers reference a 129 | block, and free the block when there are no more references. 130 | 131 | While this sounds pretty simple and straightforward, the compiler also does a 132 | number of smart things to also make it efficient, and to make sure it never 133 | does more work then strictly needed. 134 | 135 | This is where the _move semantics_ come in. Move semantics allow your code, 136 | under certain conditions, to turn expensive memory copies into cheap _moves_. 137 | 138 | 139 | === Last use 140 | 141 | TODO 142 | 143 | === Return value optimzation 144 | 145 | TODO 146 | 147 | === Sinks 148 | 149 | TODO 150 | 151 | === Lents 152 | 153 | TODO 154 | 155 | -------------------------------------------------------------------------------- /nim-memory.md: -------------------------------------------------------------------------------- 1 | 2 | = The Nim memory model 3 | :toc: left 4 | :toclevels: 4 5 | :icons: font 6 | :doctype: book 7 | :stylesheet: style.css 8 | :nofooter: 9 | 10 | == Introduction 11 | 12 | This is a small tutorial explaining how Nim stores data in memory. It will 13 | explain the essentials which every Nim programmer should know, but it will also 14 | dive deeper in the way Nim organizes its data structures like strings and seqs. 15 | 16 | For most practical purposes, Nim will take care of all memory management for 17 | your program without bothering you with the details. As long as you stick to 18 | the safe parts of the language, you will rarely have to work with memory 19 | addresses, or make explicit memory allocations. This changes however when you 20 | want your Nim code to interop with external C code or C libraries - in this 21 | case you might need to know where and how your Nim objects are stored in memory 22 | so you can pass this to C, or you will need to know how to access C-allocated 23 | data to make it accessible by Nim. 24 | 25 | The first parts of this document will be familiar to readers with a C or C++ 26 | background, as a lot of it is not unique to the Nim language. In contrast, some 27 | things might be new to programmers coming from dynamic languages like Python or 28 | Javascript, where memory handling is more abstracted away. 29 | 30 | NOTE: Most -- if not all -- of this document applies to the C and C++ code generator, 31 | since the Javascript backend does not use raw memory but relies on Javascript 32 | objects instead. 33 | 34 | 35 | == Computer memory basics 36 | 37 | This section gives a brief and abstract introduction (warning: gross 38 | simplifications ahead!) about computer memory, and what it looks like from the 39 | point of view of a CPU and a computer program. 40 | 41 | === Word size 42 | 43 | A computer's main memory (RAM) consists of a lot of memory locations, each of 44 | which has an unique address. Depending on the CPU architecture the size of each 45 | memory location (the "word size") typically varies between one byte (8 bit) to 46 | eight bytes (64 bit), while the CPU is usually also able to access large words 47 | as smaller chunks. Some architectures can read and write memory from arbitrary 48 | addresses, while others can only access memory at addresses which are a 49 | multiple of the word size. 50 | 51 | The CPU accesses memory using specific instructions that allow it to read or 52 | write data of a given word size from or to a given address. For example, it 53 | might store the value 0x12345 as a 32 bit number at address 0x100000. The low 54 | level assembly instruction for doing this might look something like this: 55 | 56 | mov [0x100000], 0x12345 57 | 58 | This is what the memory on address 0x100000 will look like after the above 59 | instruction completes, with each column representing a byte: 60 | 61 | 00 01 02 03 04 62 | +----+----+----+----+---- 63 | 0x100000 | 00 | 01 | 23 | 45 | .. 64 | +----+----+----+----+---- 65 | 66 | 67 | === Endianess 68 | 69 | To complicate things a bit more, the actual order of bytes within a word varies 70 | between CPU types - some CPUs put the most significant byte first, while others 71 | put the least significant byte first. This is called the _endianess_ of a CPU. 72 | 73 | - Most CPUs these days (Intel compatible, x86, amd64, most ARM families) are 74 | little endian. The integer 0x1234 is stored with the *least* significant byte 75 | first: 76 | 77 | 00 01 78 | +----+----+ 79 | | 34 | 12 | 80 | +----+----+ 81 | 82 | - Some other CPUs like Freescale or OpenRISC are big endian. The integer 0x1234 83 | is stored with the *most* significant byte first. Most network protocols 84 | serialize data in big endian order when sending it out on the network; this 85 | is why big endian is also know as _network endian_: 86 | 87 | 00 01 88 | +----+----+ 89 | | 12 | 34 | 90 | +----+----+ 91 | 92 | Most important of all: if you want to write portable code, do not ever 93 | make any assumptions about your machines endianess when writing binary data 94 | to disk or over the network and make sure to explicitly convert your data 95 | to the proper endianess. 96 | 97 | 98 | == Two ways to organize memory 99 | 100 | Traditionally, C programs use two common methods used for organizing objects in 101 | computer memory: the _stack_ and the _heap_. Both methods serve different 102 | purposes and have very different characteristics. Nim code is compiled to C or 103 | C++ code, so Nim naturally shares the memory model of these languages. 104 | 105 | 106 | === The stack 107 | 108 | A stack is a region of memory where data is always added and removed from one 109 | end. This is called "last-in-first-out" (LIFO). 110 | 111 | 112 | ==== Stack theory 113 | 114 | A good analogy for a stack is a stack of plates in a restaurant kitchen: new 115 | plates are taken out of the dishwasher and added on top; when plates are 116 | needed, they are also taken from the top. Plates are never inserted halfway or 117 | on the bottom, and plates are never taken from the middle or bottom of the 118 | stack. 119 | 120 | For historical reasons, computer stacks usually work top down: new data is 121 | added to and removed from the bottom of the stack, but this does not change the 122 | mechanism itself. 123 | 124 | +--------------+ <-- stack top 125 | | | 126 | | in use | 127 | | | 128 | | | 129 | +--------------+ <-- stack pointer 130 | | | 131 | | | | new data added 132 | : free : v on the bottom 133 | 134 | The administration for a stack is pretty simple: the program needs to keep 135 | track of only one address which points to the current stack bottom -- this is 136 | commonly know as the _stack pointer_. When data is added to the stack, it is 137 | copied in place and the stack pointer is decreased. When data is removed from 138 | the stack, it is copied out and the stack pointer is again increased. 139 | 140 | ==== Stacks in practice 141 | 142 | In Nim, C and most other compiled languages, the stack is used for two 143 | different purposes: 144 | 145 | - first it is used as a place to store temporary local variables. These 146 | variables only exist in a function as long as the function is active (i.e. it 147 | has not returned). 148 | 149 | - the compiler also uses the stack for a different kind of bookkeeping: every 150 | time a function is called, the address of the next instruction after the 151 | `call` instruction is placed on the stack -- this is the _return address_. 152 | When the function returns, it finds that address on the stack, and jumps to 153 | it. 154 | 155 | The combination data of the above two mechanisms make up a _stack frame_: this is 156 | a section of the stack which holds the return address of the current active 157 | function, together with all its local variables. 158 | 159 | During program execution, this is what the stack will look like if your program 160 | is nested two functions deep: 161 | 162 | +----------------+ <-- stack top 163 | | return address | 164 | | variable | <-- stack frame #1 165 | | variable | 166 | | ... | 167 | +----------------+ 168 | | return address | 169 | | variable | <-- stack frame #2 170 | | ... | 171 | +----------------+ <-- stack pointer 172 | | free | 173 | : : 174 | 175 | Using the stack for both data and return addresses is a pretty neat trick and 176 | has the nice side effect of offering automatic storage allocation and cleanup 177 | for data in a program. 178 | 179 | Stacks also work nicely with threads: each thread simply has its own stack, 180 | storing its own local variables and holding is own stack frames. 181 | 182 | Now you know where Nim gets the information from when it generates a _stack 183 | trace_ when it hits a run time error or exception: It will find the address of 184 | the innermost active function on the stack, and print its name. Then it goes 185 | looking further up the stack for the next level active function, all the way to 186 | the top. 187 | 188 | 189 | === The heap 190 | 191 | Next to the stack, the heap is the other place to store data in a computer 192 | program. While the stack is typically used to hold local variables, the heap 193 | can be used for more dynamic storage. 194 | 195 | ==== Heap theory 196 | 197 | A heap is a region of memory which is a bit like a warehouse. The memory region 198 | is called the _arena_: 199 | 200 | : : ^ heap can grow at the top 201 | | | | 202 | | | 203 | | free! | <--- The heap arena 204 | | | 205 | | | 206 | +--------------+ 207 | 208 | When a program wants to store data, it will first calculate how much storage it 209 | will need. It will then go to the warehouse clerk (the memory allocator) and 210 | request a place to store the data. The clerk has a ledger where it keeps track 211 | of all allocations in the warehouse, and it will find a free spot that is large 212 | enough to fit the data. It will then make an entry in the ledger that the area 213 | at that address and size is now taken, and it returns the address to the 214 | program. The program can now store and retrieve its data from this area in 215 | memory at will. 216 | 217 | : : 218 | | free | 219 | | | 220 | +--------------+ 221 | | allocated | <--- allocation address 222 | +--------------+ 223 | 224 | The above process can be repeated, allocating other blocks on the heap, some of 225 | different sizes: 226 | 227 | : : 228 | | free | 229 | +--------------+ 230 | | | 231 | | allocated #3 | 232 | | | 233 | +--------------+ 234 | | allocated #2 | 235 | +--------------+ 236 | | allocated #1 | 237 | +--------------+ 238 | 239 | When the data block is no longer used, the program will tell the memory 240 | allocator the address of the block. The allocator looks up the address in the 241 | ledger, and removes the entry. This block is now free for future use. This 242 | is what the above picture looks like when block #2 is released: 243 | 244 | : : 245 | | free | 246 | +--------------+ 247 | | | 248 | | allocated #3 | 249 | | | 250 | +--------------+ 251 | | free | <-- There's a hole in the heap! 252 | +--------------+ 253 | | allocated #1 | 254 | +--------------+ 255 | 256 | As you can see, the freeing of block #2 now leaves a hole in the heap, which 257 | might lead to problems in the future. Consider the next allocation request: 258 | 259 | - If the size of the next allocation is smaller then the size of the hole, the 260 | allocator might reuse the free space in the hole; but since the new request 261 | is smaller, a new smaller hole will be left after the new block 262 | 263 | - If the size of the next allocation is bigger then the size of the hole, the 264 | allocator has to find a bigger free spot somewhere, leaving the hole open. 265 | 266 | The only way to effectively reuse the hole is if the next allocation is of the 267 | exact same size of the hole. 268 | 269 | Heavy use of a heap with a lot of different sized objects might lead to a 270 | phenomenon called _fragmentation_. This means that the allocator is not able to 271 | effectively use 100% of the arena size to fulfil allocation requests, 272 | effectively wasting a part of the available memory. 273 | 274 | 275 | ==== The heap in practice 276 | 277 | In Nim, all your data is stored on the stack, unless you explicitly request it 278 | to go on the heap: the `new()` proc is typically used allocate memory on the 279 | heap for a new object: 280 | 281 | ---- 282 | type Thing = object 283 | a: int 284 | 285 | var t = new Thing 286 | ---- 287 | 288 | The above snippet will allocate memory on the heap to store an object of type 289 | `Thing` The _address_ of the newly allocated memory block is returned by `new`, 290 | which is now of type `ref Thing`. A `ref` is a special kind of pointer which is 291 | generally managed by Nim for you. More on this in the section 292 | <> 293 | 294 | 295 | == Memory organization in Nim 296 | 297 | As long as you stick to the _safe_ parts of the language, Nim will take care of 298 | managing memory allocations for you. It will make sure your data is stored at 299 | the appropriate place, and freed when you no longer need it. However, if the 300 | need arises, Nim offers you full control as well, allowing you to choose 301 | exactly how and where to store your data. 302 | 303 | Nim offers some handy functions to allow you to inspect how your data is 304 | organized in memory. These will be used in the examples in the sections below 305 | to inspect how and where Nim stores your data: 306 | 307 | `addr(x)`:: This proc returns the address of variable `x`. For a variable of 308 | type `T`, its address will have type `ptr T` 309 | 310 | `unsafeAddr(x)`:: This proc is basically the same as `addr()`, but it can be 311 | used even if Nim thinks it would not be safe to get the address 312 | of an object -- more on this later. 313 | 314 | `sizeof(x)`:: Returns the size of variable `x` in bytes 315 | 316 | `typeof(x)`:: Returns the string representation of the type of variable `x` 317 | 318 | 319 | The result of `addr(x)` and `unsafeAddr(x)` on an object of type `T` has a 320 | result of type `ptr T`. Nim does not know how to print this by default, so we 321 | will make use of `repr()` to nicely format the type for us: 322 | 323 | ---- 324 | var a: int 325 | echo a.addr.repr 326 | # ptr 0x56274ece0c60 --> 0 327 | ---- 328 | 329 | === Using pointers 330 | 331 | Basically, a pointer is nothing more then a special type of variable which 332 | holds a memory address -- it points to something else in memory. As briefly 333 | mentioned above, there are two types of pointers in Nim: 334 | 335 | - `ptr T` for _untraced references_, aka _pointers_ 336 | - `ref T` for _traced references_, for memory that is managed by Nim 337 | 338 | The `ptr T` pointer type is considered _unsafe_. Pointers point to manually 339 | allocated objects or to objects somewhere else in memory, and it is your task 340 | as a programmer to make sure your pointers always point to valid data. 341 | 342 | When you want to access the data in the memory that the pointer points to -- 343 | the contents of the address with that numerical index -- you need to 344 | _dereference_ (or in short, _deref_) the pointer. 345 | 346 | In Nim you can use an empty array subscript `[]` to do this, analogous to using 347 | the `*` prefix operator in C. The snippet below shows how to create an alias to 348 | an int and change its value. 349 | 350 | ---- 351 | var a = 20 <1> 352 | var p = a.addr <2> 353 | p[] = 30 <3> 354 | echo a # --> 30 355 | ---- 356 | 357 | <1> Here a normal variable `a` is declared and initialized with the value 20 358 | <2> `p` is a pointer of type `ptr int`, pointing to the address of int `a` 359 | <3> The `[]` operator is used to dereference the pointer p. As `p` is a pointer 360 | of type `ptr int` which points to the memory address where `a` is stored, 361 | dereferenced variable `p[]` is again of type int. The variables `a` and `p[]` 362 | now refer to the exact same memory location, so assigning a value to `p[]` 363 | will also change the value of `a` 364 | 365 | For object or tuple access, Nim will perform automatic dereferencing for you: 366 | the normal `.` access operator can be used just as with a normal object. 367 | 368 | 369 | === The stack: local variables 370 | 371 | Local variables (also called _automatic_ variables) are the default method by 372 | which Nim stores your variables and data. 373 | 374 | Nim will reserve space for your variable on the stack, and it will stay there 375 | as long as it is in scope. In practice, this means that the variable will exist 376 | as long as the function in which it is declared does not return. As soon as the 377 | function returns the stack _unwinds_ and the variables are gone. 378 | 379 | Here are some examples of variables which will be stored on the stack: 380 | 381 | ---- 382 | type Thing = object 383 | a, b: int 384 | 385 | var a: int 386 | var b = 14 387 | var c: Thing 388 | var d = Thing(a: 5, b: 18) 389 | ---- 390 | 391 | 392 | === Traced references and the garbage collector 393 | 394 | In the previous sections we saw that pointers in Nim as returned by `addr()` 395 | are of the type `ptr T`, but we saw that `new` returns a `ref T`. 396 | 397 | While both `ptr` and `ref` are pointers to data, there is an important 398 | difference between the two: 399 | 400 | - a `ptr T` is just a pointer -- a variable holding an address which points to 401 | data living elsewhere. You as the programmer are responsible for making sure 402 | this pointer is referencing to valid memory when you use it. 403 | 404 | - a `ref T` is a _traced reference_: this also is an address pointing to 405 | something else, but Nim will keep track of data it points to for you, and 406 | make sure this will be freed when it is no longer needed. 407 | 408 | 409 | The only way to acquire a `ref T` pointer is to allocate the memory using the 410 | `new()` proc. Nim will reserve the memory for you, and also will start keeping 411 | track of where in the code this data is referenced. When the Nim runtime sees 412 | that the data is no longer referred to, it knows it is safe to discard it and 413 | it will automatically free it for you. This is known as _garbage collection_, 414 | or _GC_ for short. 415 | 416 | 417 | == How Nim stores data in memory 418 | 419 | This section will show some experiments where we investigate how Nim stores 420 | various data types in memory. 421 | 422 | 423 | === Primitive types 424 | 425 | A _primitive_ or _scalar_ type is a "single" value like an `int`, a `bool` or a 426 | `float`. Scalars are usually kept on the stack, unless they are part of a 427 | container type like an object. 428 | 429 | Let's see how Nim manages memory for primitive types for us. The snippet below 430 | first creates a variable `a` of type `int` and prints this variable and its 431 | size. Then it will create a second variable `b` of type `ptr int` which is 432 | called a _pointer_, and now holds the _address_ of variable `a`. 433 | 434 | ---- 435 | var a = 9 436 | echo a.repr 437 | echo sizeof(a) 438 | 439 | var b = a.addr 440 | echo b.repr 441 | echo sizeof(b) 442 | ---- 443 | 444 | On my machine I might get the following output: 445 | 446 | 9 <1> 447 | 8 <2> 448 | ptr 0x300000 --> 9 <3> 449 | 8 <4> 450 | 451 | <1> No surprise here: this is the value of variable `a` 452 | 453 | <2> This is the size of the variable, in bytes. 8 bytes makes 64 bits, which 454 | happens to be the default size for `int` types in Nim on my machine. So far 455 | so good. 456 | 457 | <3> This line shows a representation of variable `b`. `b` holds the address 458 | of variable `a`, which happens to live at address `0x300000`. In Nim an 459 | address is known as a _ref_ or a _pointer_. 460 | 461 | <4> `b` itself is also a variable, which is not of the type `ptr int`. On 462 | my machine memory addresses also have a size of 64 bit, which equals 8 463 | bytes. 464 | 465 | 466 | The above can be represented by the following diagram: 467 | 468 | +---------------------------------------+ 469 | 0x??????: | 00 | 00 | 00 | 00 | 30 | 00 | 00 | 00 | b: ptr int = 470 | +---------------------------------------+ 0x300000 471 | | 472 | | 473 | v 474 | +---------------------------------------+ 475 | 0x300000: | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 09 | a: int = 9 476 | +---------------------------------------+ 477 | 478 | 479 | 480 | === Compound types: objects 481 | 482 | Let's put a more complicated object on the stack and see what happens: 483 | 484 | ---- 485 | type Thing = object <1> 486 | a: uint32 487 | b: uint8 488 | c: uint16 489 | 490 | var t: Thing <2> 491 | 492 | echo "size t.a ", t.a.sizeof 493 | echo "size t.b ", t.b.sizeof 494 | echo "size t.c ", t.c.sizeof 495 | echo "size t ", t.sizeof <3> 496 | 497 | echo "addr t.a ", t.a.addr.repr 498 | echo "addr t.b ", t.b.addr.repr 499 | echo "addr t.c ", t.c.addr.repr 500 | echo "addr t ", t.addr.repr <4> 501 | ---- 502 | 503 | <1> The definition of our object type `Thing`, which holds integers of various 504 | sizes 505 | 506 | <2> Create a variable `t` of type `Thing` 507 | 508 | <3> Print the size of `t` and all its fields 509 | 510 | <4> Print the address of `t` and all its fields 511 | 512 | In Nim, an object is just a way of grouping variables into a handy container, 513 | making sure they are placed next to each other in memory the same way as C 514 | would do. 515 | 516 | Here is the output on my machine: 517 | 518 | ---- 519 | size t.a 4 <1> 520 | size t.b 1 521 | size t.c 2 522 | size t 8 <2> 523 | addr t ptr 0x300000 --> [a = 0, b = 0, c = 0] <3> 524 | addr t.a ptr 0x300000 --> 0 <4> 525 | addr t.b ptr 0x300004 --> 0 526 | addr t.c ptr 0x300006 --> 0 <5> 527 | ---- 528 | 529 | Lets go through the output: 530 | 531 | <1> First get the size of fields of the object. `a` was declared as an `uint32`, which 532 | is 4 bytes big, `b` is an `uint8` which is 1 byte, and `c` is an `uint16` which is 2 bytes 533 | big. check! 534 | 535 | <2> Here is a bit of a surprise: print the size of the container object `t`, which seems 536 | to be 8 bytes big. But that does not add up, as the contents of the object is 537 | only 4+1+2 = 7 bytes! More on this below. 538 | 539 | <3> Let's get the address of the object `t`: on my machine it was placed on 540 | address `0x300000` on the stack. 541 | 542 | <4> Here we can see that the field `t.a` lies at exactly the same place in memory as the object 543 | itself: `0x300000`. The address of `t.b` is `0x300004`, which is 4 544 | bytes after `t.a`. That makes sense, since `t.a` is four bytes big. 545 | 546 | <5> The address of `t.c` is `0x300006`, which is 2 (!) bytes after `t.b`, but `t.b` is only 547 | one byte big? 548 | 549 | So, let's draw a little picture of what we have learned from the above: 550 | 551 | ---- 552 | 00 01 02 03 04 05 06 07 553 | +-------------------+----+----+---------+ 554 | 0x300000: | a | b | ?? | c | 555 | +-------------------+----+----+---------+ 556 | ^ ^ ^ 557 | | | | 558 | address of addr addr 559 | t and t.a of t.b of t.c 560 | ---- 561 | 562 | So this is what our `Thing` object looks like in memory. So what is up with 563 | the hole marked `??` at offset 5, and why is the total size not 7 but 8 bytes? 564 | 565 | This is caused by something the compiler does which is called _alignment_, to 566 | make it easier for the CPU to access the data in memory. By making sure objects 567 | are nicely aligned in memory at a multiple of their size (or a multiple of the 568 | architecture's word size), the CPU can access the memory more efficiently. This 569 | usually results in faster code, at the price of wasting some memory. 570 | 571 | (You can hint the Nim compiler not to do alignment but to place the fields of 572 | an object back-to-back in memory using the `{.packed.}` pragma -- refer to the 573 | link:https://nim-lang.github.io/Nim/manual.html#[Nim language manual] for details) 574 | 575 | 576 | 577 | === Strings and seqs 578 | 579 | The above sections described how Nim manages relativily simple static objects 580 | in memory. This section will go into the implementation of more complex and 581 | dynamic data types which are part of the Nim language: strings and seqs. 582 | 583 | 584 | In Nim, the `string` and `seq` data types are closely related. These are 585 | basically a long row of objects of the same type (chars for a strings, any 586 | other type for seqs). What is different for these types is that they can 587 | dynamically grow or shrink in memory. 588 | 589 | ==== Let's talk about seqs 590 | 591 | Lets create a `seq` and do some experiments with it: 592 | 593 | ---- 594 | var a = @[ 30, 40, 50 ] 595 | ---- 596 | 597 | Let's ask Nim what the type of variable `a` is: 598 | 599 | ---- 600 | var a = @[ 30, 40, 50 ] 601 | echo typeof(a) # -> seq[int] 602 | ---- 603 | 604 | We see the type is `seq[int]`, which is what was expected. 605 | 606 | Now, lets add some code to see how Nim stores the data: 607 | 608 | ---- 609 | var a = @[ 0x30, 0x40, 0x50 ] 610 | echo a.repr 611 | echo a.len 612 | echo a[0].addr.repr 613 | echo a[1].addr.repr 614 | ---- 615 | 616 | And here is the output on my machine: 617 | 618 | ---- 619 | ptr 0x300000 --> 0x900000@[0x30, 0x40, 0x50] <1> 620 | 3 <2> 621 | ptr 0x900010 --> 0x30 <3> 622 | ptr 0x900018 --> 0x40 <4> 623 | ---- 624 | 625 | What can be deduced from this? 626 | 627 | <1> The variable `a` itself is placed on the stack, which happens to be at 628 | address `0x300000` on my machine. A is some kind of pointer that points to 629 | address `0x900000` which is on the heap! And this is where the actual seq 630 | lives. 631 | 632 | <2> This seq contains 3 elements, just as it should be. 633 | 634 | <3> `a[0]` is the first element of the seq. Its value is `0x30`, and i is stored 635 | at address `0x900010`, which is right after the seq itself 636 | 637 | <4> The second item in the seq is `a[1]`, which is placed at address `0x900018`. 638 | This makes perfect sense, as the size of an `int` is 8 bytes, and all 639 | ints in the seq are placed back-to-back in memory. 640 | 641 | Let's make a little drawing again. We know `a` is a pointer living on the 642 | stack, which refers to something on the heap with a size of 16 bytes, followed 643 | by the elements of our seq: 644 | 645 | stack 646 | +---------------------------------------+ 647 | 0x300000 | 00 | 00 | 00 | 00 | 90 | 00 | 00 | 00 | a: seq[int] 648 | +---------------------------------------+ 649 | | 650 | heap v 651 | +---------------------------------------+ 652 | 0x900000 | ?? | ?? | ?? | ?? | ?? | ?? | ?? | ?? | 653 | +---------------------------------------+ 654 | 0x900008 | ?? | ?? | ?? | ?? | ?? | ?? | ?? | ?? | 655 | +---------------------------------------+ 656 | 0x900010 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 30 | a[0] = 0x30 657 | +---------------------------------------+ 658 | 0x900018 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 40 | a[1] = 0x40 659 | +---------------------------------------+ 660 | 0x900020 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 50 | a[2] = 0x50 661 | +---------------------------------------+ 662 | 663 | This almost explains all of the seq, except for the 16 unknown bytes at the 664 | start of the block: this area is where Nim stores its internal information 665 | about the seq. 666 | 667 | This data is normally hidden from the user, but you can simply find the 668 | implementation of this header in the Nim system library, and it looks like 669 | this: 670 | 671 | ---- 672 | type TGenericSeq = object 673 | len: int <1> 674 | reserved: int <2> 675 | ---- 676 | 677 | <1> The `len` field is used by Nim to store the current length of the seq - 678 | that is how many elements are in it. 679 | 680 | <2> The `reserved` field is used to keep track of the actual size of the storage 681 | inside the seq -- for performance reasons Nim might reserve a larger space 682 | ahead of time to avoid resizing the seq when new items need to be added. 683 | 684 | Let's do a little experiment to inspect what is in the our seq header (unsafe 685 | code ahead!): 686 | 687 | ---- 688 | type TGenericSeq = object <1> 689 | len, reserved: int 690 | 691 | var a = @[10, 20, 30] 692 | var b = cast[ptr TGenericSeq](a) <2> 693 | echo b.repr 694 | ---- 695 | 696 | <1> The original `TGenericSeq` object is not exported from the system lib, so 697 | here the same object is defined 698 | 699 | <2> Here the variable `a` is casted to the `TGenericSeq` type. 700 | 701 | When we print the result with `echo b.repr`, the output looks like this: 702 | 703 | ---- 704 | ptr 0x900000 --> [len = 3, reserved = 3] 705 | ---- 706 | 707 | There we have it: Our seq has a size of 3, and has reserved space for 3 708 | elements in total. The next section will explain what happens when more fields 709 | are added to a seq. 710 | 711 | 712 | ==== Growing a seq 713 | 714 | The snippet below starts with the same seq, and then adds new elements. Each 715 | iteration it will print the seq header: 716 | 717 | ---- 718 | type TGenericSeq = object 719 | len, reserved: int 720 | 721 | var a = @[10, 20, 30] 722 | 723 | for i in 0..4: 724 | echo cast[ptr TGenericSeq](a).repr 725 | a.add i 726 | 727 | ---- 728 | 729 | Here is the output, see if you can spot the interesting bits: 730 | 731 | ---- 732 | ptr 0x900000 --> [len = 3, reserved = 3] <1> 733 | ptr 0x900070 --> [len = 4, reserved = 6] <2> 734 | ptr 0x900070 --> [len = 5, reserved = 6] <3> 735 | ptr 0x900070 --> [len = 6, reserved = 6] 736 | ptr 0x9000d0 --> [len = 7, reserved = 12] <4> 737 | ---- 738 | 739 | <1> This is the original 3 element seq: it is stored on the heap at 740 | address `0x900000`, has a length of 3 elements, and reserved storage for 741 | 3 elements as well 742 | 743 | <2> One element was added, and a few notable things have happened: 744 | 745 | - the `len` field is increased to 4, which makes perfect sense because the 746 | seq now holds 4 elements 747 | 748 | - the `reserved` field increased from 3 to 6. This is because Nim 749 | doubles the storage size when doing a new allocation - this is more 750 | efficient when repeatedly adding data without having to resize the 751 | allocation for every `add()` 752 | 753 | - note that the address of the seq itself also changed! The reason for 754 | this is that the inital memory allocation for the seq data on the heap 755 | was not large enough to fit the new element, so Nim had to find a larger 756 | chunk of memory to hold the data. It is likely that the allocator already 757 | reserved the area directly behind the seq to something else, so it was 758 | not possible to grow this area. Instead, a new allocation somewhere else 759 | on the heap was made, the old data of the seq was copied from the old 760 | location to the new location, and the new element was added. 761 | 762 | <3> When adding the 4th element above, Nim resized the seq storage to hold 6 763 | elements -- this allows adding two more elements without having to make 764 | a larger allocation. There are now 6 elements placed in the seq, with a total 765 | reserved size for 6 elements. 766 | 767 | <4> And here the same happens once more: The block is not large enough to fit 768 | the 7th item, so the whole seq is moved to another place, and the allocation is 769 | scaled up to hold 12 elements. 770 | 771 | 772 | == Conclusion 773 | 774 | This document only scratched the surface of how Nim's handles memory, there is 775 | a lot more to tell. Here are some subjects I think also deserve a chapter one 776 | day, but which I didn't come to write yet: 777 | 778 | - A more elaborate discussion on garbage collection, and the available GC 779 | flavours in Nim. 780 | 781 | - Using Nim without a garbage collector / embedded systems with tight memory. 782 | 783 | - The new Nim runtime! 784 | 785 | - Memory usage in closures/iterators/async -- locals do not always go on the stack. 786 | 787 | - FFI: Discussion and examples of passing data between C and Nim. 788 | 789 | This is a document in progress, any comments are much appreciated. The source 790 | can be found on github at https://github.com/zevv/nim-memory 791 | 792 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | table { 2 | border-collapse: separate; 3 | border-spacing: 0.2em 0.6em; 4 | } 5 | 6 | *, *:before, *:after { 7 | box-sizing: border-box; 8 | -moz-box-sizing: border-box; 9 | -webkit-box-sizing: border-box; 10 | } 11 | 12 | #header, #content, #footnotes, #footer { 13 | width: 100%; 14 | margin-left: auto; 15 | margin-right: auto; 16 | max-width: 45em; 17 | position: relative; 18 | padding-left: 1em; 19 | padding-right: 1em 20 | } 21 | 22 | body { 23 | background: #fcfaf7; 24 | color: #222727; 25 | margin-top: -1.8em; 26 | font-family: "Fira Sans", "Open Sans", "Noto Sans", "DejaVu Sans", sans-serif; 27 | line-height: 1.6; 28 | tab-size: 4; 29 | -moz-osx-font-smoothing: grayscale; 30 | -webkit-font-smoothing: antialiased; 31 | } 32 | 33 | h1, h2, h3, #toctitle, .sidebarblock>.content>.title, h4, h5, h6 { 34 | font-weight: 400; 35 | font-style: normal; 36 | color: #cb4b16; 37 | margin-top: 2.5em; 38 | } 39 | 40 | h1 { 41 | margin-top: 4.0em; 42 | } 43 | 44 | h3 { 45 | font-style: italic; 46 | } 47 | 48 | code { 49 | font-family: "Fira Mono", "Hack", "Noto Sans Mono", "DejaVu Sans Mono", monospace; 50 | font-size: 0.9em; 51 | font-weight: 400; 52 | } 53 | 54 | a { 55 | color: #268bd2; 56 | text-decoration-color: #7daed1; 57 | } 58 | 59 | a:hover, a:focus { 60 | color: #cb4b16; 61 | text-decoration-color: #cb4b16; 62 | } 63 | 64 | p { 65 | font-size: 1.125em; 66 | text-rendering: optimizeLegibility 67 | } 68 | 69 | 70 | *:not(pre)>code { 71 | padding: 0.0em 0.35ex; 72 | background-color: #eeebe2; 73 | border-radius: 6px; 74 | -webkit-border-radius: 6px; 75 | word-wrap: break-word; 76 | } 77 | 78 | pre.rouge { 79 | padding: 1.0em; 80 | } 81 | 82 | pre, pre>code { 83 | line-height: 1.30; 84 | font-family: "Fira Mono", "Hack", "Noto Sans Mono", "DejaVu Sans Mono", monospace; 85 | font-size: 0.95em; 86 | border-radius: 12px; 87 | -webkit-border-radius: 12px; 88 | } 89 | 90 | td { 91 | vertical-align: top; 92 | } 93 | 94 | .admonitionblock td.icon [class^="fa icon-"] { 95 | font-size: 2.5em; 96 | text-shadow: 1px 1px 2px rgba(0, 0, 0, .5); 97 | } 98 | 99 | .admonitionblock>table td.icon { 100 | text-align: center; 101 | width: 80px; 102 | } 103 | 104 | .admonitionblock td.icon .icon-note:before { 105 | content: "\f05a"; 106 | color: #586e75; 107 | } 108 | 109 | .admonitionblock td.icon .icon-warning:before { 110 | content: "\f071"; 111 | color: #cb4b16; 112 | } 113 | 114 | .literalblock pre, .listingblock pre:not(.highlight), .listingblock pre[class="highlight"], .listingblock pre[class^="highlight "], .listingblock pre.CodeRay, .listingblock pre.prettyprint { 115 | background: #eeebe2; 116 | padding: 10px; 117 | } 118 | 119 | .listingblock>.title { 120 | line-height: 1.0; 121 | font-size: 0.95em; 122 | color: #cb4b16; 123 | font-weight: 400; 124 | font-style: italic; 125 | margin-bottom: -0.5em; 126 | } 127 | 128 | .conum[data-value] { 129 | display: inline-block; 130 | color: #fcfaf7; 131 | background-color: #586e75; 132 | border-radius: 5px; 133 | -webkit-border-radius: 5px; 134 | text-align: center; 135 | font-size: 0.8em; 136 | width: 3.0em; 137 | line-height: 1.5em; 138 | margin-right: 1em; 139 | font-style: normal; 140 | font-weight: bold; 141 | } 142 | 143 | hr { 144 | border: solid #586e75; 145 | border-width: 1px 0 0; 146 | margin-top: 5em; 147 | } 148 | 149 | em, i { 150 | font-style: italic; 151 | line-height: inherit; 152 | } 153 | 154 | strong, b { 155 | font-weight: bold; 156 | line-height: inherit; 157 | } 158 | 159 | .conum[data-value]+b { 160 | display: none; 161 | } 162 | 163 | .conum[data-value]:after { 164 | content: attr(data-value) 165 | } 166 | 167 | pre .conum[data-value] { 168 | position: relative; 169 | top: -.125em; 170 | } 171 | 172 | b.conum * { 173 | color: inherit!important; 174 | } 175 | 176 | ol li { 177 | padding-left: 1em; 178 | } 179 | 180 | @media only screen and (min-width:850px) { 181 | body.toc2 { 182 | padding-left: 15em; 183 | margin-top: -5em; 184 | } 185 | 186 | #toc.toc2 { 187 | background: #fcfaf7; 188 | position: fixed; 189 | width: 15em; 190 | left: 0; 191 | top: 0; 192 | border-right: 2px solid #efefed; 193 | padding: 0.5em; 194 | height: 100%; 195 | overflow: auto; 196 | } 197 | } 198 | 199 | #toc.toc2 #toctitle { 200 | margin-top: 0; 201 | margin-bottom: .8rem; 202 | font-size: 1.25em; 203 | } 204 | #toc.toc2>ul { 205 | font-size: 0.875em; 206 | padding-left: 0.5em; 207 | } 208 | 209 | #toc.toc2 ul ul { 210 | padding-left: 0.5em; 211 | } 212 | 213 | #toc.toc2 a { 214 | text-decoration: none; 215 | } 216 | 217 | #toc.toc2 li { 218 | margin-left: 0.5em; 219 | margin-top: 0em; 220 | margin-bottom: 0em; 221 | } 222 | --------------------------------------------------------------------------------