github.com/teh-cmc/mmm@v0.0.0-20160717174312-f3d5d92d1c27/README.md (about)

     1  # mmm ![Status](https://img.shields.io/badge/status-stable-green.svg?style=plastic) [![Build Status](http://img.shields.io/travis/teh-cmc/mmm.svg?style=plastic)](https://travis-ci.org/teh-cmc/mmm) [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=plastic)](http://godoc.org/github.com/teh-cmc/mmm)
     2  
     3  Manual memory management for golang.
     4  
     5  Have a look at [FreeTree](https://github.com/teh-cmc/freetree) for a real-world example of how to use `mmm`.
     6  
     7  ## What you should definitely know..
     8  
     9  ## ..before using `mmm`
    10  
    11  Go doesn't provide any manual memory management primitives. [**For very good reasons**](https://golang.org/doc/faq#garbage_collection).
    12  This has been talked about numerous times on the [go-nuts mailing list](https://groups.google.com/forum/#!forum/golang-nuts), have a look over there for detailed discussions.
    13  
    14  To make it short: **unless you are absolutely certain that you have no better alternative and that you understand all of the tradeoffs involved, please do not use this library.**
    15  
    16  `mmm` is no black magic: it simply allocates memory segments outside of the GC-managed heap and provides a simple API (`Read()`, `Write()`, `Pointer()`) that abstracts away all of the evil stuff actually going on behind the scenes.
    17  
    18  **The performances of Go's garbage collector depend heavily on the number of pointers your software is using.**
    19  *No matter how much performance you gain by using `mmm`, you could have had the same gains had you redesigned your software to avoid the use of pointers entirely.*
    20  
    21  This is the raison d'ĂȘtre of `mmm`: in some cases, purposefully (re)designing your software to avoid the use of pointers actually leads to code that is overly complex, harder to reason about, and thus, harder to maintain. In such cases, `mmm` might allow you to completely eliminate the GC overhead issues in your software, while keeping your original design (with minimal changes to your implementation, of course).
    22  
    23  Note that `mmm` heavily relies on Go's implementation of interfaces.
    24  
    25  Finally, for the adventurous, you'll find most of the ugly stuff [here](https://github.com/teh-cmc/mmm/blob/master/mmm.go#L44-L108) and [there](https://github.com/teh-cmc/mmm/blob/master/bytes.go#L50-L93).
    26  
    27  UPDATE: this was discussed at length in [this HN thread](https://news.ycombinator.com/item?id=10649059).
    28  
    29  ## ..once you've decided to use `mmm`
    30  
    31  - Never point to data on the GC-managed heap using a pointer stored on an unmanaged heap.
    32  
    33  If the only references to your garbage-collectable data are stored in an unmanaged memory chunk, and thus non-existent to the eyes of the GC, your data will be automatically deallocated. As it should be.
    34  
    35  - `mmm` provides support for the following types: interfaces, arrays, structs, numerics/boolean (`bool`/`int`/`uint`/`float`/`complex` and their variants), `unsafe.Pointer`, and any possible combination of the above.
    36  
    37  Slices and `string` are thus not supported, use arrays and byte arrays instead.
    38  
    39  - `mmm` doesn't provide synchronization of reads and writes on a `MemChunk`.
    40  
    41  It's entirely up to you to decide how you want to manage thread-safety.
    42  
    43  ## Install
    44  
    45  ```bash
    46  go get -u github.com/teh-cmc/mmm
    47  ```
    48  
    49  ## Example
    50  
    51  Here's a simple example of usage (code [here](examples/simple.go)):
    52  
    53  ```Go
    54  package main
    55  
    56  /////
    57  // Simple example of usage
    58  //
    59  //	 go run examples/simple.go
    60  //
    61  /////
    62  
    63  import (
    64  	"fmt"
    65  	"log"
    66  	"unsafe"
    67  
    68  	"github.com/teh-cmc/mmm"
    69  )
    70  
    71  type Coordinate struct {
    72  	x, y int
    73  }
    74  
    75  func main() {
    76  	// create a new memory chunk that contains 3 Coordinate structures
    77  	mc, err := mmm.NewMemChunk(Coordinate{}, 3)
    78  	if err != nil {
    79  		log.Fatal(err)
    80  	}
    81  
    82  	// print 3
    83  	fmt.Println(mc.NbObjects())
    84  
    85  	// write {3,9} at index 0, then print {3,9}
    86  	fmt.Println(mc.Write(0, Coordinate{3, 9}))
    87  	// write {17,2} at index 1, then print {17,2}
    88  	fmt.Println(mc.Write(1, Coordinate{17, 2}))
    89  	// write {42,42} at index 2, then print {42,42}
    90  	fmt.Println(mc.Write(2, Coordinate{42, 42}))
    91  
    92  	// print {17,2}
    93  	fmt.Println(mc.Read(1))
    94  	// print {42,42}
    95  	fmt.Println(*((*Coordinate)(unsafe.Pointer(mc.Pointer(2)))))
    96  
    97  	// free memory chunk
    98  	if err := mc.Delete(); err != nil {
    99  		log.Fatal(err)
   100  	}
   101  }
   102  ```
   103  
   104  ## Demonstration
   105  
   106  Complete code for the following demonstration is available [here](experiment/experiment.go).
   107  
   108  All of the results shown below were computed using a DELL XPS 15-9530 (i7-4712HQ@2.30GHz).
   109  
   110  #### Case A: managed heap, 10 million pointers to int
   111  
   112  Let's see what happens when we store 10 millions pointers to integer on the managed heap:
   113  
   114  ```Go
   115  // build 10 million pointers to integer on the managed heap
   116  ints := make([]*int, 10*1e6)
   117  // init our pointers
   118  for i := range ints {
   119  	j := i
   120  	ints[i] = &j
   121  }
   122  
   123  for i := 0; i < 5; i++ {
   124  	// randomly print one of our integers to make sure it's all working
   125  	// as expected, and to prevent them from being optimized away
   126  	fmt.Printf("\tvalue @ index %d: %d\n", i*1e4, *(ints[i*1e4]))
   127  
   128  	// run GC
   129  	now := time.Now().UnixNano()
   130  	runtime.GC()
   131  	fmt.Printf("\tGC time (managed heap, 10 million pointers): %d us\n", (time.Now().UnixNano()-now)/1e3)
   132  }
   133  ```
   134  
   135  This prints:
   136  
   137  ```
   138  value @ index 0: 0
   139  GC time (managed heap, 10 million pointers): 329840 us
   140  value @ index 10000: 10000
   141  GC time (managed heap, 10 million pointers): 325375 us
   142  value @ index 20000: 20000
   143  GC time (managed heap, 10 million pointers): 323367 us
   144  value @ index 30000: 30000
   145  GC time (managed heap, 10 million pointers): 327905 us
   146  value @ index 40000: 40000
   147  GC time (managed heap, 10 million pointers): 326469 us
   148  ```
   149  
   150  That's an average ~326ms per GC call.
   151  Let's move to case B where we will start using `mmm`'s memory chunks.
   152  
   153  #### Case B: unmanaged heap, pointers generated on-the-fly
   154  
   155  `mmm` doesn't store any pointer, it doesn't need to.
   156  
   157  Since the data is stored on an unmanaged heap, it cannot be collected even if there's no reference to it. This allows `mmm` to generate pointers only when something's actually reading or writing to the data.
   158  
   159  In pratice, it looks like that:
   160  
   161  ```Go
   162  // build 10 million integers on an unmanaged heap
   163  intz, err := mmm.NewMemChunk(int(0), 10*1e6)
   164  if err != nil {
   165  	log.Fatal(err)
   166  }
   167  // init our integers
   168  for i := 0; i < int(intz.NbObjects()); i++ {
   169  	intz.Write(i, i)
   170  }
   171  
   172  for i := 0; i < 5; i++ {
   173  	// randomly print one of our integers to make sure it's all working
   174  	// as expected (pointer to data is generated on-the-fly)
   175  	fmt.Printf("\tvalue @ index %d: %d\n", i*1e4, intz.Read(i*1e4))
   176  
   177  	// run GC
   178  	now := time.Now().UnixNano()
   179  	runtime.GC()
   180  	fmt.Printf("\tGC time (unmanaged heap, pointers generated on-the-fly): %d us\n", (time.Now().UnixNano()-now)/1e3)
   181  ```
   182  
   183  This prints:
   184  
   185  ```
   186  value @ index 0: 0
   187  GC time (unmanaged heap, pointers generated on-the-fly): 999 us
   188  value @ index 10000: 10000
   189  GC time (unmanaged heap, pointers generated on-the-fly): 665 us
   190  value @ index 20000: 20000
   191  GC time (unmanaged heap, pointers generated on-the-fly): 827 us
   192  value @ index 30000: 30000
   193  GC time (unmanaged heap, pointers generated on-the-fly): 882 us
   194  value @ index 40000: 40000
   195  GC time (unmanaged heap, pointers generated on-the-fly): 1016 us
   196  ```
   197  
   198  That's an average ~0.9ms per GC call.
   199  
   200  We went from a ~326ms average to a ~0.9ms average; but the comparison isn't really fair now, is it? In case A we were storing every pointer, here we're simply not storing any.
   201  
   202  That leads us to case C, in which we build pointers to each and every integer that's in our unmanaged heap.
   203  
   204  #### Case C: unmanaged heap, storing all generated pointers
   205  
   206  What happens when we build and store 10 million pointers: one for each and every integer that's in our unmanaged memory chunk?
   207  
   208  ```Go
   209  // build 10 million unsafe pointers on the managed heap
   210  ptrs := make([]unsafe.Pointer, 10*1e6)
   211  // init those pointers so that they point to the unmanaged heap
   212  for i := range ptrs {
   213  	ptrs[i] = unsafe.Pointer(intz.Pointer(i))
   214  }
   215  
   216  for i := 0; i < 5; i++ {
   217  	// randomly print one of our integers to make sure it's all working
   218  	// as expected
   219  	fmt.Printf("\tvalue @ index %d: %d\n", i*1e4, *(*int)(ptrs[i*1e4]))
   220  
   221  	// run GC
   222  	now := time.Now().UnixNano()
   223  	runtime.GC()
   224  	fmt.Printf("\tGC time (unmanaged heap, all generated pointers stored): %d us\n", (time.Now().UnixNano()-now)/1e3)
   225  }
   226  ```
   227  
   228  This prints:
   229  
   230  ```
   231  value @ index 0: 0
   232  GC time (unmanaged heap, all generated pointers stored): 47196 us
   233  value @ index 10000: 10000
   234  GC time (unmanaged heap, all generated pointers stored): 47307 us
   235  value @ index 20000: 20000
   236  GC time (unmanaged heap, all generated pointers stored): 47485 us
   237  value @ index 30000: 30000
   238  GC time (unmanaged heap, all generated pointers stored): 47145 us
   239  value @ index 40000: 40000
   240  GC time (unmanaged heap, all generated pointers stored): 47221 us
   241  ```
   242  
   243  The results here are pretty interesting: on the one hand this is ~47 times slower than case B (in which we used `mmm`'s memory chunks but didn't actually store any pointer), but on the other hand this is still 6 times faster than case A (in which we used native pointers) because unsafe pointers require far less work from the GC.
   244  
   245  Six times faster is already quite the good deal, but why stop there? As we've already pointed out, `mmm` doesn't need to store references to its data... so.. don't.
   246  
   247  This is what case D is all about, in which we will convert those pointers into simple numeric references and store them as such.
   248  
   249  #### Case D: unmanaged heap, storing numeric references
   250  
   251  Instead of storing (unsafe) pointers, let's treat these pointers as what they really are: simple numeric references.
   252  
   253  ```Go
   254  // build 10 million numeric references on the managed heap
   255  refs := make([]uintptr, 10*1e6)
   256  // init those references so that they each contain one of the addresses in
   257  // our unmanaged heap
   258  for i := range refs {
   259  	refs[i] = uintptr(ptrs[i])
   260  }
   261  
   262  // get rid of those unsafe pointers we stored
   263  ptrs = nil
   264  
   265  for i := 0; i < 5; i++ {
   266  	// randomly print one of our integers to make sure it's all working
   267  	// as expected
   268  	fmt.Printf("\tvalue @ index %d: %d\n", i*1e4, *(*int)(unsafe.Pointer(refs[i*1e4])))
   269  
   270  	// run GC
   271  	now := time.Now().UnixNano()
   272  	runtime.GC()
   273  	fmt.Printf("\tGC time (unmanaged heap, all numeric references stored): %d us\n", (time.Now().UnixNano()-now)/1e3)
   274  }
   275  ```
   276  
   277  This prints:
   278  
   279  ```
   280  value @ index 0: 0
   281  GC time (unmanaged heap, all numeric references stored): 715 us
   282  value @ index 10000: 10000
   283  GC time (unmanaged heap, all numeric references stored): 783 us
   284  value @ index 20000: 20000
   285  GC time (unmanaged heap, all numeric references stored): 882 us
   286  value @ index 30000: 30000
   287  GC time (unmanaged heap, all numeric references stored): 711 us
   288  value @ index 40000: 40000
   289  GC time (unmanaged heap, all numeric references stored): 723 us
   290  ```
   291  
   292  We're basically back to the results of case B.
   293  As far as the GC is concerned, those pointers don't exist, which translates into sub-millisecond GC calls.
   294  
   295  Still, the memory they point to does exist, and is just one cast away from being read from and written to.
   296  
   297  We now have everything we need to build pointer-based software without any GC overhead, and without any design modification: this is basically how [FreeTree](https://github.com/teh-cmc/freetree) is implemented.
   298  
   299  ## License ![License](https://img.shields.io/badge/license-MIT-blue.svg?style=plastic)
   300  
   301  The MIT License (MIT) - see LICENSE for more details
   302  
   303  Copyright (c) 2015	Clement 'cmc' Rey	<cr.rey.clement@gmail.com>