@h12.io

Value vs Pointer Receivers

19 June 2020

Should I use value receivers or pointer receivers?

Value receivers have some benefits include immutability, concurrent safety and clean logic (not always, often true). But to what extend can I use value receivers without an issue or performance penalty?

In the Go FAQ, there are 3 rules:

  1. most important, does the method need to modify the receiver? If it does, the receiver must be a pointer
  2. if the receiver is large, a big struct for instance, it will be much cheaper to use a pointer receiver
  3. if some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used

Let’s look at rule 1. In many cases, an object needs to be modified by a method after its initialization, but it doesn’t mean the modification has to be done in place, an alternative and often a better way is to get a modified copy of the object, which is usually called a Fluent Interface. Basically it means a method with a value receiver and a return value of the same type. It’s just pure (no side effect) functional programming style under another name.

Then rule 2 tells us to choose based on the struct size, but how big is a big struct? We know that time.Time is 3-words large with value receivers, but how about 4-words, 5-words or bigger structs? To answer this question, I did some benchmarks.

The benchmarks are based on the idea that a type with value receivers would need to implement fluent interface, so it should compare the performance between two fluent methods with value and pointer receivers:

func (s S) ByValue() S {
    // ...
	return s
}

func (s *S) ByPointer() *S {
    // ...
	return s
}

It turned out inlining is critical to the overhead of value copying.

When inlining is disabled (gcflags=-l), for a struct of 1 word, value receiver has the same performance as the pointer receiver, but for structs larger than 2 words, value receivers begin to show more and more overhead.

Without Inline

However, when inlining is enabled (the default option), value receivers have almost the same performance as pointer receivers for struct size from 1 up to 9 words! Overhead of value copying starts to rise for structs of 10 words or more.

With Inline

These results were tested on a Macbook Pro with go1.14.3.

Conclusion

From the benchmark results, value receivers should be preferred if the methods are inlined and the struct size is equal to or smaller than 9 words (1 word == 64 bit), but this might not be the end of the story, the more complex a method is, the less likely it is inlined, but also the less proportion the copying overhead to the whole method logic. The overhead of copying from a value receiver might not be significant to the overall running time, so at the end of the day, the choice depends on the frequency of the method calls, the business logic and the performance requirements. When in doubt, benchmark it!

Go vs Python

27 September 2019

Slices

Go slice and Python slice have very similar syntax, but Python slice is a shallow copy of part of the original list, while Go slice is just a new range within the same underlying array of the original slice.

Let’s try:

a = [1, 2, 3]
b = a[:2]
b[0] = 9
print(a)
print(b)

# output:
# [1, 2, 3]
# [9, 2]

See a[0] remains the same.

package main

import (
    "fmt"
)

func main() {
	a := []int{1, 2, 3}
	b := a[:2]
	b[0] = 9
	fmt.Println(a)
	fmt.Println(b)

	# output:
	# [9 2 3]
	# [9 2]
}

See a[0] changes because slice a and b shares the same underlying array.

Learning Frontend

15 February 2019

JavaScript Surprises to a Go Developer

12 February 2019

The scope of var is wrong

Never use var to declare variables, use let instead.

REF

== is conversion and comparison

What you really need is ===, which is similar to comparing two interface{}.

=== is shallow

{a:1}==={a:1} is false, while Go struct with string are compared by contents (but not for slice).

Also this affects map key comparison. So object key is not so useful in JS as struct key in Go. The equality test is always on the references rather than the contents.

REF

const works for objects, but the contents are modifiable

REF

Single and double quotes are the same

REF

Back quotes are like text/template

Escapes also works in back quotes and more template related features.

REF

Switch cases fall through by default

Access undeclared object property returns undefined

Trying to access undeclared variables throws an exception but accessing undeclared object properties return a value called undefined.

Use 'property' in obj to check if a property is declared or not.

Only float64 is supported

Object is like map[string]interface{}

Array is like map[int]interface{}

Map is like map[interface{}]interface{}

for...in traverses properties recursively

for...of is like for...range in Go

object.constructor.prototype

    console.log({}.constructor === {}['__proto__'].constructor);
    console.log({}.constructor.prototype === {}['__proto__']);

Promise can be chained and composed

It is quite impressive how JS do concurrency differently and efficiently.

Promise.then(onFulfilled) where onFufilled can return another Promise.

Promise.all([func1(), func2(), func3()])
.then(([result1, result2, result3]) => { /* use result1, result2 and result3 */ });

Computed property name

{
    [prop]: 42,
}

There are two types of generator method syntax

{
    *gen() {

    }
}
{
    gen: function*() {

    }
}

Run: Graceful Goroutine Orchestration

14 December 2018

GoDoc

Overview

While Go provides goroutines, channels and selects as first-class citizens to support concurrent programming, it is not trivial to combine these elements to address important concerns of goroutine orchestration, e.g. error handling, panic recovery, goroutine leak prevention, goroutine reuse, goroutine throttle and logging.

The package provides a mini-framework to address those cross-cutting concerns.

Quick start

go get -u h12.io/run

Here is an example illustrating the usage of the goroutine pool and the group. The task is described in the “Google Search 2.0” page from this slide.

// the goroutine pool
pool := run.NewGoroutinePool(
	run.Max(8),                // the pool contains maximum 8 goroutines
	run.IdleTime(time.Minute), // a goroutine will stay in idle for maximum 1 minute before exiting
)

// the group
// the goroutine pool might have longer lifespan than the group
group := run.NewGroup(
	context.Background(), // a context that can cancel the whole group
	run.Pool(pool),       // the goroutine pool used by the group
	run.Recover(true),    // recover from panic and returns the PanicError
	run.Log(func(info *run.LogInfo) { // a log function for all starts/stops
		log.Print(info)
	}),
)

searches := []*GoogleSearch{
	{Search: Web, Query: "golang"},
	{Search: Image, Query: "golang"},
	{Search: Video, Query: "golang"},
}
for _, search := range searches {
	// start searching in parallel
	if err := group.Go(search); err != nil {
		log.Fatal(err)
	}
}

// wait for all searches stop
if err := group.Wait(); err != nil {
	log.Fatal(err)
}

for _, search := range searches {
	fmt.Println(search.Result)
}

See the full example here.

Design

The package is built around the concept of a runner.

type Runner interface {
	Run(context.Context) error
}

A correct implementation of a runner should satisify the following conditions:

  • blocks when the work is on going
  • returns when all work is done, an error occurred or context is cancelled

With goroutine pool and group in the package, the user does not need to use the go statement explicitly, but only needs to implement their objects satisfying the Runner interface.

A Group is useful when multiple concurrent sub-tasks needed to be combined as a single task (the task failed when one of them failed, every sub-task should be cancelled when the task is cancelled).

A Pool is useful when there are many short-lived gorotuines.

Group can be built upon pool, not vice versa.

How to Start Godoc on Mac

5 May 2018

Fish:

godoc -http=:6060 &; disown (pidof godoc)

Bash:

godoc -http=:6060 &; disown `pidof godoc`

And pidof can be installed with Homebrew.

Tmux Cheatsheet on Mac

20 April 2018

Create a new session with iTerm2 integration

tmux -CC new -s [session-name]

Attach to a session with iTerm2 integration

tmux -CC attach -t [session-name]