diff --git a/Makefile b/Makefile index f7830ed..db5d52c 100644 --- a/Makefile +++ b/Makefile @@ -2,3 +2,6 @@ all: hugo s: hugo server -D + +pdf: + go build pdfgen.go \ No newline at end of file diff --git a/content/bench-time.pdf b/content/bench-time.pdf new file mode 100644 index 0000000..9b21db7 Binary files /dev/null and b/content/bench-time.pdf differ diff --git a/content/cgo-handle.pdf b/content/cgo-handle.pdf new file mode 100644 index 0000000..23b8140 Binary files /dev/null and b/content/cgo-handle.pdf differ diff --git a/content/generic-options.pdf b/content/generic-options.pdf new file mode 100644 index 0000000..68a7159 Binary files /dev/null and b/content/generic-options.pdf differ diff --git a/content/pointer-params.pdf b/content/pointer-params.pdf new file mode 100644 index 0000000..38a164b Binary files /dev/null and b/content/pointer-params.pdf differ diff --git a/content/posts/bench-time.md b/content/posts/bench-time.md index 0f627f4..4c8715b 100644 --- a/content/posts/bench-time.md +++ b/content/posts/bench-time.md @@ -1,6 +1,5 @@ --- date: 2020-09-30T09:02:20+01:00 -toc: true slug: /bench-time tags: - Benchmark @@ -9,14 +8,14 @@ tags: title: Eliminating A Source of Measurement Errors in Benchmarks --- -Author(s): [Changkun Ou](https://changkun.de) +Author(s): [Changkun Ou](mailto:research[at]changkun.de) Permalink: https://golang.design/research/bench-time -About six months ago, I did a [presentation](https://golang.design/s/gobench) -that talks about how to conduct a reliable benchmark in Go. -Recently, I submitted an issue [#41641](https://golang.org/issue/41641) to the Go project, which is also a subtle issue that you might need to address in some cases. - + +About six months ago, I did a presentation[^ou2020bench] +that talks about how to conduct a reliable benchmark[^beyer2019reliable] in Go. +Recently, I submitted an issue #41641[^ou2020timer] to the Go project, which is also a subtle issue that you might need to address in some cases. ## Introduction @@ -182,7 +181,7 @@ func (b *B) StopTimer() { } ``` -As we know that `runtime.ReadMemStats` stops the world, and each call to it is very time-consuming. This is an known issue [#20875](https://golang.org/issue/20875) regarding `runtime.ReadMemStats` in benchmarking. +As we know that `runtime.ReadMemStats` stops the world, and each call to it is very time-consuming. This is an known issue #20875[^snyder2020memstats] regarding `runtime.ReadMemStats` in benchmarking. Since we do not care about memory allocation at the moment, to avoid this issue, let's just hacking the source code by just comment out the call to `runtime.ReadMemStats`: @@ -295,7 +294,7 @@ to the execution time of target code plus the overhead of calling `now()`: ![](../assets/bench-time/flow.png) Assume the target code consumes in `T` ns, and the overhead of `now()` is `t` ns. -Now, let's run the target code `N` times. +Now, let's run the target code `N` times. The total measured time is `T*N+t`, then the average of a single iteration of the target code is `T+t/N`. Thus, the systematic measurement error becomes: `t/N`. Therefore with a higher `N`, you can get rid of the systematic error. @@ -357,11 +356,11 @@ calibrate := func(d time.Duration, n int) time.Duration { fmt.Printf("%v ns/op\n", calibrate(r.T, r.N)) ``` -As a take-away message, if you would like to write a micro-benchmark (whose runs in nanoseconds), and you have to interrupt the timer to clean up and reset some resources for some reason, then you must do a calibration on the measurement. If the Go's benchmark facility plans to fix #41641, then it is great; but if they don't, at least you are aware of this issue and know how to fix it now. +As a take-away message, if you would like to write a micro-benchmark (whose runs in nanoseconds), and you have to interrupt the timer to clean up and reset some resources for some reason, then you must do a calibration on the measurement. If the Go's benchmark facility plans to fix #41641[^ou2020timer], then it is great; but if they don't, at least you are aware of this issue and know how to fix it now. -## Further Reading Suggestions +## References -- Changkun Ou. Conduct Reliable Benchmarking in Go. March 26, 2020. https://golang.design/s/gobench -- Changkun Ou. testing: inconsistent benchmark measurements when interrupts timer. Sep 26, 2020. https://golang.org/issue/41641 -- Josh Bleecher Snyder. testing: consider calling ReadMemStats less during benchmarking. Jul 1, 2017. https://golang.org/issue/20875 -- Beyer, D., Löwe, S. & Wendler, P. Reliable benchmarking: requirements and solutions. Int J Softw Tools Technol Transfer 21, 1–29 (2019). https://doi.org/10.1007/s10009-017-0469-y +[^ou2020bench]: Changkun Ou. 2020. Conduct Reliable Benchmarking in Go. TalkGo Meetup. Virtual Event. March 26. https://golang.design/s/gobench +[^ou2020timer]: Changkun Ou. 2020. testing: inconsistent benchmark measurements when interrupts timer. The Go Project Issue Tracker. Sep 26. https://go.dev/issue/41641 +[^snyder2020memstats]: Josh Bleecher Snyder. 2020. testing: consider calling ReadMemStats less during benchmarking. The Go Project Issue Tracker. Jul 1. https://go.dev/issue/20875 +[^beyer2019reliable]: Beyer, D., Löwe, S. \& Wendler, P. 2019. Reliable benchmarking: requirements and solutions. International Journal on Software Tools for Technology Transfer. Issue 21. https://doi.org/10.1007/s10009-017-0469-y diff --git a/content/posts/cgo-handle.md b/content/posts/cgo-handle.md index 8b8402c..6ae18ae 100644 --- a/content/posts/cgo-handle.md +++ b/content/posts/cgo-handle.md @@ -1,6 +1,5 @@ --- date: 2021-06-10T19:24:41+02:00 -toc: true slug: /cgo-handle tags: - Go @@ -11,17 +10,17 @@ tags: title: A Concurrent-safe Centralized Pointer Managing Facility --- -Author: [Changkun Ou](https://changkun.de) +Author(s): [Changkun Ou](mailto:research[at]changkun.de) Permalink: [https://golang.design/research/cgo-handle](https://golang.design/research/cgo-handle) + In the Go 1.17 release, we contributed a new cgo facility [runtime/cgo.Handle](https://tip.golang.org/pkg/runtime/cgo/#Handle) in order to help future cgo applications better and easier to build concurrent-safe applications while passing pointers between Go and C. This article will guide us through the feature by asking what the feature offers to us, why we need such a facility, and how exactly we contributed to the implementation eventually. - ## Starting from Cgo and X Window Clipboard -Cgo is the de facto approach to interact with the C facility in Go. Nevertheless, how often do we need to interact with C in Go? The answer to the question depends on how much we work on the system level or how often do we have to utilize a legacy C library, such as for image processing. Whenever a Go application needs to use a legacy from C, it needs to import a sort of C dedicated package as follows, then on the Go side, one can simply call the `myprint` function through the imported `C` symbol: +Cgo[^go2019cgo] is the de facto approach to interact with the C facility in Go. Nevertheless, how often do we need to interact with C in Go? The answer to the question depends on how much we work on the system level or how often do we have to utilize a legacy C library, such as for image processing. Whenever a Go application needs to use a legacy from C, it needs to import a sort of C dedicated package as follows, then on the Go side, one can simply call the `myprint` function through the imported `C` symbol: ```go /* @@ -40,7 +39,7 @@ func main() { } ``` -A few months ago, while we were working on building a new package [`golang.design/x/clipboard`](https://golang.design/x/clipboard), a package that offers cross-platform clipboard access. We found out, there is a lacking of facility in Go, despite the variety of approaches in the wild, still suffering from soundness and performance issues. +A few months ago, while we were working on building a new package [`golang.design/x/clipboard`](https://golang.design/x/clipboard)[^ou2021clipboard], a package that offers cross-platform clipboard access. We found out, there is a lacking of facility in Go, despite the variety of approaches in the wild, still suffering from soundness and performance issues. In the [`golang.design/x/clipboard`](https://golang.design/x/clipboard) package, we had to cooperate with cgo to access system level APIs (technically, it is an API from a legacy and widely used C system), but lacking the facility of knowing the execution progress on the C side. For instance, on the Go side, we have to call the C code in a goroutine, then do something else in parallel: @@ -69,7 +68,7 @@ clipboard.Write("some information") We have to guarantee from its inside that when the function returns, the information should be available to be accessed by other applications. -Back then, our first idea to deal with the problem was to pass a channel from Go to C, then send a value through the channel from C to Go. After a quick research, we realized that it is impossible because channels cannot be passed as a value between C and Go due to the [rules of passing pointers in Cgo](https://pkg.go.dev/cmd/cgo#hdr-Passing_pointers) (see a previous [proposal document](https://golang.org/design/12416-cgo-pointers)). Even there is a way to pass the entire channel value to the C, there will be no facility to send values through that channel on the C side because C does not have the language support of the `<-` operator. +Back then, our first idea to deal with the problem was to pass a channel from Go to C, then send a value through the channel from C to Go. After a quick research, we realized that it is impossible because channels cannot be passed as a value between C and Go due to the [rules of passing pointers in Cgo](https://pkg.go.dev/cmd/cgo#hdr-Passing_pointers) (see a previous proposal document [^taylor2015cgorules] [^taylor2015cgorules2]). Even there is a way to pass the entire channel value to the C, there will be no facility to send values through that channel on the C side because C does not have the language support of the `<-` operator. The next idea was to pass a function callback, then get it called on the C side. The function's execution will use the desired channel to send a notification back to the waiting goroutine. @@ -142,11 +141,11 @@ func() { } ``` -Note that the `funcCallback` must be a global function variable. Otherwise, it is a violation of the [cgo pointer passing rules](https://pkg.go.dev/cmd/cgo/#hdr-Passing_pointers) as mentioned before. +Note that the `funcCallback` must be a global function variable. Otherwise, it is a violation of the [cgo pointer passing rules](https://pkg.go.dev/cmd/cgo/#hdr-Passing_pointers) as mentioned before. Furthermore, an immediate reaction to the readability of the above code is: too complicated. The demonstrated approach can only assign one function at a time, which is also a violation of the concurrent nature. Any per-goroutine dedicated application will not benefit from this approach because they need a per-goroutine function callback instead of a single global callback. By then, we wonder if there is a better and elegant approach to deal with it. -Through our research, we found that the need occurs quite often in the community, and is also being proposed in [golang/go#37033](https://golang.org/issue/37033). Luckily, such a facility is now ready in Go 1.17 :) +Through our research, we found that the need occurs quite often in the community, and is also being proposed in golang/go#37033[^dubov2020cgohandle]. Luckily, such a facility is now ready in Go 1.17 :) ## What is `runtime/cgo.Handle`? @@ -268,7 +267,7 @@ Next question: How to implement `cgo.Handle`? ## First Attempt -The first attempt was a lot complicated. Since we need a centralized way to manage all pointers in a concurrent-safe way, the quickest idea that comes to our mind was the `sync.Map` that maps a unique number to the desired value. Hence, we can easily use a global `sync.Map`: +The first attempt[^ou2021cgohandle] was a lot complicated. Since we need a centralized way to manage all pointers in a concurrent-safe way, the quickest idea that comes to our mind was the `sync.Map` that maps a unique number to the desired value. Hence, we can easily use a global `sync.Map`: ```go package cgo @@ -485,7 +484,7 @@ func (h Handle) Delete() { } ``` -In this implementation, we do not have to assume the runtime mechanism but just use the language. As long as the Go 1 compatibility keeps the promise `sync.Map` to work, there will be no need to rework the whole `Handle` design. Because of its simplicity, this is the accepted approach (see [CL 295369](https://golang.org/cl/295369)) by the Go team. +In this implementation, we do not have to assume the runtime mechanism but just use the language. As long as the Go 1 compatibility keeps the promise `sync.Map` to work, there will be no need to rework the whole `Handle` design. Because of its simplicity, this is the accepted approach (see CL 295369[^out2020cgohandle2]) by the Go team. Aside from a future re-implementation of `sync.Map` that optimizes parallelism, the `Handle` will automatically benefit from it. Let us do a final benchmark that compares the previous method and the current approach: @@ -521,14 +520,14 @@ Simpler, faster, why not? ## Conclusion -This article discussed the newly introduced `runtime/cgo.Handle` facility coming in the Go 1.17 release that we contributed. The `Handle` facility enables us to pass Go values between Go and C back and forth without breaking the cgo pointer passing rules. After a short introduction to the usage of the feature, we first discussed a first attempt implementation based on the fact that the runtime garbage collector is not a moving GC and the escape behavior of `interface{}` arguments. +This article discussed the newly introduced `runtime/cgo.Handle` facility coming in the Go 1.17 release that we contributed. The `Handle` facility enables us to pass Go values between Go and C back and forth without breaking the cgo pointer passing rules. After a short introduction to the usage of the feature, we first discussed a first attempt implementation based on the fact that the runtime garbage collector is not a moving GC and the escape behavior of `interface{}` arguments. After a few discussions of the ambiguity of the Handle semantics and the drawbacks in the previous implementation, we also introduced a straightforward and better-performed approach and demonstrated its performance. As a real-world demonstration, we have been using the mentioned two approaches in two of our released packages for quite a long time: [golang.design/x/clipboard](https://github.com/golang-design/clipboard) -and [golang.design/x/hotkey](https://github.com/golang-design/hotkey) -before in their `internal/cgo` package. +and [golang.design/x/hotkey](https://github.com/golang-design/hotkey) [^ou2021hotkey] +before in their `internal/cgo` package. We are looking forward to switching to the officially released `runtime/cgo` package in the Go 1.17 release. @@ -543,14 +542,13 @@ When we allocate 100 handles per second, the handle space can run out in [CC us](mailto:hi[at]golang.design) when you send a CL, it would also be interesting for us to read your excellent approach._ +## References -## Further Reading Suggestions - -- Alex Dubov. runtime: provide centralized facility for managing (c)go pointer handles. Feb 5, 2020. https://golang.org/issue/37033 -- Changkun Ou. runtime/cgo: add Handle for managing (c)go pointers Feb 21, 2021. https://golang.org/cl/294670 -- Changkun Ou. runtime/cgo: add Handle for managing (c)go pointers Feb 23, 2021. https://golang.org/cl/295369 -- Ian Lance Taylor. cmd/cgo: specify rules for passing pointers between Go and C. Aug 31, 2015. https://golang.org/issue/12416 -- Ian Lance Taylor. Proposal: Rules for passing pointers between Go and C. October, 2015. https://golang.org/design/12416-cgo-pointers -- Go Contributors. cgo. Mar 12, 2019. https://github.com/golang/go/wiki/cgo -- The golang.design Initiative. 📋 cross-platform clipboard package in Go. Feb 25, 2021. https://github.com/golang-design/clipboard -- The golang.design Initiative. ⌨️ cross-platform hotkey package in GO. Feb 27, 2021. https://github.com/golang-design/hotkey \ No newline at end of file +[^dubov2020cgohandle]: Alex Dubov. 2020. runtime: provide centralized facility for managing (c)go pointer handles. The Go Project Issue Tracker. Feb 5. https://go.dev/issue/37033 +[^ou2021cgohandle]: Changkun Ou. 2021. runtime/cgo: add Handle for managing (c)go pointers. The Go Project CL Tracker. Feb 21, 2021. https://go.dev/cl/294670 +[^out2020cgohandle2]: Changkun Ou. 2021. runtime/cgo: add Handle for managing (c)go pointers. The Go Project CL Tracker. Feb 23, 2021. https://go.dev/cl/295369 +[^taylor2015cgorules]: Ian Lance Taylor. 2015. cmd/cgo: specify rules for passing pointers between Go and C. The Go Project Issue Tracker. Aug 31. https://go.dev/issue/12416 +[^taylor2015cgorules2]: Ian Lance Taylor. 2015. Proposal: Rules for passing pointers between Go and C. The Go project design proposals. https://golang.org/design/12416-cgo-pointers +[^go2019cgo]: Go Contributors. cgo. Mar 12, 2019. https://github.com/golang/go/wiki/cgo +[^ou2021clipboard]: Changkun Ou. 2021. cross-platform clipboard package. The golang.design Initiative. Feb 25. https://github.com/golang-design/clipboard +[^ou2021hotkey]: Changkun Ou. 2021. cross-platform hotkey package. The golang.design Initiative. Feb 27. https://github.com/golang-design/hotkey \ No newline at end of file diff --git a/content/posts/generic-options.md b/content/posts/generic-options.md index 1d49913..36ce9b9 100644 --- a/content/posts/generic-options.md +++ b/content/posts/generic-options.md @@ -1,6 +1,5 @@ --- date: 2022-04-11T00:27:43+02:00 -toc: true slug: /generic-option tags: - Go @@ -9,19 +8,19 @@ tags: title: (Generic) Functional Options Pattern --- -Author(s): [Changkun Ou](https://changkun.de) - -Permalink: https://golang.design/research/generic-option - Targeted Go version: 1.18 -The widely used self-referential function pattern as options, [originally proposed by Rob Pike](https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html), allows us to design a flexible set of APIs to help arbitrary configurations and initialization of a struct. However, when such a pattern is cumbersome when we use one option to support multiple types. This article investigates how the latest Go generics design could empower a refreshed "generic" functional options pattern and show what improvements in the future version of Go could better support such a pattern. +Author(s): [Changkun Ou](mailto:research[at]changkun.de) + +Permalink: https://golang.design/research/generic-option + +The widely used self-referential function pattern as options, originally proposed by Rob Pike[^pike2014funcopt], allows us to design a flexible set of APIs to help arbitrary configurations and initialization of a struct. However, when such a pattern is cumbersome when we use one option to support multiple types. This article investigates how the latest Go generics design could empower a refreshed "generic" functional options pattern and show what improvements in the future version of Go could better support such a pattern. ## The Functional Options Pattern -In the [dotGo](https://www.dotgo.eu/) 2014, [Dave Cheney](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis) well explained the motivation and the use of self-referential functional options pattern in addition to the original thoughts from Rob Pike. Let's recall the key idea briefly. +In the [dotGo](https://www.dotgo.eu/) 2014, Dave Cheney[^cheney2014funcopt] well explained the motivation and the use of self-referential functional options pattern in addition to the original thoughts from Rob Pike. Let's recall the key idea briefly. Assume we have a struct `A` and it internally holds two user-customizable fields `v1`, `v2`: @@ -447,10 +446,10 @@ pkgname.NewB(pkgname.V1(42), pkgname.V2(42)) With this simplification, on the caller side, we see a sort of magic function V1 as an option, which can be used both for `NewA` and `NewB`. Unfortunately, with the current Go 1.18 generics implementation, this type of inference is not yet supported. -We have created [an issue](https://go.dev/issue/52272) for the Go team and see if this type of optimization could be possible without introducing any other flaws. Let's looking forward to it! +We have created an issue[^ou2022coretype] for the Go team and see if this type of optimization could be possible without introducing any other flaws. Let's looking forward to it! -## Further Reading Suggestions +## References -- Rob Pike. Self-referential functions and the design of options. Jan 24, 2014. https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html -- Dave Cheney. Functional options for friendly APIs. Oct 17, 2014. https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis -- cmd/compile: infer argument types when a type set only represents its core type. https://go.dev/issue/52272 \ No newline at end of file +[^pike2014funcopt]: Rob Pike. Self-referential functions and the design of options. Jan 24, 2014. https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html +[^cheney2014funcopt]: Dave Cheney. Functional options for friendly APIs. Oct 17, 2014. https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis +[^ou2022coretype]: Changkun Ou. 2022. cmd/compile: infer argument types when a type set only represents its core type. The Go Project Issue Tracker. April 11. https://go.dev/issue/52272 \ No newline at end of file diff --git a/content/posts/pointer-params.md b/content/posts/pointer-params.md index 8beff90..196a271 100644 --- a/content/posts/pointer-params.md +++ b/content/posts/pointer-params.md @@ -1,6 +1,5 @@ --- date: 2020-11-05T09:14:53+01:00 -toc: true slug: /pointer-params tags: - Performance @@ -9,14 +8,14 @@ tags: title: Pointers Might Not be Ideal as Arguments --- -Author(s): [Changkun Ou](https://changkun.de) +Author(s): [Changkun Ou](mailto:research[at]changkun.de) Permalink: https://golang.design/research/pointer-params + We are aware that using pointers for passing parameters can avoid data copy, which will benefit the performance. Nevertheless, there are always some edge cases we might need concern. - ## Introduction @@ -78,7 +77,7 @@ func BenchmarkVec(b *testing.B) { } ``` -And run as follows: +And run as follows: ```sh $ perflock -governor 80% go test -v -run=none -bench=. -count=10 | tee new.txt @@ -93,11 +92,11 @@ Vec/addv-16 0.25ns ± 2% Vec/addp-16 2.20ns ± 0% name alloc/op -Vec/addv-16 0.00B -Vec/addp-16 0.00B +Vec/addv-16 0.00B +Vec/addp-16 0.00B name allocs/op -Vec/addv-16 0.00 +Vec/addv-16 0.00 Vec/addp-16 0.00 ``` @@ -107,7 +106,7 @@ How is this happening? This is all because of compiler optimization, and mostly because of inlining. -If we disable inline from the `addv` and `addp`: +If we disable inline[^cheney2020inline] [^cheney2020inline2] from the `addv` and `addp`: ```go //go:noinline @@ -161,7 +160,7 @@ to a direct manipulation: ```go v1 := vec{1, 2, 3, 4} v2 := vec{4, 5, 6, 7} -v1.x, v1.y, v1.z, v1.w = v1.x+v2.x, v1.y+v2.y, v1.z+v2.z, v1.w+v2.w +v1.x, v1.y, v1.z, v1.w = v1.x+v2.x, v1.y+v2.y, v1.z+v2.z, v1.w+v2.w ``` ## Addressing Modes @@ -219,10 +218,9 @@ The dumped assumbly code is as follows: ``` The `addv` implementation uses values from the previous stack frame and -writes the result directly to the return; whereas `addp` needs MOVQ that +writes the result directly to the return; whereas `addp` needs MOVQ[^man2020movsd] [^man2020addsd] [^man2020moveq] that copies the parameter to different registers (e.g., copy pointers to AX and CX), -then write back when returning. Therefore, with inline disabled, the reason that `addv` -is slower than `addp` is caused by different memory access pattern. +then write back when returning. Therefore, with inline disabled, the reason that `addv` is slower than `addp` is caused by different memory access pattern. ## Conclusion @@ -430,11 +428,10 @@ or pass-by-pointer. If you are certain that your code won't produce any escape variables, and the size of your argument is smaller than 4*8 = 32 bytes, then you should go for pass-by-value; otherwise, you should keep using pointers. -## Further Reading Suggestions +## References -- Changkun Ou. Conduct Reliable Benchmarking in Go. March 26, 2020. https://golang.design/s/gobench -- Dave Cheney. Mid-stack inlining in Go. May 2, 2020. https://dave.cheney.net/2020/05/02/mid-stack-inlining-in-go -- Dave Cheney. Inlining optimisations in Go. April 25, 2020. https://dave.cheney.net/2020/04/25/inlining-optimisations-in-go -- MOVSD. Move or Merge Scalar Double-Precision Floating-Point Value. Last access: 2020-10-27. https://www.felixcloutier.com/x86/movsd -- ADDSD. Add Scalar Double-Precision Floating-Point Values. Last access: 2020-10-27. https://www.felixcloutier.com/x86/addsd -- MOVEQ. Move Quadword. Last access: 2020-10-27. https://www.felixcloutier.com/x86/movq +[^cheney2020inline]: Dave Cheney. Mid-stack inlining in Go. May 2, 2020. https://dave.cheney.net/2020/05/02/mid-stack-inlining-in-go +[^cheney2020inline2]: Dave Cheney. Inlining optimisations in Go. April 25, 2020. https://dave.cheney.net/2020/04/25/inlining-optimisations-in-go +[^man2020movsd]: MOVSD. Move or Merge Scalar Double-Precision Floating-Point Value. Last access: 2020-10-27. https://www.felixcloutier.com/x86/movsd +[^man2020addsd]: ADDSD. Add Scalar Double-Precision Floating-Point Values. Last access: 2020-10-27. https://www.felixcloutier.com/x86/addsd +[^man2020moveq]: MOVEQ. Move Quadword. Last access: 2020-10-27. https://www.felixcloutier.com/x86/movq diff --git a/content/posts/ultimate-channel.md b/content/posts/ultimate-channel.md index 422c6e1..3476e1a 100644 --- a/content/posts/ultimate-channel.md +++ b/content/posts/ultimate-channel.md @@ -1,6 +1,5 @@ --- date: 2021-08-09T09:02:42+02:00 -toc: true slug: /ultimate-channel tags: - Go @@ -9,17 +8,17 @@ tags: title: The Ultimate Channel Abstraction --- -Author(s): [Changkun Ou](https://changkun.de) +Author(s): [Changkun Ou](mailto:research[at]changkun.de) Permalink: https://golang.design/research/ultimate-channel + Recently, I have been rethinking the programming patterns regarding graphics applications, and already wrote a 3D graphics package in Go, called [polyred](https://poly.red). While I was designing the rendering pipeline APIs, a tricky deadlock struggled with me for a while and led to creating an unbounded channel as a workaround solution eventually. - ## The problem @@ -345,7 +344,7 @@ implementation constructs an `interface{}` typed channel. We may ask ourselves, does unbounded make sense to have in the Go language with this particular example? Does the Go team ever consider such usage? -The answer to the second question is: Yes. They do, see [golang/go#20352](https://golang.org/issue/20352). +The answer to the second question is: Yes. They do, see golang/go#20352 [^rgoch2017unbound]. The discussion thread shows that unbounded channels indeed serve a certain application, but clear drawbacks may hurt the application. The major drawback is that an unbounded channel may run out of memory (OOM). @@ -358,7 +357,7 @@ the statically typed Go code. Eventually, Ian Lance Taylor from the Go team an unbounded channel may have a sort of usage but is unworthy to be added to the language. As long as we have generics, a type-safe unbounded channel can be easily implemented in a library, answering the first question. -As of Go 1.18, soon we have type parameters, the above difficulty finally +As of Go 1.18, soon we have type parameters[^taylor2021typeparam], the above difficulty finally can be resolved. Here I provide a generic channel abstraction that is able @@ -614,8 +613,8 @@ Lastly, we also made a few contribution to the [fyne-io/fyne] GUI project to improve their draw call batching mechanism, where it previously can only render a fixed number of draw calls can be executed at a frame (more draw calls are ignored), which fixes one of their long-existing code. -See [fyne-io/fyne#2406](https://github.com/fyne-io/fyne/pull/2406), -and [fyne-io/fyne#2473](https://github.com/fyne-io/fyne/pull/2473) +See fyne-io/fyne#2406[^ou2021unbound], +and fyne-io/fyne#2473[^ou2021glfix] for more details. Here are two videos to demonstrate the problem intuitively: | Before the fix | After the fix | @@ -627,7 +626,7 @@ Before the fix, the tiny blocks are only partially rendered; whereas all blocks ## Conclusion -In this article, we talked about a generic implementation of a channel with arbitrary capacity through a real-world deadlock example. A public package [chann](https://golang.design/x/chann) is provided as a generic channel package. +In this article, we talked about a generic implementation of a channel with arbitrary capacity through a real-world deadlock example. A public package chann[^ou2021chann] is provided as a generic channel package. ```go import "golang.design/x/chann" @@ -642,14 +641,14 @@ As we know that channel is typically used for synchronization purposes. If there is a `len(ch)` that happens concurrently with a send/receive operation, there is no guarantee what is the return of the `len()`. The length is outdated immediately as `len()` returns. -This scenario is neither discussed in the [language specification](https://golang.org/ref/spec), or the [Go's memory model](https://golang.org/ref/mem). After all, Do we really need a `len()` operation for the ultimate channel abstraction? The answer speaks for itself. +This scenario is neither discussed in the language specification[^go2021spec], or the Go's memory model[^go2014mem]. After all, Do we really need a `len()` operation for the ultimate channel abstraction? The answer speaks for itself. -## Further Reading Suggestions +## References -- Ian Lance Taylor. Type Parameters. March 19, 2021. https://golang.org/design/43651-type-parameters -- rgooch. proposal: spec: add support for unlimited capacity channels. 13 May 2017. https://golang.org/issue/20352 -- The Go Authors. The Go Programming Language Specification. Feb 10, 2021. https://golang.org/ref/spec -- The Go Authors. The Go Memory Model. May 31, 2014. https://golang.org/ref/mem -- Changkun Ou. internal/dirver: use unbounded channel for event processing #2406. Aug 27, 2021. https://github.com/fyne-io/fyne/pull/2406 -- Changkun Ou. internal/driver: fix rendering freeze in mobile #2406. Sep 15, 2021. https://github.com/fyne-io/fyne/pull/2473 -- Changkun Ou. Package `chann`. Sep 10, 2021. https://golang.design/s/chann \ No newline at end of file +[^taylor2021typeparam]: Ian Lance Taylor. Type Parameters. March 19, 2021. https://golang.org/design/43651-type-parameters +[^rgoch2017unbound]: rgooch. proposal: spec: add support for unlimited capacity channels. 13 May 2017. https://golang.org/issue/20352 +[^go2021spec]: The Go Authors. The Go Programming Language Specification. Feb 10, 2021. https://golang.org/ref/spec +[^go2014mem]: The Go Authors. The Go Memory Model. May 31, 2014. https://golang.org/ref/mem +[^ou2021unbound]: Changkun Ou. internal/dirver: use unbounded channel for event processing Issue 2406. Aug 27, 2021. https://github.com/fyne-io/fyne/pull/2406 +[^ou2021glfix]: Changkun Ou. internal/driver: fix rendering freeze in mobile Issue 2473. Sep 15, 2021. https://github.com/fyne-io/fyne/pull/2473 +[^ou2021chann]: Changkun Ou. Package chann. Sep 10, 2021. https://golang.design/s/chann \ No newline at end of file diff --git a/content/posts/zero-alloc-call-sched.md b/content/posts/zero-alloc-call-sched.md index b9392ac..9a574b6 100644 --- a/content/posts/zero-alloc-call-sched.md +++ b/content/posts/zero-alloc-call-sched.md @@ -1,6 +1,5 @@ --- date: 2021-01-26T13:11:00+01:00 -toc: true slug: /zero-alloc-call-sched tags: - Channel @@ -13,10 +12,11 @@ tags: title: Scheduling Function Calls with Zero Allocation --- -Author(s): [Changkun Ou](https://changkun.de) +Author(s): [Changkun Ou](mailto:research[at]changkun.de) Permalink: https://golang.design/research/zero-alloc-call-sched + GUI programming in Go is a little bit tricky. The infamous issue regarding interacting with legacy, GUI frameworks is that most graphics related APIs must be called from the main thread. @@ -24,7 +24,6 @@ The issue violates the concurrent nature of Go: A goroutine maybe arbitrarily and randomly scheduled or rescheduled on different running threads, i.e., the same piece of code will be called from different threads over time, even without evolving the `go` keyword. - ## Background @@ -516,7 +515,7 @@ it becomes somewhat tricky. In Go, variables can be allocated from heap if: -1. Using `make` and `new` keywords explicitly, or +1. Using `make` and `new` keywords explicitly, or 2. Escape from the stack The second case is a little bit advance from the regular use of Go. @@ -678,7 +677,7 @@ func acquireSudog() *sudog { ``` Unfortunately, this is entirely outside the control of the userland. -We are not able to optimize here anymore. +We are not able to optimize here anymore. Nevertheless, we have reached our goal for today, and this is the best of what we can do so far. @@ -708,7 +707,7 @@ There are several points we can summarize: 4. Sending information via a channel can cause allocation intrinsically from the runtime. 5. Go runtime grows the heap 8K on each step as page allocation -We also encapsulated all the abstractions from this research and published two packages: `mainthread`[^mainthread] and `thread`[^thread]. These packages allow us to schedule any function calls either on the main thread or a specific thread. Furthermore, We also submitted a pull request to the Fyne project [^fyne], which could reduce a considerable amount of memory allocations from the existing real-world GUI applications. +We also encapsulated all the abstractions from this research and published two packages: `mainthread`[^mainthread] and `thread`[^thread]. These packages allow us to schedule any function calls either on the main thread or a specific thread. Furthermore, We also submitted a pull request to the Fyne project[^fyne], which could reduce a considerable amount of memory allocations from the existing real-world GUI applications. Have fun! diff --git a/content/ultimate-channel.pdf b/content/ultimate-channel.pdf new file mode 100644 index 0000000..4db722b Binary files /dev/null and b/content/ultimate-channel.pdf differ diff --git a/content/zero-alloc-call-sched.pdf b/content/zero-alloc-call-sched.pdf new file mode 100644 index 0000000..63267d5 Binary files /dev/null and b/content/zero-alloc-call-sched.pdf differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6480a62 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module golang.design/x/research + +go 1.18 + +require ( + github.com/yuin/goldmark v1.4.12 + github.com/yuin/goldmark-meta v1.1.0 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b + mvdan.cc/xurls/v2 v2.4.0 +) + +require gopkg.in/yaml.v2 v2.3.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4d3ac16 --- /dev/null +++ b/go.sum @@ -0,0 +1,22 @@ +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0= +github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= +github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mvdan.cc/xurls/v2 v2.4.0 h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc= +mvdan.cc/xurls/v2 v2.4.0/go.mod h1:+GEjq9uNjqs8LQfM9nVnM8rff0OQ5Iash5rzX+N1CSg= diff --git a/pdfgen.go b/pdfgen.go new file mode 100644 index 0000000..4b20e87 --- /dev/null +++ b/pdfgen.go @@ -0,0 +1,267 @@ +// Copyright 2022 The golang.design Initiative. +// All rights reserved. Created by Changkun Ou + +package main + +import ( + "bufio" + "bytes" + "flag" + "fmt" + "log" + "os" + "os/exec" + "regexp" + "strings" + "time" + + "github.com/yuin/goldmark" + "github.com/yuin/goldmark-meta" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "gopkg.in/yaml.v3" + "mvdan.cc/xurls/v2" +) + +var md goldmark.Markdown + +func init() { + md = goldmark.New( + goldmark.WithExtensions( + meta.Meta, + extension.Table, + ), + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + ) +} + +func usage() { + fmt.Fprintf(os.Stderr, `pdfgen converts a golang.design research markdown file to a pdf. + +usage: pdfgen content/posts/bench-time.md +`) +} + +func main() { + flag.Parse() + args := flag.Args() + if len(args) != 1 { + usage() + return + } + path := args[0] + + // Only deal with .md files + if !strings.HasSuffix(path, ".md") { + log.Fatalf("pdfgen: input file must be a markdown file.") + return + } + + b, err := os.ReadFile(path) + if err != nil { + log.Fatalf("pdfgen: failed to load the given markdown file.") + } + + var buf bytes.Buffer + context := parser.NewContext() + if err := md.Convert(b, &buf, parser.WithContext(context)); err != nil { + log.Fatal(err) + } + metaData := meta.Get(context) + convertDate(metaData) + + authors := parseAuthor(b) + metaData["author"] = authors + + abstrat := parseAbstract(b) + // https://stackoverflow.com/questions/1919982/regex-smallest-possible-match-or-nongreedy-match + re := regexp.MustCompile("\\[\\^(.*?)\\]") + abstrat = re.ReplaceAllString(abstrat, "\\cite{$1}") // use citation key + metaData["abstract"] = abstrat + + metaData["header-includes"] = `\usepackage{fancyhdr} + \pagestyle{fancy} + \fancyhead[LE,RO]{\rightmark} + \fancyhead[RE,LO]{The golang.design Research} + \fancyfoot{} + \fancyfoot[C]{\thepage}` + + body := parseBody(b) + body = re.ReplaceAllString(body, "\\cite{$1}") // use citation key + + head, err := yaml.Marshal(metaData) + if err != nil { + log.Fatalf("pdfgen: failed to construct metadata") + } + + content := fmt.Sprintf(`--- +%v +--- +%v +`, string(head), body) + + // Prepare all content. + + ref := "content/posts/ref.tex" + references := parseReferences(b) + if err := os.WriteFile(ref, []byte(references), os.ModePerm); err != nil { + log.Fatalf("pdfgen: cannot create reference file: %v", err) + } + defer os.Remove(ref) + + article := "content/posts/article.md" + if err := os.WriteFile(article, []byte(content), os.ModePerm); err != nil { + log.Fatalf("pdfgen: cannot create temporary file: %v", err) + } + defer os.Remove(article) + + // Generate pdf + + before, after, _ := strings.Cut(strings.TrimSuffix(path, ".md")+".pdf", "/posts") + dst := before + after + cmd := exec.Command("pandoc", article, ref, + "-V", "linkcolor:blue", + "--pdf-engine=xelatex", + "-o", dst) + log.Println(cmd.String()) + if b, err := cmd.CombinedOutput(); err != nil { + log.Fatal(string(b)) + } + return +} + +func convertDate(metaData map[string]any) { + dateRaw, ok := metaData["date"] + if !ok { + log.Fatalf("pdfgen: metadata missing date information.") + } + date, ok := dateRaw.(string) + if !ok { + log.Fatalf("pdfgen: metadata contains invalid date format.") + } + t, err := time.Parse("2006-01-02T15:04:05Z07:00", date) + if err != nil { + log.Fatalf("pdfgen: cannot parse date: %v", err) + } + metaData["date"] = t.Format("January 02, 2006") +} + +type author struct { + Name string + Email string +} + +func (a author) String() string { + return fmt.Sprintf("%v^[Email: %v]", a.Name, a.Email) +} + +func parseAuthor(b []byte) []string { + s := bufio.NewScanner(bytes.NewReader(b)) + authors := []string{} + + for s.Scan() { + l := s.Text() + if strings.HasPrefix(l, "Author(s): ") { + authorsStr := strings.TrimPrefix(l, "Author(s): ") + authorList := strings.Split(authorsStr, ", ") + + for _, a := range authorList { + before, after, ok := strings.Cut(a, "](") + if !ok { + continue + } + name := strings.TrimPrefix(before, "[") + email := strings.TrimPrefix(strings.TrimSuffix(after, ")"), "mailto:") + email = strings.ReplaceAll(email, "[at]", "@") + authors = append(authors, author{name, email}.String()) + } + } + } + + if len(authors) == 0 { + log.Fatalf(`pdfgen: cannot find authors, make sure the markdown uses the correct convention: + +Author(s): [FirstName LastName](mailto:email), [FirstName LastName](mailto:email)`) + } + + return authors +} + +func parseAbstract(b []byte) string { + content := string(b) + + var ok bool + _, content, ok = strings.Cut(content, "\n") + if !ok { + goto err + } + content, _, ok = strings.Cut(content, "\n") + if !ok { + goto err + } + + return content + +err: + log.Fatal(`pdfgen: cannot find abstract, make sure the markdown uses the correct convention: + + + abstract content goes here... + + `) + return "" +} + +func parseBody(b []byte) string { + content := string(b) + + var ok bool + _, content, ok = strings.Cut(content, "\n") + if !ok { + goto err + } + content, _, ok = strings.Cut(content, "## References") + if !ok { + goto err + } + return content + +err: + log.Fatal(`pdfgen: cannot find body, make sure the markdown uses the correct convention: + + + + content body... + + ## References + `) + return "" +} + +func parseReferences(b []byte) string { + content := string(b) + + var ok bool + _, content, ok = strings.Cut(content, "## References\n") + if !ok { + log.Fatal(`pdfgen: cannot find references, make sure the markdown uses the correct convention: + + ## References + + [@ou2022bench]: Changkun Ou. 2020. Conduct Reliable Benchmarking in Go. TalkGo Meetup. Virtual Event. March 26. https://golang.design/s/gobench + `) + + } + + content = strings.ReplaceAll(content, "[^", "\\bibitem{") + content = strings.ReplaceAll(content, "]:", "}") + + rxStrict := xurls.Strict() + urls := rxStrict.FindAllString(content, -1) + for _, url := range urls { + content = strings.ReplaceAll(content, url, "\\url{"+url+"}") + } + return "\\begin{thebibliography}{99}" + content + "\\end{thebibliography}" +} diff --git a/themes/research/layouts/_default/single.html b/themes/research/layouts/_default/single.html index e78fe10..fb8f4c6 100644 --- a/themes/research/layouts/_default/single.html +++ b/themes/research/layouts/_default/single.html @@ -2,8 +2,9 @@

{{ .Title }}

- - PV/UV:/ + | + PV/UV:/ | + PDF | {{ range .Params.tags }} #{{ . }} {{ end }}