Skip to content

Commit

Permalink
all: update pdf format
Browse files Browse the repository at this point in the history
  • Loading branch information
changkun committed May 1, 2022
1 parent b427110 commit d1e66ec
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 107 deletions.
Binary file modified content/bench-time.pdf
Binary file not shown.
Binary file modified content/cgo-handle.pdf
Binary file not shown.
Binary file modified content/generic-option.pdf
Binary file not shown.
Binary file modified content/pointer-params.pdf
Binary file not shown.
12 changes: 8 additions & 4 deletions content/posts/bench-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ func BenchmarkAtomic(b *testing.B) {
On my target machine (CPU Quad-core Intel Core i7-7700 (-MT-MCP-) speed/max 1341/4200 MHz Kernel 5.4.0-42-generic x86_64), running this snippet with the following command:

```
go test -run=none -bench=Atomic -benchtime=1000x -count=20 | tee b.txt && benchstat b.txt
go test -run=none -bench=Atomic -benchtime=1000x -count=20 | \
tee b.txt && benchstat b.txt
```

The result shows:
Expand Down Expand Up @@ -102,7 +103,8 @@ for j := 0; j < k; j++ {
Thus with higher `k`, the target code grows more costly. With similar command:

```
go test -run=none -bench=Atomic -benchtime=1000x -count=20 | tee b.txt && benchstat b.txt
go test -run=none -bench=Atomic -benchtime=1000x -count=20 | \
tee b.txt && benchstat b.txt
```

```
Expand Down Expand Up @@ -146,7 +148,8 @@ func BenchmarkWithTimer(b *testing.B) {
```

```
go test -v -run=none -bench=WithTimer -benchtime=100000x -count=5 -cpuprofile cpu.pprof
go test -v -run=none -bench=WithTimer -benchtime=100000x -count=5 \
-cpuprofile cpu.pprof
```

Sadly, the graph shows a chunk of useless information where most of the costs shows as `runtime.ReadMemStats`:
Expand Down Expand Up @@ -279,7 +282,8 @@ int main() {
for (int j = 0; j < 10; j++) {
std::chrono::nanoseconds since(0);
for (int i = 0; i < n; i++) {
since -= std::chrono::steady_clock::now() - std::chrono::steady_clock::now();
since -= std::chrono::steady_clock::now() -
std::chrono::steady_clock::now();
}
std::cout << "avg since: " << since.count() / n << "ns \n";
}
Expand Down
74 changes: 39 additions & 35 deletions content/posts/cgo-handle.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ int myfunc(void* go_value);
*/
import "C"

// This funcCallback tries to avoid a runtime panic error when directly
// pass it to Cgo because it violates the pointer passing rules:
// This funcCallback tries to avoid a runtime panic error when
// directly pass it to Cgo because it violates the pointer passing
// rules:
//
// panic: runtime error: cgo argument has Go pointer to Go pointer
var (
Expand All @@ -109,12 +110,12 @@ func main() {
In above, the `gocallback` pointer on the Go side is passed through the C function `myfunc`. On the C side, there will be a call using `go_func_callback` that being called on the C, via passing the struct `gocallback` as a parameter:

```c
// myfunc will trigger a callback, c_func, whenever it is needed and pass
// the gocallback data though the void* parameter.
// myfunc will trigger a callback, c_func, whenever it is needed
// and pass the gocallback data though the void* parameter.
void c_func(void *data) {
void *gocallback = userData;
// the gocallback is received as a pointer, we pass it as an argument
// to the go_func_callback
// the gocallback is received as a pointer, we pass it as
// an argument to the go_func_callback
go_func_callback(gocallback);
}
```
Expand Down Expand Up @@ -158,23 +159,23 @@ type Handle uintptr

// NewHandle returns a handle for a given value.
//
// The handle is valid until the program calls Delete on it. The handle
// uses resources, and this package assumes that C code may hold on to
// the handle, so a program must explicitly call Delete when the handle
// is no longer needed.
// The handle is valid until the program calls Delete on it.
// The handle uses resources, and this package assumes that C
// code may hold on to the handle, so a program must explicitly
// call Delete when the handle is no longer needed.
//
// The intended use is to pass the returned handle to C code, which
// passes it back to Go, which calls Value.
// The intended use is to pass the returned handle to C code,
// which passes it back to Go, which calls Value.
func NewHandle(v interface{}) Handle

// Value returns the associated Go value for a valid handle.
//
// The method panics if the handle is invalid.
func (h Handle) Value() interface{}

// Delete invalidates a handle. This method should only be called once
// the program no longer needs to pass the handle to C and the C code
// no longer has a copy of the handle value.
// Delete invalidates a handle. This method should only be
// called once the program no longer needs to pass the handle
// to C and the C code no longer has a copy of the handle value.
//
// The method panics if the handle is invalid.
func (h Handle) Delete()
Expand Down Expand Up @@ -245,7 +246,8 @@ func main() {
ch := make(chan struct{})
handle := cgo.NewHandle(ch)
go func() {
C.myfunc(C.uintptr_t(handle)) // myfunc will call goCallback when needed.
// myfunc will call goCallback when needed.
C.myfunc(C.uintptr_t(handle))
...
}()

Expand Down Expand Up @@ -312,10 +314,10 @@ func NewHandle(v interface{}) Handle {

k = rv.Pointer()
default:
// Wrap and turn a value parameter into a pointer. This enables
// us to always store the passing object as a pointer, and helps
// to identify which of whose are initially pointers or values
// when Value is called.
// Wrap and turn a value parameter into a pointer.
// This enables us to always store the passing object
// as a pointer, and helps to identify which of whose
// are initially pointers or values when Value is called.
v = &wrap{v}
k = reflect.ValueOf(v).Pointer()
}
Expand All @@ -327,10 +329,10 @@ func NewHandle(v interface{}) Handle {
Note that the implementation above treats the values differently: For `reflect.Ptr`, `reflect.UnsafePointer`, `reflect.Slice`, `reflect.Map`, `reflect.Chan`, `reflect.Func` types, they are already pointers escaped to the heap, we can safely get the address from them. For the other kinds, we need to turn them from a value to a pointer and also make sure they will always escape to the heap. That is the part:

```go
// Wrap and turn a value parameter into a pointer. This enables
// us to always store the passing object as a pointer, and helps
// to identify which of whose are initially pointers or values
// when Value is called.
// Wrap and turn a value parameter into a pointer. This
// enables us to always store the passing object as a
// pointer, and helps to identify which of whose are
// initially pointers or values when Value is called.
v = &wrap{v}
k = reflect.ValueOf(v).Pointer()
```
Expand All @@ -344,11 +346,12 @@ The easy case is, of course, if the address is not on the global map, then we do
func NewHandle(v interface{}) Handle {
...

// v was escaped to the heap because of reflection. As Go do not have
// a moving GC (and possibly lasts true for a long future), it is
// safe to use its pointer address as the key of the global map at
// this moment. The implementation must be reconsidered if moving GC
// is introduced internally in the runtime.
// v was escaped to the heap because of reflection. As Go do
// not have a moving GC (and possibly lasts true for a long
// future), it is safe to use its pointer address as the key
// of the global map at this moment. The implementation must
// be reconsidered if moving GC is introduced internally in
// the runtime.
actual, loaded := m.LoadOrStore(k, v)
if !loaded {
return Handle(k)
Expand All @@ -374,12 +377,13 @@ func NewHandle(v interface{}) Handle {
return Handle(k)
}

// If the loaded pointer is inconsistent with the new pointer,
// it means the address has been used for different objects
// because of GC and its address is reused for a new Go object,
// meaning that the Handle does not call Delete explicitly when
// the old Go value is not needed. Consider this as a misuse of
// a handle, do panic.
// If the loaded pointer is inconsistent with the new
// pointer, it means the address has been used for
// different objects because of GC and its address is
// reused for a new Go object, meaning that the Handle
// does not call Delete explicitly when the old Go value
// is not needed. Consider this as a misuse of a handle,
// do panic.
panic("cgo: misuse of a Handle")
default:
panic("cgo: Handle implementation has an internal bug")
Expand Down
46 changes: 26 additions & 20 deletions content/posts/generic-option.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ func NewA(opts ...Option) *A {
For example, the following four different usages both work:

```go
fmt.Printf("%#v\n", NewA()) // &main.A{v1:0, v2:0}
fmt.Printf("%#v\n", NewA(V1(42))) // &main.A{v1:42, v2:0}
fmt.Printf("%#v\n", NewA(V2(42))) // &main.A{v1:0, v2:0}
fmt.Printf("%#v\n", NewA(V1(42), V2(42))) // &main.A{v1:42, v2:0}
fmt.Printf("%#v\n", NewA()) // &A{v1:0, v2:0}
fmt.Printf("%#v\n", NewA(V1(42))) // &A{v1:42, v2:0}
fmt.Printf("%#v\n", NewA(V2(42))) // &A{v1:0, v2:0}
fmt.Printf("%#v\n", NewA(V1(42), V2(42))) // &A{v1:42, v2:0}
```

This is also super easy to deprecate an option, because we can simply let
Expand Down Expand Up @@ -189,12 +189,12 @@ func NewB(opts ...OptionB) *B {
In this way, whenever we need create a new `A` or `B`, we could:

```go
fmt.Printf("%#v\n", NewA()) // &main.A{v1:0}
fmt.Printf("%#v\n", NewA(V1ForA(42))) // &main.A{v1:42}
fmt.Printf("%#v\n", NewB()) // &main.B{v1:0, v2:0}
fmt.Printf("%#v\n", NewB(V1ForB(42))) // &main.B{v1:42, v2:0}
fmt.Printf("%#v\n", NewB(V2ForB(42))) // &main.B{v1:0, v2:42}
fmt.Printf("%#v\n", NewB(V1ForB(42), V2ForB(42))) // &main.B{v1:42, v2:42}
fmt.Printf("%#v\n", NewA()) // &A{v1:0}
fmt.Printf("%#v\n", NewA(V1ForA(42))) // &A{v1:42}
fmt.Printf("%#v\n", NewB()) // &B{v1:0, v2:0}
fmt.Printf("%#v\n", NewB(V1ForB(42))) // &B{v1:42, v2:0}
fmt.Printf("%#v\n", NewB(V2ForB(42))) // &B{v1:0, v2:42}
fmt.Printf("%#v\n", NewB(V1ForB(42), V2ForB(42))) // &B{v1:42, v2:42}
```

Although the above workaround is possible, but the actual naming and usage
Expand Down Expand Up @@ -283,12 +283,12 @@ func NewB(opts ...Option) *B {
Without further changes, one can use `V1` both for `A` and `B`, which is a quite simplification from the previous use already:

```go
fmt.Printf("%#v\n", NewA()) // &main.A{v1:0}
fmt.Printf("%#v\n", NewA(V1(42))) // &main.A{v1:42}
fmt.Printf("%#v\n", NewB()) // &main.B{v1:0, v2:0}
fmt.Printf("%#v\n", NewB(V1(42))) // &main.B{v1:42, v2:0}
fmt.Printf("%#v\n", NewB(V2(42))) // &main.B{v1:0, v2:42}
fmt.Printf("%#v\n", NewB(V1(42), V2(42))) // &main.B{v1:42, v2:42}
fmt.Printf("%#v\n", NewA()) // &A{v1:0}
fmt.Printf("%#v\n", NewA(V1(42))) // &A{v1:42}
fmt.Printf("%#v\n", NewB()) // &B{v1:0, v2:0}
fmt.Printf("%#v\n", NewB(V1(42))) // &B{v1:42, v2:0}
fmt.Printf("%#v\n", NewB(V2(42))) // &B{v1:0, v2:42}
fmt.Printf("%#v\n", NewB(V1(42), V2(42))) // &B{v1:42, v2:42}
```

However, not everything goes as expected. There is a heavy cost for this type of functional options pattern: safety.
Expand Down Expand Up @@ -400,10 +400,16 @@ fmt.Printf("%#v\n", NewB(V1[B](42), V2[B](42))) // &main.B{v1:42, v2:42}
With this design, the user of these APIs is safe because it is guaranteed by the compiler at compile-time, to disallow its misuse by the following errors:

```go
_ = NewA(V2[B](42)) // ERROR: B does not implement A
_ = NewA(V2[A](42)) // ERROR: A does not implement B
_ = NewB(V1[A](42), V2[B](42)) // ERROR: type Option[B] of V2[B](42) does not match inferred type Option[A] for Option[T]
_ = NewB(V1[B](42), V2[A](42)) // ERROR: type Option[A] of V2[A](42) does not match inferred type Option[B] for Option[T]
// ERROR: B does not implement A
_ = NewA(V2[B](42))
// ERROR: A does not implement B
_ = NewA(V2[A](42))
// ERROR: type Option[B] of V2[B](42) does not match
// inferred type Option[A] for Option[T]
_ = NewB(V1[A](42), V2[B](42))
// ERROR: type Option[A] of V2[A](42) does not match
// inferred type Option[B] for Option[T]
_ = NewB(V1[B](42), V2[A](42))
```

## Conclusion
Expand Down
23 changes: 14 additions & 9 deletions content/posts/pointer-params.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ func BenchmarkVec(b *testing.B) {
And run as follows:

```sh
$ perflock -governor 80% go test -v -run=none -bench=. -count=10 | tee new.txt
$ perflock -governor 80% go test -v -run=none -bench=. -count=10 | \
tee new.txt
$ benchstat new.txt
```

Expand Down Expand Up @@ -124,7 +125,8 @@ func (v *vec) addp(u *vec) *vec {
Then run the benchmark and compare the perf with the previous one:

```sh
$ perflock -governor 80% go test -v -run=none -bench=. -count=10 | tee old.txt
$ perflock -governor 80% go test -v -run=none -bench=. -count=10 | \
tee old.txt
$ benchstat old.txt new.txt
name old time/op new time/op delta
Vec/addv-16 4.99ns ± 1% 0.25ns ± 2% -95.05% (p=0.000 n=9+10)
Expand Down Expand Up @@ -176,8 +178,8 @@ The dumped assumbly code is as follows:
```
"".vec.addv STEXT nosplit size=89 args=0x60 locals=0x0 funcid=0x0
0x0000 00000 (vec.go:7) TEXT "".vec.addv(SB), NOSPLIT|ABIInternal, $0-96
0x0000 00000 (vec.go:7) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (vec.go:7) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (vec.go:7) FUNCDATA $0, gclocals·...(SB)
0x0000 00000 (vec.go:7) FUNCDATA $1, gclocals·...(SB)
0x0000 00000 (vec.go:8) MOVSD "".u+40(SP), X0
0x0006 00006 (vec.go:8) MOVSD "".v+8(SP), X1
0x000c 00012 (vec.go:8) ADDSD X1, X0
Expand All @@ -197,8 +199,8 @@ The dumped assumbly code is as follows:
0x0058 00088 (vec.go:8) RET
"".(*vec).addp STEXT nosplit size=73 args=0x18 locals=0x0 funcid=0x0
0x0000 00000 (vec.go:11) TEXT "".(*vec).addp(SB), NOSPLIT|ABIInternal, $0-24
0x0000 00000 (vec.go:11) FUNCDATA $0, gclocals·522734ad228da40e2256ba19cf2bc72c(SB)
0x0000 00000 (vec.go:11) FUNCDATA $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)
0x0000 00000 (vec.go:11) FUNCDATA $0, gclocals·...(SB)
0x0000 00000 (vec.go:11) FUNCDATA $1, gclocals·...(SB)
0x0000 00000 (vec.go:12) MOVQ "".u+16(SP), AX
0x0005 00005 (vec.go:12) MOVSD (AX), X0
0x0009 00009 (vec.go:12) MOVQ "".v+8(SP), CX
Expand Down Expand Up @@ -329,7 +331,8 @@ func main() {
Name: fmt.Sprintf("s%d", i),
Properties: strings.Join(ps, "\n"),
Addv: strings.Join(adv, "\n"),
Addp: strings.Join(adpl, ",") + " = " + strings.Join(adpr, ","),
Addp: strings.Join(adpl, ",") + " = " +
strings.Join(adpr, ","),
})
if err != nil {
panic(err)
Expand Down Expand Up @@ -363,7 +366,8 @@ v2 := s%d{%s}`, i, numstr1, i, numstr2),
if err != nil {
panic(err)
}
if err := ioutil.WriteFile("impl_test.go", out, 0660); err != nil {
err = ioutil.WriteFile("impl_test.go", out, 0660)
if err != nil {
panic(err)
}
}
Expand All @@ -373,7 +377,8 @@ If we generate our test code and perform the same benchmark procedure again:

```bash
$ go generate
$ perflock -governor 80% go test -v -run=none -bench=. -count=10 | tee inline.txt
$ perflock -governor 80% go test -v -run=none -bench=. -count=10 | \
tee inline.txt
$ benchstat inline.txt
name time/op
Vec/addv-s0-16 0.25ns ± 0%
Expand Down
Loading

0 comments on commit d1e66ec

Please sign in to comment.