Adding sections
BIN
.cache/plugin/social/53f24aa072dbcfbf532abf9e62945e11.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
.cache/plugin/social/6c3afdd84225436ef1eddcf2b1c82547.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
.cache/plugin/social/c7b8072b4d90baf29b7e819cb3a02064.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
.cache/plugin/social/ea9a0a27b2b9ccc98f485e832ea2eef4.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
.cache/plugin/social/ee1d2159b11eef4ec692741f34e52fcd.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
111
docs/28-maps-memory-leaks.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
---
|
||||
title: Maps and memory leaks (#28)
|
||||
---
|
||||
|
||||
# Maps and memory leaks
|
||||
|
||||
When working with maps in Go, we need to understand some important characteristics of how a map grows and shrinks. Let’s delve into this to prevent issues that can cause memory leaks.
|
||||
|
||||
First, to view a concrete example of this problem, let’s design a scenario where we will work with the following map:
|
||||
|
||||
```go
|
||||
m := make(map[int][128]byte)
|
||||
```
|
||||
|
||||
Each value of m is an array of 128 bytes. We will do the following:
|
||||
|
||||
1. Allocate an empty map.
|
||||
2. Add 1 million elements.
|
||||
3. Remove all the elements, and run a Garbage Collection (GC).
|
||||
|
||||
After each step, we want to print the size of the heap (using a `printAlloc` utility function). This shows us how this example behaves memory-wise:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
n := 1_000_000
|
||||
m := make(map[int][128]byte)
|
||||
printAlloc()
|
||||
|
||||
for i := 0; i < n; i++ { // Adds 1 million elements
|
||||
m[i] = [128]byte{}
|
||||
}
|
||||
printAlloc()
|
||||
|
||||
for i := 0; i < n; i++ { // Deletes 1 million elements
|
||||
delete(m, i)
|
||||
}
|
||||
|
||||
runtime.GC() // Triggers a manual GC
|
||||
printAlloc()
|
||||
runtime.KeepAlive(m) // Keeps a reference to m so that the map isn’t collected
|
||||
}
|
||||
|
||||
func printAlloc() {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
fmt.Printf("%d KB\n", m.Alloc/1024)
|
||||
}
|
||||
```
|
||||
|
||||
We allocate an empty map, add 1 million elements, remove 1 million elements, and then run a GC. We also make sure to keep a reference to the map using [`runtime.KeepAlive`](https://pkg.go.dev/runtime#KeepAlive) so that the map isn’t collected as well. Let’s run this example:
|
||||
|
||||
```
|
||||
0 MB <-- After m is allocated
|
||||
461 MB <-- After we add 1 million elements
|
||||
293 MB <-- After we remove 1 million elements
|
||||
```
|
||||
|
||||
What can we observe? At first, the heap size is minimal. Then it grows significantly after having added 1 million elements to the map. But if we expected the heap size to decrease after removing all the elements, this isn’t how maps work in Go. In the end, even though the GC has collected all the elements, the heap size is still 293 MB. So the memory shrunk, but not as we might have expected. What’s the rationale? We need to delve into how a map works in Go.
|
||||
|
||||
A map provides an unordered collection of key-value pairs in which all the keys are distinct. In Go, a map is based on the hash table data structure: an array where each element is a pointer to a bucket of key-value pairs, as shown in figure 1.
|
||||
|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 1: A hash table example with a focus on bucket 0.</figcaption>
|
||||
</figure>
|
||||
|
||||
Each bucket is a fixed-size array of eight elements. In the case of an insertion into a bucket that is already full (a bucket overflow), Go creates another bucket of eight elements and links the previous one to it. Figure 2 shows an example:
|
||||
|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 2: In case of a bucket overflow, Go allocates a new bucket and links the previous bucket to it.</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
Under the hood, a Go map is a pointer to a runtime.hmap struct. This struct contains multiple fields, including a B field, giving the number of buckets in the map:
|
||||
|
||||
```go
|
||||
type hmap struct {
|
||||
B uint8 // log_2 of # of buckets
|
||||
// (can hold up to loadFactor * 2^B items)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
After adding 1 million elements, the value of B equals 18, which means 2¹⁸ = 262,144 buckets. When we remove 1 million elements, what’s the value of B? Still 18. Hence, the map still contains the same number of buckets.
|
||||
|
||||
The reason is that the number of buckets in a map cannot shrink. Therefore, removing elements from a map doesn’t impact the number of existing buckets; it just zeroes the slots in the buckets. A map can only grow and have more buckets; it never shrinks.
|
||||
|
||||
In the previous example, we went from 461 MB to 293 MB because the elements were collected, but running the GC didn’t impact the map itself. Even the number of extra buckets (the buckets created because of overflows) remains the same.
|
||||
|
||||
Let’s take a step back and discuss when the fact that a map cannot shrink can be a problem. Imagine building a cache using a `map[int][128]byte`. This map holds per customer ID (the `int`), a sequence of 128 bytes. Now, suppose we want to save the last 1,000 customers. The map size will remain constant, so we shouldn’t worry about the fact that a map cannot shrink.
|
||||
|
||||
However, let’s say we want to store one hour of data. Meanwhile, our company has decided to have a big promotion for Black Friday: in one hour, we may have millions of customers connected to our system. But a few days after Black Friday, our map will contain the same number of buckets as during the peak time. This explains why we can experience high memory consumption that doesn’t significantly decrease in such a scenario.
|
||||
|
||||
What are the solutions if we don’t want to manually restart our service to clean the amount of memory consumed by the map? One solution could be to re-create a copy of the current map at a regular pace. For example, every hour, we can build a new map, copy all the elements, and release the previous one. The main drawback of this option is that following the copy and until the next garbage collection, we may consume twice the current memory for a short period.
|
||||
|
||||
Another solution would be to change the map type to store an array pointer: `map[int]*[128]byte`. It doesn’t solve the fact that we will have a significant number of buckets; however, each bucket entry will reserve the size of a pointer for the value instead of 128 bytes (8 bytes on 64-bit systems and 4 bytes on 32-bit systems).
|
||||
|
||||
Coming back to the original scenario, let’s compare the memory consumption for each map type following each step. The following table shows the comparison.
|
||||
|
||||
| Step | `map[int][128]byte` | `map[int]*[128]byte` |
|
||||
|---|---|---|
|
||||
| Allocate an empty map | 0 MB | 0 MB |
|
||||
| Add 1 million elements | 461 MB | 182 MB |
|
||||
| Remove all the elements and run a GC | 293 MB | 38 MB |
|
||||
|
||||
???+ note
|
||||
|
||||
If a key or a value is over 128 bytes, Go won’t store it directly in the map bucket. Instead, Go stores a pointer to reference the key or the value.
|
||||
|
||||
As we have seen, adding n elements to a map and then deleting all the elements means keeping the same number of buckets in memory. So, we must remember that because a Go map can only grow in size, so does its memory consumption. There is no automated strategy to shrink it. If this leads to high memory consumption, we can try different options such as forcing Go to re-create the map or using pointers to check if it can be optimized.
|
||||
367
docs/89-benchmarks.md
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
---
|
||||
title: Writing inaccurate benchmarks (#89)
|
||||
---
|
||||
|
||||
# Writing inaccurate benchmarks
|
||||
|
||||
In general, we should never guess about performance. When writing optimizations, so many factors may come into play that even if we have a strong opinion about the results, it’s rarely a bad idea to test them. However, writing benchmarks isn’t straightforward. It can be pretty simple to write inaccurate benchmarks and make wrong assumptions based on them. The goal of this post is to examine four common and concrete traps leading to inaccuracy:
|
||||
|
||||
* Not resetting or pausing the timer
|
||||
* Making wrong assumptions about micro-benchmarks
|
||||
* Not being careful about compiler optimizations
|
||||
* Being fooled by the observer effect
|
||||
|
||||
## General concepts
|
||||
|
||||
Before discussing these traps, let’s briefly review how benchmarks work in Go. The skeleton of a benchmark is as follows:
|
||||
|
||||
```go
|
||||
func BenchmarkFoo(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
foo()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The function name starts with the `Benchmark` prefix. The function under test (foo) is called within the `for` loop. `b.N` represents a variable number of iterations. When running a benchmark, Go tries to make it match the requested benchmark time. The benchmark time is set by default to 1 second and can be changed with the `-benchtime` flag. `b.N` starts at 1; if the benchmark completes in under 1 second, `b.N` is increased, and the benchmark runs again until `b.N` roughly matches benchtime:
|
||||
|
||||
```
|
||||
$ go test -bench=.
|
||||
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
|
||||
BenchmarkFoo-4 73 16511228 ns/op
|
||||
```
|
||||
|
||||
Here, the benchmark took about 1 second, and `foo` was executed 73 times, for an average execution time of 16,511,228 nanoseconds. We can change the benchmark time using `-benchtime`:
|
||||
|
||||
```
|
||||
$ go test -bench=. -benchtime=2s
|
||||
BenchmarkFoo-4 150 15832169 ns/op
|
||||
```
|
||||
|
||||
`foo` was executed roughly twice more than during the previous benchmark.
|
||||
|
||||
Next, let’s look at some common traps.
|
||||
|
||||
## Not resetting or pausing the timer
|
||||
|
||||
In some cases, we need to perform operations before the benchmark loop. These operations may take quite a while (for example, generating a large slice of data) and may significantly impact the benchmark results:
|
||||
|
||||
```go
|
||||
func BenchmarkFoo(b *testing.B) {
|
||||
expensiveSetup()
|
||||
for i := 0; i < b.N; i++ {
|
||||
functionUnderTest()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this case, we can use the `ResetTimer` method before entering the loop:
|
||||
|
||||
```go
|
||||
func BenchmarkFoo(b *testing.B) {
|
||||
expensiveSetup()
|
||||
b.ResetTimer() // Reset the benchmark timer
|
||||
for i := 0; i < b.N; i++ {
|
||||
functionUnderTest()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Calling `ResetTimer` zeroes the elapsed benchmark time and memory allocation counters since the beginning of the test. This way, an expensive setup can be discarded from the test results.
|
||||
|
||||
What if we have to perform an expensive setup not just once but within each loop iteration?
|
||||
|
||||
```go
|
||||
func BenchmarkFoo(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
expensiveSetup()
|
||||
functionUnderTest()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can’t reset the timer, because that would be executed during each loop iteration. But we can stop and resume the benchmark timer, surrounding the call to `expensiveSetup`:
|
||||
|
||||
```go
|
||||
func BenchmarkFoo(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer() // Pause the benchmark timer
|
||||
expensiveSetup()
|
||||
b.StartTimer() // Resume the benchmark timer
|
||||
functionUnderTest()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Here, we pause the benchmark timer to perform the expensive setup and then resume the timer.
|
||||
|
||||
???+ note
|
||||
|
||||
There’s one catch to remember about this approach: if the function under test is too fast to execute compared to the setup function, the benchmark may take too long to complete. The reason is that it would take much longer than 1 second to reach `benchtime`. Calculating the benchmark time is based solely on the execution time of `functionUnderTest`. So, if we wait a significant time in each loop iteration, the benchmark will be much slower than 1 second. If we want to keep the benchmark, one possible mitigation is to decrease `benchtime`.
|
||||
|
||||
We must be sure to use the timer methods to preserve the accuracy of a benchmark.
|
||||
|
||||
## Making wrong assumptions about micro-benchmarks
|
||||
|
||||
A micro-benchmark measures a tiny computation unit, and it can be extremely easy to make wrong assumptions about it. Let’s say, for example, that we aren’t sure whether to use `atomic.StoreInt32` or `atomic.StoreInt64` (assuming that the values we handle will always fit in 32 bits). We want to write a benchmark to compare both functions:
|
||||
|
||||
```go
|
||||
func BenchmarkAtomicStoreInt32(b *testing.B) {
|
||||
var v int32
|
||||
for i := 0; i < b.N; i++ {
|
||||
atomic.StoreInt32(&v, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAtomicStoreInt64(b *testing.B) {
|
||||
var v int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
atomic.StoreInt64(&v, 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we run this benchmark, here’s some example output:
|
||||
|
||||
```
|
||||
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
|
||||
BenchmarkAtomicStoreInt32
|
||||
BenchmarkAtomicStoreInt32-4 197107742 5.682 ns/op
|
||||
BenchmarkAtomicStoreInt64
|
||||
BenchmarkAtomicStoreInt64-4 213917528 5.134 ns/op
|
||||
```
|
||||
|
||||
We could easily take this benchmark for granted and decide to use `atomic.StoreInt64` because it appears to be faster. Now, for the sake of doing a fair benchmark, we reverse the order and test `atomic.StoreInt64` first, followed by `atomic.StoreInt32`. Here is some example output:
|
||||
|
||||
```
|
||||
BenchmarkAtomicStoreInt64
|
||||
BenchmarkAtomicStoreInt64-4 224900722 5.434 ns/op
|
||||
BenchmarkAtomicStoreInt32
|
||||
BenchmarkAtomicStoreInt32-4 230253900 5.159 ns/op
|
||||
```
|
||||
|
||||
This time, `atomic.StoreInt32` has better results. What happened?
|
||||
|
||||
In the case of micro-benchmarks, many factors can impact the results, such as machine activity while running the benchmarks, power management, thermal scaling, and better cache alignment of a sequence of instructions. We must remember that many factors, even outside the scope of our Go project, can impact the results.
|
||||
|
||||
???+ note
|
||||
|
||||
We should make sure the machine executing the benchmark is idle. However, external processes may run in the background, which may affect benchmark results. For that reason, tools such as `perflock` can limit how much CPU a benchmark can consume. For example, we can run a benchmark with 70% of the total available CPU, giving 30% to the OS and other processes and reducing the impact of the machine activity factor on the results.
|
||||
|
||||
One option is to increase the benchmark time using the `-benchtime` option. Similar to the law of large numbers in probability theory, if we run a benchmark a large number of times, it should tend to approach its expected value (assuming we omit the benefits of instructions caching and similar mechanics).
|
||||
|
||||
Another option is to use external tools on top of the classic benchmark tooling. For instance, the `benchstat` tool, which is part of the `golang.org/x` repository, allows us to compute and compare statistics about benchmark executions.
|
||||
|
||||
Let’s run the benchmark 10 times using the `-count` option and pipe the output to a specific file:
|
||||
|
||||
```
|
||||
$ go test -bench=. -count=10 | tee stats.txt
|
||||
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
|
||||
BenchmarkAtomicStoreInt32-4 234935682 5.124 ns/op
|
||||
BenchmarkAtomicStoreInt32-4 235307204 5.112 ns/op
|
||||
// ...
|
||||
BenchmarkAtomicStoreInt64-4 235548591 5.107 ns/op
|
||||
BenchmarkAtomicStoreInt64-4 235210292 5.090 ns/op
|
||||
// ...
|
||||
```
|
||||
|
||||
We can then run `benchstat` on this file:
|
||||
|
||||
```
|
||||
$ benchstat stats.txt
|
||||
name time/op
|
||||
AtomicStoreInt32-4 5.10ns ± 1%
|
||||
AtomicStoreInt64-4 5.10ns ± 1%
|
||||
```
|
||||
|
||||
The results are the same: both functions take on average 5.10 nanoseconds to complete. We also see the percent variation between the executions of a given benchmark: ± 1%. This metric tells us that both benchmarks are stable, giving us more confidence in the computed average results. Therefore, instead of concluding that `atomic.StoreInt32` is faster or slower, we can conclude that its execution time is similar to that of `atomic.StoreInt64` for the usage we tested (in a specific Go version on a particular machine).
|
||||
|
||||
In general, we should be cautious about micro-benchmarks. Many factors can significantly impact the results and potentially lead to wrong assumptions. Increasing the benchmark time or repeating the benchmark executions and computing stats with tools such as `benchstat` can be an efficient way to limit external factors and get more accurate results, leading to better conclusions.
|
||||
|
||||
Let’s also highlight that we should be careful about using the results of a micro-benchmark executed on a given machine if another system ends up running the application. The production system may act quite differently from the one on which we ran the micro-benchmark.
|
||||
|
||||
## Not being careful about compiler optimizations
|
||||
|
||||
Another common mistake related to writing benchmarks is being fooled by compiler optimizations, which can also lead to wrong benchmark assumptions. In this section, we look at Go issue 14813 (https://github.com/golang/go/issues/14813, also discussed by Go project member Dave Cheney) with a population count function (a function that counts the number of bits set to 1):
|
||||
|
||||
```go
|
||||
const m1 = 0x5555555555555555
|
||||
const m2 = 0x3333333333333333
|
||||
const m4 = 0x0f0f0f0f0f0f0f0f
|
||||
const h01 = 0x0101010101010101
|
||||
|
||||
func popcnt(x uint64) uint64 {
|
||||
x -= (x >> 1) & m1
|
||||
x = (x & m2) + ((x >> 2) & m2)
|
||||
x = (x + (x >> 4)) & m4
|
||||
return (x * h01) >> 56
|
||||
}
|
||||
```
|
||||
|
||||
This function takes and returns a `uint64`. To benchmark this function, we can write the following:
|
||||
|
||||
```go
|
||||
func BenchmarkPopcnt1(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
popcnt(uint64(i))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
However, if we execute this benchmark, we get a surprisingly low result:
|
||||
|
||||
```
|
||||
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
|
||||
BenchmarkPopcnt1-4 1000000000 0.2858 ns/op
|
||||
```
|
||||
|
||||
A duration of 0.28 nanoseconds is roughly one clock cycle, so this number is unreasonably low. The problem is that the developer wasn’t careful enough about compiler optimizations. In this case, the function under test is simple enough to be a candidate for inlining: an optimization that replaces a function call with the body of the called function and lets us prevent a function call, which has a small footprint. Once the function is inlined, the compiler notices that the call has no side effects and replaces it with the following benchmark:
|
||||
|
||||
```go
|
||||
func BenchmarkPopcnt1(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Empty
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The benchmark is now empty — which is why we got a result close to one clock cycle. To prevent this from happening, a best practice is to follow this pattern:
|
||||
|
||||
1. During each loop iteration, assign the result to a local variable (local in the context of the benchmark function).
|
||||
2. Assign the latest result to a global variable.
|
||||
|
||||
In our case, we write the following benchmark:
|
||||
|
||||
```go
|
||||
var global uint64 // Define a global variable
|
||||
|
||||
func BenchmarkPopcnt2(b *testing.B) {
|
||||
var v uint64 // Define a local variable
|
||||
for i := 0; i < b.N; i++ {
|
||||
v = popcnt(uint64(i)) // Assign the result to the local variable
|
||||
}
|
||||
global = v // Assign the result to the global variable
|
||||
}
|
||||
```
|
||||
|
||||
`global` is a global variable, whereas v is a local variable whose scope is the benchmark function. During each loop iteration, we assign the result of `popcnt` to the local variable. Then we assign the latest result to the global variable.
|
||||
|
||||
???+ note
|
||||
|
||||
Why not assign the result of the popcnt call directly to global to simplify the test? Writing to a global variable is slower than writing to a local variable (these concepts are discussed in 100 Go Mistakes, mistake #95: “[Not understanding stack vs. heap](https://100go.co#not-understanding-stack-vs-heap-95)”). Therefore, we should write each result to a local variable to limit the footprint during each loop iteration.
|
||||
|
||||
If we run these two benchmarks, we now get a significant difference in the results:
|
||||
|
||||
```
|
||||
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
|
||||
BenchmarkPopcnt1-4 1000000000 0.2858 ns/op
|
||||
BenchmarkPopcnt2-4 606402058 1.993 ns/op
|
||||
```
|
||||
|
||||
`BenchmarkPopcnt2` is the accurate version of the benchmark. It guarantees that we avoid the inlining optimizations, which can artificially lower the execution time or even remove the call to the function under test. Relying on the results of `BenchmarkPopcnt1` could have led to wrong assumptions.
|
||||
|
||||
Let’s remember the pattern to avoid compiler optimizations fooling benchmark results: assign the result of the function under test to a local variable, and then assign the latest result to a global variable. This best practice also prevents us from making incorrect assumptions.
|
||||
|
||||
## Being fooled by the observer effect
|
||||
|
||||
In physics, the observer effect is the disturbance of an observed system by the act of observation. This effect can also be seen in benchmarks and can lead to wrong assumptions about results. Let’s look at a concrete example and then try to mitigate it.
|
||||
|
||||
We want to implement a function receiving a matrix of `int64` elements. This matrix has a fixed number of 512 columns, and we want to compute the total sum of the first eight columns, as shown in figure 1.
|
||||
|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 1: Computing the sum of the first eight columns.</figcaption>
|
||||
</figure>
|
||||
|
||||
For the sake of optimizations, we also want to determine whether varying the number of columns has an impact, so we also implement a second function with 513 columns. The implementation is the following:
|
||||
|
||||
```go
|
||||
func calculateSum512(s [][512]int64) int64 {
|
||||
var sum int64
|
||||
for i := 0; i < len(s); i++ { // Iterate over each row
|
||||
for j := 0; j < 8; j++ { // Iterate over the first eight columns
|
||||
sum += s[i][j] // Increment sum
|
||||
}
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func calculateSum513(s [][513]int64) int64 {
|
||||
// Same implementation as calculateSum512
|
||||
}
|
||||
```
|
||||
|
||||
We iterate over each row and then over the first eight columns, and we increment a sum variable that we return. The implementation in `calculateSum513` remains the same.
|
||||
|
||||
We want to benchmark these functions to decide which one is the most performant given a fixed number of rows:
|
||||
|
||||
```go
|
||||
const rows = 1000
|
||||
|
||||
var res int64
|
||||
|
||||
func BenchmarkCalculateSum512(b *testing.B) {
|
||||
var sum int64
|
||||
s := createMatrix512(rows) // Create a matrix of 512 columns
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sum = calculateSum512(s) // Create a matrix of 512 columns
|
||||
}
|
||||
res = sum
|
||||
}
|
||||
|
||||
func BenchmarkCalculateSum513(b *testing.B) {
|
||||
var sum int64
|
||||
s := createMatrix513(rows) // Create a matrix of 513 columns
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sum = calculateSum513(s) // Calculate the sum
|
||||
}
|
||||
res = sum
|
||||
}
|
||||
```
|
||||
|
||||
We want to create the matrix only once, to limit the footprint on the results. Therefore, we call `createMatrix512` and `createMatrix513` outside of the loop. We may expect the results to be similar as again we only want to iterate on the first eight columns, but this isn’t the case (on my machine):
|
||||
|
||||
```
|
||||
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
|
||||
BenchmarkCalculateSum512-4 81854 15073 ns/op
|
||||
BenchmarkCalculateSum513-4 161479 7358 ns/op
|
||||
```
|
||||
|
||||
The second benchmark with 513 columns is about 50% faster. Again, because we iterate only over the first eight columns, this result is quite surprising.
|
||||
|
||||
To understand this difference, we need to understand the basics of CPU caches. In a nutshell, a CPU is composed of different caches (usually L1, L2, and L3). These caches reduce the average cost of accessing data from the main memory. In some conditions, the CPU can fetch data from the main memory and copy it to L1. In this case, the CPU tries to fetch into L1 the matrix’s subset that `calculateSum` is interested in (the first eight columns of each row). However, the matrix fits in memory in one case (513 columns) but not in the other case (512 columns).
|
||||
|
||||
???+ note
|
||||
|
||||
This isn’t in the scope of this post to explain why, but we look at this problem in 100 Go Mistakes, mistake #91: “[Not understanding CPU caches.](https://100go.co#not-understanding-cpu-caches-91)”
|
||||
|
||||
Coming back to the benchmark, the main issue is that we keep reusing the same matrix in both cases. Because the function is repeated thousands of times, we don’t measure the function’s execution when it receives a plain new matrix. Instead, we measure a function that gets a matrix that already has a subset of the cells present in the cache. Therefore, because `calculateSum513` leads to fewer cache misses, it has a better execution time.
|
||||
|
||||
This is an example of the observer effect. Because we keep observing a repeatedly called CPU-bound function, CPU caching may come into play and significantly affect the results. In this example, to prevent this effect, we should create a matrix during each test instead of reusing one:
|
||||
|
||||
```go
|
||||
func BenchmarkCalculateSum512(b *testing.B) {
|
||||
var sum int64
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
s := createMatrix512(rows) // Create a new matrix during each loop iteration
|
||||
b.StartTimer()
|
||||
sum = calculateSum512(s)
|
||||
}
|
||||
res = sum
|
||||
}
|
||||
```
|
||||
|
||||
A new matrix is now created during each loop iteration. If we run the benchmark again (and adjust `benchtime` — otherwise, it takes too long to execute), the results are closer to each other:
|
||||
|
||||
```
|
||||
cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
|
||||
BenchmarkCalculateSum512-4 1116 33547 ns/op
|
||||
BenchmarkCalculateSum513-4 998 35507 ns/op
|
||||
```
|
||||
|
||||
Instead of making the incorrect assumption that calculateSum513 is faster, we see that both benchmarks lead to similar results when receiving a new matrix.
|
||||
|
||||
As we have seen in this post, because we were reusing the same matrix, CPU caches significantly impacted the results. To prevent this, we had to create a new matrix during each loop iteration. In general, we should remember that observing a function under test may lead to significant differences in results, especially in the context of micro-benchmarks of CPU-bound functions where low-level optimizations matter. Forcing a benchmark to re-create data during each iteration can be a good way to prevent this effect.
|
||||
|
|
@ -67,11 +67,17 @@ $ go tool pprof -http=:8080 <file>
|
|||
|
||||
This command opens a web UI showing the call graph. The next figure shows an example taken from an application. The larger the arrow, the more it was a hot path. We can then navigate into this graph and get execution insights.
|
||||
|
||||

|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 1: The call graph of an application during 30 seconds.</figcaption>
|
||||
</figure>
|
||||
|
||||
For example, the graph in the next figure tells us that during 30 seconds, 0.06 seconds were spent in the `decode` method (`*FetchResponse` receiver). Of these 0.06 seconds, 0.02 were spent in `RecordBatch.decode` and 0.01 in `makemap` (creating a map).
|
||||
|
||||

|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 2: Example call graph.</figcaption>
|
||||
</figure>
|
||||
|
||||
We can also access this kind of information from the web UI with different representations. For example, the Top view sorts the functions per execution time, and Flame Graph visualizes the execution time hierarchy. The UI can even display the expensive parts of the source code line by line.
|
||||
|
||||
|
|
@ -99,7 +105,10 @@ If we reach /debug/pprof/heap/, we get raw data that can be hard to read. Howeve
|
|||
|
||||
The next figure shows an example of a heap graph. Calling the `MetadataResponse.decode` method leads to allocating 1536 KB of heap data (which represents 6.32% of the total heap). However, 0 out of these 1536 KB were allocated by this function directly, so we need to inspect the second call. The `TopicMetadata.decode` method allocated 512 KB out of the 1536 KB; the rest — 1024 KB — were allocated in another method.
|
||||
|
||||

|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 3: A heap graph.</figcaption>
|
||||
</figure>
|
||||
|
||||
This is how we can navigate the call chain to understand what part of an application is responsible for most of the heap allocations. We can also look at different sample types:
|
||||
|
||||
|
|
@ -132,7 +141,10 @@ $ go tool pprof -http=:8080 -diff_base <file2> <file1>
|
|||
|
||||
The next figure shows the kind of data we can access. For example, the amount of heap memory held by the newTopicProducer method (top left) has decreased (–513 KB). In contrast, the amount held by updateMetadata (bottom right) has increased (+512 KB). Slow increases are normal. The second heap profile may have been calculated in the middle of a service call, for example. We can repeat this process or wait longer; the important part is to track steady increases in allocations of a specific object.
|
||||
|
||||

|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 4: The differences between the two heap profiles.</figcaption>
|
||||
</figure>
|
||||
|
||||
???+ note
|
||||
|
||||
|
|
@ -142,7 +154,10 @@ The next figure shows the kind of data we can access. For example, the amount of
|
|||
|
||||
The `goroutine` profile reports the stack trace of all the current goroutines in an application. We can download a file using /debug/pprof/goroutine/?debug=0 and use go tool again. The next figure shows the kind of information we can get.
|
||||
|
||||

|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 5: Goroutine graph.</figcaption>
|
||||
</figure>
|
||||
|
||||
We can see the current state of the application and how many goroutines were created per function. In this case, `withRecover` has created 296 ongoing goroutines (63%), and 29 were related to a call to `responseFeeder`.
|
||||
|
||||
|
|
@ -222,19 +237,31 @@ $ go tool trace trace.out
|
|||
The web browser opens, and we can click View Trace to see all the traces during a specific timeframe, as shown in the next figure. This figure represents about 150 ms. We can see multiple helpful metrics, such as the goroutine count and the heap size. The heap size grows steadily until a GC is triggered. We can also observe the activity of the Go application per CPU core. The timeframe starts with user-level code; then a “stop the
|
||||
world” is executed, which occupies the four CPU cores for approximately 40 ms.
|
||||
|
||||

|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 6: Showing goroutine activity and runtime events such as a GC phase.</figcaption>
|
||||
</figure>
|
||||
|
||||
Regarding concurrency, we can see that this version uses all the available CPU cores on the machine. However, the next figure zooms in on a portion of 1 ms. Each bar corresponds to a single goroutine execution. Having too many small bars doesn’t look right: it means execution that is poorly parallelized.
|
||||
|
||||

|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 7: Too many small bars mean poorly parallelized execution.</figcaption>
|
||||
</figure>
|
||||
|
||||
The next figure zooms even closer to see how these goroutines are orchestrated. Roughly 50% of the CPU time isn’t spent executing application code. The white spaces represent the time the Go runtime takes to spin up and orchestrate new goroutines.
|
||||
|
||||

|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 8: About 50% of CPU time is spent handling goroutine switches.</figcaption>
|
||||
</figure>
|
||||
|
||||
Let’s compare this with the second parallel implementation, which was about an order of magnitude faster. The next figure again zooms to a 1 ms timeframe.
|
||||
|
||||

|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 9: The number of white spaces has been significantly reduced, proving that the CPU is more fully occupied.</figcaption>
|
||||
</figure>
|
||||
|
||||
Each goroutine takes more time to execute, and the number of white spaces has been significantly reduced. Hence, the CPU is much more occupied executing application code than it was in the first version. Each millisecond of CPU time is spent more efficiently, explaining the benchmark differences.
|
||||
|
||||
|
|
@ -261,7 +288,12 @@ fibStore.End()
|
|||
|
||||
Using `go tool`, we can get more precise information about how these two tasks perform. In the previous trace UI, we can see the boundaries for each task per goroutine. In User-Defined Tasks, we can follow the duration distribution:
|
||||
|
||||

|
||||
<figure markdown>
|
||||

|
||||
<figcaption>Figure 10: Distribution of user-level tasks.</figcaption>
|
||||
</figure>
|
||||
|
||||
|
||||
|
||||
We see that in most cases, the `fibonacci` task is executed in less than 15 microseconds, whereas the `store` task takes less than 6309 nanoseconds.
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ This book aims to help accelerate our journey toward proficiency by delving into
|
|||
|
||||
Why should we read a book about common Go mistakes? Why not deepen our knowledge with an ordinary book that would dig into different topics?
|
||||
|
||||
In a 2011 article, neuroscientists proved that the best time for brain growth is when we’re facing mistakes. Haven’t we all experienced the process of learning from a mistake and recalling that occasion after months or even years, when some context related to it? As presented in another article, by Janet Metcalfe, this happens because mistakes have a facilitative effect. The main idea is that we can remember not only the error but also the context surrounding the mistake. This is one of the reasons why learning from mistakes is so efficient.
|
||||
In a 2011 article, neuroscientists proved that the best time for brain growth is when we’re facing mistakes. [^1] Haven’t we all experienced the process of learning from a mistake and recalling that occasion after months or even years, when some context related to it? As presented in another article, by Janet Metcalfe, this happens because mistakes have a facilitative effect. [^2] The main idea is that we can remember not only the error but also the context surrounding the mistake. This is one of the reasons why learning from mistakes is so efficient.
|
||||
|
||||
To strengthen this facilitative effect, this book accompanies each mistake as much as possible with real-world examples. This book isn’t only about theory; it also helps us get better at avoiding mistakes and making more well-informed, conscious decisions because we now understand the rationale behind them.
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ We introduce each mistake category next.
|
|||
|
||||
### Bugs
|
||||
|
||||
The first type of mistake and probably the most obvious is software bugs. In 2020, a study conducted by Synopsys estimated the cost of software bugs in the U.S. alone to be over $2 trillion.
|
||||
The first type of mistake and probably the most obvious is software bugs. In 2020, a study conducted by Synopsys estimated the cost of software bugs in the U.S. alone to be over $2 trillion. [^3]
|
||||
|
||||
Furthermore, bugs can also lead to tragic impacts. We can, for example, mention cases such as Therac-25, a radiation therapy machine produced by Atomic Energy of Canada Limited (AECL). Because of a race condition, the machine gave its patients radiation doses that were hundreds of times greater than expected, leading to the death of three patients. Hence, software bugs aren’t only about money. As developers, we should remember how impactful our jobs are.
|
||||
|
||||
|
|
@ -130,3 +130,7 @@ In this book, we will cover many cases and concrete examples that will help us t
|
|||
* Go is a modern programming language that enables developer productivity, which is crucial for most companies today.
|
||||
* Go is simple to learn but not easy to master. This is why we need to deepen our knowledge to make the most effective use of the language.
|
||||
* Learning via mistakes and concrete examples is a powerful way to be proficient in a language. This book will accelerate our path to proficiency by exploring 100 common mistakes.
|
||||
|
||||
[^1]: J. S. Moser, H. S. Schroder, et al., “Mind Your Errors: Evidence for a Neural Mechanism Linking Growth Mindset to Adaptive Posterror Adjustments,” Psychological Science, vol. 22, no. 12, pp. 1484–1489, Dec. 2011.
|
||||
[^2]: J. Metcalfe, “Learning from Errors,” Annual Review of Psychology, vol. 68, pp. 465–489, Jan. 2017.
|
||||
[^3]: Synopsys, “The Cost of Poor Software Quality in the US: A 2020 Report.” 2020. [https://news.synopsys.com/2021-01-06-Synopsys-Sponsored-CISQ-Research-Estimates-Cost-of-Poor-Software-Quality-in-the-US-2-08-Trillion-in-2020](https://news.synopsys.com/2021-01-06-Synopsys-Sponsored-CISQ-Research-Estimates-Cost-of-Poor-Software-Quality-in-the-US-2-08-Trillion-in-2020).
|
||||
|
|
|
|||
BIN
docs/img/map-leak-1.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
docs/img/map-leak-2.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/img/matrix.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
|
|
@ -169,8 +169,6 @@ The `any` type can be helpful if there is a genuine need for accepting or return
|
|||
|
||||
Read the full section [here](9-generics.md).
|
||||
|
||||
<!-- TODO Include 9-generics.md file -->
|
||||
|
||||
[Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/9-generics/main.go)
|
||||
|
||||
### Not being aware of the possible problems with type embedding (#10)
|
||||
|
|
@ -479,7 +477,7 @@ When using slicing, we must remember that we can face a situation leading to uni
|
|||
|
||||
[Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/25-slice-append/main.go)
|
||||
|
||||
### Slice and memory leaks (#26)
|
||||
### Slices and memory leaks (#26)
|
||||
|
||||
???+ info "TL;DR"
|
||||
|
||||
|
|
@ -509,13 +507,13 @@ If we know up front the number of elements a map will contain, we should create
|
|||
|
||||
[Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/27-map-init/main_test.go)
|
||||
|
||||
### [Map and memory leaks](https://teivah.medium.com/maps-and-memory-leaks-in-go-a85ebe6e7e69) (#28)
|
||||
### Maps and memory leaks (#28)
|
||||
|
||||
???+ info "TL;DR"
|
||||
|
||||
A map can always grow in memory, but it never shrinks. Hence, if it leads to some memory issues, you can try different options, such as forcing Go to recreate the map or using pointers.
|
||||
|
||||
<!-- TODO -->
|
||||
Read the full section [here](28-maps-memory-leaks.md).
|
||||
|
||||
[Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/28-map-memory-leak/main.go)
|
||||
|
||||
|
|
@ -1570,33 +1568,21 @@ You should have a good reason to specify a channel size other than one for buffe
|
|||
|
||||
[Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/88-utility-package/iotest/main_test.go)
|
||||
|
||||
### [Writing inaccurate benchmarks](https://teivah.medium.com/how-to-write-accurate-benchmarks-in-go-4266d7dd1a95) (#89)
|
||||
### Writing inaccurate benchmarks (#89)
|
||||
|
||||
* Not resetting or pausing the timer
|
||||
???+ info "TL;DR"
|
||||
|
||||
Use time methods to preserve the accuracy of a benchmark.
|
||||
Regarding benchmarks:
|
||||
|
||||
[Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/timer/main_test.go)
|
||||
* Use time methods to preserve the accuracy of a benchmark.
|
||||
* Increasing benchtime or using tools such as benchstat can be helpful when dealing with micro-benchmarks.
|
||||
* Be careful with the results of a micro-benchmark if the system that ends up running the application is different from the one running the micro-bench- mark.
|
||||
* Make sure the function under test leads to a side effect, to prevent compiler optimizations from fooling you about the benchmark results.
|
||||
* To prevent the observer effect, force a benchmark to re-create the data used by a CPU-bound function.
|
||||
|
||||
* Making wrong assumptions about micro-benchmarks
|
||||
Read the full section [here](89-benchmarks.md).
|
||||
|
||||
Increasing `benchtime` or using tools such as `benchstat` can be helpful when dealing with micro-benchmarks.
|
||||
|
||||
Be careful with the results of a micro-benchmark if the system that ends up running the application is different from the one running the micro-benchmark.
|
||||
|
||||
[Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/wrong-assumptions/main_test.go)
|
||||
|
||||
* Not being careful about compiler optimizations
|
||||
|
||||
Make sure the function under test leads to a side effect, to prevent compiler optimizations from fooling you about the benchmark results.
|
||||
|
||||
[Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/compiler-optimizations/main_test.go)
|
||||
|
||||
* Being fooled by the observer effect
|
||||
|
||||
To prevent the observer effect, force a benchmark to re-create the data used by a CPU-bound function.
|
||||
|
||||
[Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/observer-effect/main_test.go)
|
||||
[Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/)
|
||||
|
||||
### Not exploring all the Go testing features (#90)
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ theme:
|
|||
- search.highlight
|
||||
- search.share
|
||||
- search.suggest
|
||||
- content.code.copy
|
||||
palette:
|
||||
# Palette toggle for light mode
|
||||
- scheme: default
|
||||
|
|
@ -31,6 +32,7 @@ theme:
|
|||
repo_url: https://github.com/teivah/100-go-mistakes
|
||||
plugins:
|
||||
- search
|
||||
- glightbox
|
||||
- social:
|
||||
cards_layout_options:
|
||||
logo: img/cover.png
|
||||
|
|
@ -59,6 +61,8 @@ nav:
|
|||
- index.md
|
||||
- Full Sections:
|
||||
- 9-generics.md
|
||||
- 28-maps-memory-leaks.md
|
||||
- 89-benchmarks.md
|
||||
- 98-profiling-execution-tracing.md
|
||||
- zh.md
|
||||
- ❤️ Go Jobs:
|
||||
|
|
@ -73,4 +77,8 @@ markdown_extensions:
|
|||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- pymdownx.superfences
|
||||
- tables
|
||||
- attr_list
|
||||
- md_in_html
|
||||
- footnotes
|
||||
copyright: Copyright © 2022 - 2023 Teiva Harsanyi
|
||||
|
|
|
|||
984
site/28-maps-memory-leaks/index.html
Normal file
|
|
@ -0,0 +1,984 @@
|
|||
|
||||
<!doctype html>
|
||||
<html lang="en" class="no-js">
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
|
||||
|
||||
<link rel="canonical" href="https://100go.co/28-maps-memory-leaks/">
|
||||
|
||||
|
||||
<link rel="prev" href="../9-generics/">
|
||||
|
||||
|
||||
<link rel="next" href="../89-benchmarks/">
|
||||
|
||||
|
||||
<link rel="icon" href="../img/Go-Logo_LightBlue.svg">
|
||||
<meta name="generator" content="mkdocs-1.5.2, mkdocs-material-9.3.1">
|
||||
|
||||
|
||||
<title>100 Go Mistakes</title>
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../assets/stylesheets/main.046329b4.min.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../assets/stylesheets/palette.85d0ee34.min.css">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
|
||||
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
|
||||
|
||||
|
||||
|
||||
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script id="__analytics">function __md_analytics(){function n(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],n("js",new Date),n("config","G-HMY1HYDM93"),document.addEventListener("DOMContentLoaded",function(){document.forms.search&&document.forms.search.query.addEventListener("blur",function(){this.value&&n("event","search",{search_term:this.value})}),document$.subscribe(function(){var a=document.forms.feedback;if(void 0!==a)for(var e of a.querySelectorAll("[type=submit]"))e.addEventListener("click",function(e){e.preventDefault();var t=document.location.pathname,e=this.getAttribute("data-md-value");n("event","feedback",{page:t,data:e}),a.firstElementChild.disabled=!0;e=a.querySelector(".md-feedback__note [data-md-value='"+e+"']");e&&(e.hidden=!1)}),a.hidden=!1}),location$.subscribe(function(e){n("config","G-HMY1HYDM93",{page_path:e.pathname})})});var e=document.createElement("script");e.async=!0,e.src="https://www.googletagmanager.com/gtag/js?id=G-HMY1HYDM93",document.getElementById("__analytics").insertAdjacentElement("afterEnd",e)}</script>
|
||||
|
||||
<script>"undefined"!=typeof __md_analytics&&__md_analytics()</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<meta property="og:type" content="website" >
|
||||
|
||||
<meta property="og:title" content="Maps and memory leaks (#28) - 100 Go Mistakes and How to Avoid Them" >
|
||||
|
||||
<meta property="og:description" content="None" >
|
||||
|
||||
<meta property="og:image" content="https://100go.co/assets/images/social/28-maps-memory-leaks.png" >
|
||||
|
||||
<meta property="og:image:type" content="image/png" >
|
||||
|
||||
<meta property="og:image:width" content="1200" >
|
||||
|
||||
<meta property="og:image:height" content="630" >
|
||||
|
||||
<meta property="og:url" content="https://100go.co/28-maps-memory-leaks/" >
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" >
|
||||
|
||||
<meta name="twitter:title" content="Maps and memory leaks (#28) - 100 Go Mistakes and How to Avoid Them" >
|
||||
|
||||
<meta name="twitter:description" content="None" >
|
||||
|
||||
<meta name="twitter:image" content="https://100go.co/assets/images/social/28-maps-memory-leaks.png" >
|
||||
|
||||
|
||||
|
||||
<link href="../assets/stylesheets/glightbox.min.css" rel="stylesheet"/><style>
|
||||
html.glightbox-open { overflow: initial; height: 100%; }
|
||||
.gslide-title { margin-top: 0px; user-select: text; }
|
||||
.gslide-desc { color: #666; user-select: text; }
|
||||
.gslide-image img { background: white; }
|
||||
|
||||
.gscrollbar-fixer { padding-right: 15px; }
|
||||
.gdesc-inner { font-size: 0.75rem; }
|
||||
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color);}
|
||||
</style> <script src="../assets/javascripts/glightbox.min.js"></script></head>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<body dir="ltr" data-md-color-scheme="default" data-md-color-primary="cyan" data-md-color-accent="indigo">
|
||||
|
||||
|
||||
|
||||
<script>var palette=__md_get("__palette");if(palette&&"object"==typeof palette.color)for(var key of Object.keys(palette.color))document.body.setAttribute("data-md-color-"+key,palette.color[key])</script>
|
||||
|
||||
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
|
||||
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
|
||||
<label class="md-overlay" for="__drawer"></label>
|
||||
<div data-md-component="skip">
|
||||
|
||||
|
||||
<a href="#maps-and-memory-leaks" class="md-skip">
|
||||
Skip to content
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div data-md-component="announce">
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<header class="md-header md-header--shadow md-header--lifted" data-md-component="header">
|
||||
<nav class="md-header__inner md-grid" aria-label="Header">
|
||||
<a href=".." title="100 Go Mistakes and How to Avoid Them" class="md-header__button md-logo" aria-label="100 Go Mistakes and How to Avoid Them" data-md-component="logo">
|
||||
|
||||
<img src="../img/Go-Logo_White.svg" alt="logo">
|
||||
|
||||
</a>
|
||||
<label class="md-header__button md-icon" for="__drawer">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
|
||||
</label>
|
||||
<div class="md-header__title" data-md-component="header-title">
|
||||
<div class="md-header__ellipsis">
|
||||
<div class="md-header__topic">
|
||||
<span class="md-ellipsis">
|
||||
100 Go Mistakes and How to Avoid Them
|
||||
</span>
|
||||
</div>
|
||||
<div class="md-header__topic" data-md-component="header-topic">
|
||||
<span class="md-ellipsis">
|
||||
|
||||
Maps and memory leaks (#28)
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<form class="md-header__option" data-md-component="palette">
|
||||
|
||||
|
||||
|
||||
|
||||
<input class="md-option" data-md-color-media="" data-md-color-scheme="default" data-md-color-primary="cyan" data-md-color-accent="indigo" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
|
||||
|
||||
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_2" hidden>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a4 4 0 0 0-4 4 4 4 0 0 0 4 4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 10a6 6 0 0 1-6-6 6 6 0 0 1 6-6 6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69Z"/></svg>
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<input class="md-option" data-md-color-media="" data-md-color-scheme="slate" data-md-color-primary="blue-grey" data-md-color-accent="indigo" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_2">
|
||||
|
||||
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 18c-.89 0-1.74-.2-2.5-.55C11.56 16.5 13 14.42 13 12c0-2.42-1.44-4.5-3.5-5.45C10.26 6.2 11.11 6 12 6a6 6 0 0 1 6 6 6 6 0 0 1-6 6m8-9.31V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69L23.31 12 20 8.69Z"/></svg>
|
||||
</label>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
<div class="md-header__option">
|
||||
<div class="md-select">
|
||||
|
||||
<button class="md-header__button md-icon" aria-label="Select language">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m12.87 15.07-2.54-2.51.03-.03A17.52 17.52 0 0 0 14.07 6H17V4h-7V2H8v2H1v2h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04M18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12m-2.62 7 1.62-4.33L19.12 17h-3.24Z"/></svg>
|
||||
</button>
|
||||
<div class="md-select__inner">
|
||||
<ul class="md-select__list">
|
||||
|
||||
<li class="md-select__item">
|
||||
<a href="/" hreflang="en" class="md-select__link">
|
||||
English
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="md-select__item">
|
||||
<a href="/zh/" hreflang="zh" class="md-select__link">
|
||||
Chinese (Simplified)
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<label class="md-header__button md-icon" for="__search">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
|
||||
</label>
|
||||
<div class="md-search" data-md-component="search" role="dialog">
|
||||
<label class="md-search__overlay" for="__search"></label>
|
||||
<div class="md-search__inner" role="search">
|
||||
<form class="md-search__form" name="search">
|
||||
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
|
||||
<label class="md-search__icon md-icon" for="__search">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
|
||||
</label>
|
||||
<nav class="md-search__options" aria-label="Search">
|
||||
|
||||
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7 0-.24-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91 1.61 0 2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08Z"/></svg>
|
||||
</a>
|
||||
|
||||
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="md-search__suggest" data-md-component="search-suggest"></div>
|
||||
|
||||
</form>
|
||||
<div class="md-search__output">
|
||||
<div class="md-search__scrollwrap" data-md-scrollfix>
|
||||
<div class="md-search-result" data-md-component="search-result">
|
||||
<div class="md-search-result__meta">
|
||||
Initializing search
|
||||
</div>
|
||||
<ol class="md-search-result__list" role="presentation"></ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="md-header__source">
|
||||
<a href="https://github.com/teivah/100-go-mistakes" title="Go to repository" class="md-source" data-md-component="source">
|
||||
<div class="md-source__icon md-icon">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
|
||||
</div>
|
||||
<div class="md-source__repository">
|
||||
teivah/100-go-mistakes
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
|
||||
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
|
||||
<div class="md-grid">
|
||||
<ul class="md-tabs__list">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item">
|
||||
<a href="../book/" class="md-tabs__link">
|
||||
|
||||
|
||||
Book
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item md-tabs__item--active">
|
||||
<a href=".." class="md-tabs__link">
|
||||
|
||||
|
||||
Go Mistakes
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-tabs__item">
|
||||
<a href="../jobs/" class="md-tabs__link">
|
||||
|
||||
|
||||
❤️ Go Jobs
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
||||
</header>
|
||||
|
||||
<div class="md-container" data-md-component="container">
|
||||
|
||||
|
||||
|
||||
|
||||
<main class="md-main" data-md-component="main">
|
||||
<div class="md-main__inner md-grid">
|
||||
|
||||
|
||||
|
||||
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
|
||||
<div class="md-sidebar__scrollwrap">
|
||||
<div class="md-sidebar__inner">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
|
||||
<label class="md-nav__title" for="__drawer">
|
||||
<a href=".." title="100 Go Mistakes and How to Avoid Them" class="md-nav__button md-logo" aria-label="100 Go Mistakes and How to Avoid Them" data-md-component="logo">
|
||||
|
||||
<img src="../img/Go-Logo_White.svg" alt="logo">
|
||||
|
||||
</a>
|
||||
100 Go Mistakes and How to Avoid Them
|
||||
</label>
|
||||
|
||||
<div class="md-nav__source">
|
||||
<a href="https://github.com/teivah/100-go-mistakes" title="Go to repository" class="md-source" data-md-component="source">
|
||||
<div class="md-source__icon md-icon">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
|
||||
</div>
|
||||
<div class="md-source__repository">
|
||||
teivah/100-go-mistakes
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ul class="md-nav__list" data-md-scrollfix>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--nested">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_1" >
|
||||
|
||||
<label class="md-nav__link" for="__nav_1" id="__nav_1_label" tabindex="0">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Book
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_1_label" aria-expanded="false">
|
||||
<label class="md-nav__title" for="__nav_1">
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
Book
|
||||
</label>
|
||||
<ul class="md-nav__list" data-md-scrollfix>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../book/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
100 Go Mistakes and How to Avoid Them
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../chapter-1/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Read the First Chapter
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../external/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
External Resources
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" checked>
|
||||
|
||||
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Go Mistakes
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="true">
|
||||
<label class="md-nav__title" for="__nav_2">
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
Go Mistakes
|
||||
</label>
|
||||
<ul class="md-nav__list" data-md-scrollfix>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href=".." class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Common Go Mistakes
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--active md-nav__item--nested">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2_2" checked>
|
||||
|
||||
<label class="md-nav__link" for="__nav_2_2" id="__nav_2_2_label" tabindex="0">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Full Sections
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_2_2_label" aria-expanded="true">
|
||||
<label class="md-nav__title" for="__nav_2_2">
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
Full Sections
|
||||
</label>
|
||||
<ul class="md-nav__list" data-md-scrollfix>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../9-generics/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Being confused about when to use generics (#9)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--active">
|
||||
|
||||
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="./" class="md-nav__link md-nav__link--active">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Maps and memory leaks (#28)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Writing inaccurate benchmarks (#89)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../98-profiling-execution-tracing/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Not using Go diagnostics tooling (#98)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../zh/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Chinese (Simplified) Version
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--nested">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_3" >
|
||||
|
||||
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="0">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
❤️ Go Jobs
|
||||
</span>
|
||||
|
||||
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
</label>
|
||||
|
||||
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_3_label" aria-expanded="false">
|
||||
<label class="md-nav__title" for="__nav_3">
|
||||
<span class="md-nav__icon md-icon"></span>
|
||||
❤️ Go Jobs
|
||||
</label>
|
||||
<ul class="md-nav__list" data-md-scrollfix>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../jobs/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Go Jobs
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
|
||||
<div class="md-sidebar__scrollwrap">
|
||||
<div class="md-sidebar__inner">
|
||||
|
||||
|
||||
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="md-content" data-md-component="content">
|
||||
<article class="md-content__inner md-typeset">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<h1 id="maps-and-memory-leaks">Maps and memory leaks</h1>
|
||||
<p>When working with maps in Go, we need to understand some important characteristics of how a map grows and shrinks. Let’s delve into this to prevent issues that can cause memory leaks.</p>
|
||||
<p>First, to view a concrete example of this problem, let’s design a scenario where we will work with the following map:</p>
|
||||
<div class="language-go highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="nx">m</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">int</span><span class="p">][</span><span class="mi">128</span><span class="p">]</span><span class="kt">byte</span><span class="p">)</span>
|
||||
</span></code></pre></div>
|
||||
<p>Each value of m is an array of 128 bytes. We will do the following:</p>
|
||||
<ol>
|
||||
<li>Allocate an empty map.</li>
|
||||
<li>Add 1 million elements.</li>
|
||||
<li>Remove all the elements, and run a Garbage Collection (GC).</li>
|
||||
</ol>
|
||||
<p>After each step, we want to print the size of the heap (using a <code>printAlloc</code> utility function). This shows us how this example behaves memory-wise:</p>
|
||||
<div class="language-go highlight"><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1" href="#__codelineno-1-1"></a><span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2" href="#__codelineno-1-2"></a><span class="w"> </span><span class="nx">n</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">1</span><span class="nx">_000_000</span>
|
||||
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3" href="#__codelineno-1-3"></a><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">int</span><span class="p">][</span><span class="mi">128</span><span class="p">]</span><span class="kt">byte</span><span class="p">)</span>
|
||||
</span><span id="__span-1-4"><a id="__codelineno-1-4" name="__codelineno-1-4" href="#__codelineno-1-4"></a><span class="w"> </span><span class="nx">printAlloc</span><span class="p">()</span>
|
||||
</span><span id="__span-1-5"><a id="__codelineno-1-5" name="__codelineno-1-5" href="#__codelineno-1-5"></a>
|
||||
</span><span id="__span-1-6"><a id="__codelineno-1-6" name="__codelineno-1-6" href="#__codelineno-1-6"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p"><</span><span class="w"> </span><span class="nx">n</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="c1">// Adds 1 million elements</span>
|
||||
</span><span id="__span-1-7"><a id="__codelineno-1-7" name="__codelineno-1-7" href="#__codelineno-1-7"></a><span class="w"> </span><span class="nx">m</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="p">[</span><span class="mi">128</span><span class="p">]</span><span class="kt">byte</span><span class="p">{}</span>
|
||||
</span><span id="__span-1-8"><a id="__codelineno-1-8" name="__codelineno-1-8" href="#__codelineno-1-8"></a><span class="w"> </span><span class="p">}</span>
|
||||
</span><span id="__span-1-9"><a id="__codelineno-1-9" name="__codelineno-1-9" href="#__codelineno-1-9"></a><span class="w"> </span><span class="nx">printAlloc</span><span class="p">()</span>
|
||||
</span><span id="__span-1-10"><a id="__codelineno-1-10" name="__codelineno-1-10" href="#__codelineno-1-10"></a>
|
||||
</span><span id="__span-1-11"><a id="__codelineno-1-11" name="__codelineno-1-11" href="#__codelineno-1-11"></a><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="p"><</span><span class="w"> </span><span class="nx">n</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="c1">// Deletes 1 million elements</span>
|
||||
</span><span id="__span-1-12"><a id="__codelineno-1-12" name="__codelineno-1-12" href="#__codelineno-1-12"></a><span class="w"> </span><span class="nb">delete</span><span class="p">(</span><span class="nx">m</span><span class="p">,</span><span class="w"> </span><span class="nx">i</span><span class="p">)</span>
|
||||
</span><span id="__span-1-13"><a id="__codelineno-1-13" name="__codelineno-1-13" href="#__codelineno-1-13"></a><span class="w"> </span><span class="p">}</span>
|
||||
</span><span id="__span-1-14"><a id="__codelineno-1-14" name="__codelineno-1-14" href="#__codelineno-1-14"></a>
|
||||
</span><span id="__span-1-15"><a id="__codelineno-1-15" name="__codelineno-1-15" href="#__codelineno-1-15"></a><span class="w"> </span><span class="nx">runtime</span><span class="p">.</span><span class="nx">GC</span><span class="p">()</span><span class="w"> </span><span class="c1">// Triggers a manual GC</span>
|
||||
</span><span id="__span-1-16"><a id="__codelineno-1-16" name="__codelineno-1-16" href="#__codelineno-1-16"></a><span class="w"> </span><span class="nx">printAlloc</span><span class="p">()</span>
|
||||
</span><span id="__span-1-17"><a id="__codelineno-1-17" name="__codelineno-1-17" href="#__codelineno-1-17"></a><span class="w"> </span><span class="nx">runtime</span><span class="p">.</span><span class="nx">KeepAlive</span><span class="p">(</span><span class="nx">m</span><span class="p">)</span><span class="w"> </span><span class="c1">// Keeps a reference to m so that the map isn’t collected</span>
|
||||
</span><span id="__span-1-18"><a id="__codelineno-1-18" name="__codelineno-1-18" href="#__codelineno-1-18"></a><span class="p">}</span>
|
||||
</span><span id="__span-1-19"><a id="__codelineno-1-19" name="__codelineno-1-19" href="#__codelineno-1-19"></a>
|
||||
</span><span id="__span-1-20"><a id="__codelineno-1-20" name="__codelineno-1-20" href="#__codelineno-1-20"></a><span class="kd">func</span><span class="w"> </span><span class="nx">printAlloc</span><span class="p">()</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-1-21"><a id="__codelineno-1-21" name="__codelineno-1-21" href="#__codelineno-1-21"></a><span class="w"> </span><span class="kd">var</span><span class="w"> </span><span class="nx">m</span><span class="w"> </span><span class="nx">runtime</span><span class="p">.</span><span class="nx">MemStats</span>
|
||||
</span><span id="__span-1-22"><a id="__codelineno-1-22" name="__codelineno-1-22" href="#__codelineno-1-22"></a><span class="w"> </span><span class="nx">runtime</span><span class="p">.</span><span class="nx">ReadMemStats</span><span class="p">(</span><span class="o">&</span><span class="nx">m</span><span class="p">)</span>
|
||||
</span><span id="__span-1-23"><a id="__codelineno-1-23" name="__codelineno-1-23" href="#__codelineno-1-23"></a><span class="w"> </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%d KB\n"</span><span class="p">,</span><span class="w"> </span><span class="nx">m</span><span class="p">.</span><span class="nx">Alloc</span><span class="o">/</span><span class="mi">1024</span><span class="p">)</span>
|
||||
</span><span id="__span-1-24"><a id="__codelineno-1-24" name="__codelineno-1-24" href="#__codelineno-1-24"></a><span class="p">}</span>
|
||||
</span></code></pre></div>
|
||||
<p>We allocate an empty map, add 1 million elements, remove 1 million elements, and then run a GC. We also make sure to keep a reference to the map using <a href="https://pkg.go.dev/runtime#KeepAlive"><code>runtime.KeepAlive</code></a> so that the map isn’t collected as well. Let’s run this example:</p>
|
||||
<div class="language-text highlight"><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a>0 MB <-- After m is allocated
|
||||
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2" href="#__codelineno-2-2"></a>461 MB <-- After we add 1 million elements
|
||||
</span><span id="__span-2-3"><a id="__codelineno-2-3" name="__codelineno-2-3" href="#__codelineno-2-3"></a>293 MB <-- After we remove 1 million elements
|
||||
</span></code></pre></div>
|
||||
<p>What can we observe? At first, the heap size is minimal. Then it grows significantly after having added 1 million elements to the map. But if we expected the heap size to decrease after removing all the elements, this isn’t how maps work in Go. In the end, even though the GC has collected all the elements, the heap size is still 293 MB. So the memory shrunk, but not as we might have expected. What’s the rationale? We need to delve into how a map works in Go.</p>
|
||||
<p>A map provides an unordered collection of key-value pairs in which all the keys are distinct. In Go, a map is based on the hash table data structure: an array where each element is a pointer to a bucket of key-value pairs, as shown in figure 1.</p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/map-leak-1.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/map-leak-1.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 1: A hash table example with a focus on bucket 0.</figcaption>
|
||||
</figure>
|
||||
<p>Each bucket is a fixed-size array of eight elements. In the case of an insertion into a bucket that is already full (a bucket overflow), Go creates another bucket of eight elements and links the previous one to it. Figure 2 shows an example:</p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/map-leak-2.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/map-leak-2.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 2: In case of a bucket overflow, Go allocates a new bucket and links the previous bucket to it.</figcaption>
|
||||
</figure>
|
||||
<p>Under the hood, a Go map is a pointer to a runtime.hmap struct. This struct contains multiple fields, including a B field, giving the number of buckets in the map:</p>
|
||||
<div class="language-go highlight"><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a><span class="kd">type</span><span class="w"> </span><span class="nx">hmap</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2" href="#__codelineno-3-2"></a><span class="w"> </span><span class="nx">B</span><span class="w"> </span><span class="kt">uint8</span><span class="w"> </span><span class="c1">// log_2 of # of buckets</span>
|
||||
</span><span id="__span-3-3"><a id="__codelineno-3-3" name="__codelineno-3-3" href="#__codelineno-3-3"></a><span class="w"> </span><span class="c1">// (can hold up to loadFactor * 2^B items)</span>
|
||||
</span><span id="__span-3-4"><a id="__codelineno-3-4" name="__codelineno-3-4" href="#__codelineno-3-4"></a><span class="w"> </span><span class="c1">// ...</span>
|
||||
</span><span id="__span-3-5"><a id="__codelineno-3-5" name="__codelineno-3-5" href="#__codelineno-3-5"></a><span class="p">}</span>
|
||||
</span></code></pre></div>
|
||||
<p>After adding 1 million elements, the value of B equals 18, which means 2¹⁸ = 262,144 buckets. When we remove 1 million elements, what’s the value of B? Still 18. Hence, the map still contains the same number of buckets.</p>
|
||||
<p>The reason is that the number of buckets in a map cannot shrink. Therefore, removing elements from a map doesn’t impact the number of existing buckets; it just zeroes the slots in the buckets. A map can only grow and have more buckets; it never shrinks.</p>
|
||||
<p>In the previous example, we went from 461 MB to 293 MB because the elements were collected, but running the GC didn’t impact the map itself. Even the number of extra buckets (the buckets created because of overflows) remains the same.</p>
|
||||
<p>Let’s take a step back and discuss when the fact that a map cannot shrink can be a problem. Imagine building a cache using a <code>map[int][128]byte</code>. This map holds per customer ID (the <code>int</code>), a sequence of 128 bytes. Now, suppose we want to save the last 1,000 customers. The map size will remain constant, so we shouldn’t worry about the fact that a map cannot shrink.</p>
|
||||
<p>However, let’s say we want to store one hour of data. Meanwhile, our company has decided to have a big promotion for Black Friday: in one hour, we may have millions of customers connected to our system. But a few days after Black Friday, our map will contain the same number of buckets as during the peak time. This explains why we can experience high memory consumption that doesn’t significantly decrease in such a scenario.</p>
|
||||
<p>What are the solutions if we don’t want to manually restart our service to clean the amount of memory consumed by the map? One solution could be to re-create a copy of the current map at a regular pace. For example, every hour, we can build a new map, copy all the elements, and release the previous one. The main drawback of this option is that following the copy and until the next garbage collection, we may consume twice the current memory for a short period.</p>
|
||||
<p>Another solution would be to change the map type to store an array pointer: <code>map[int]*[128]byte</code>. It doesn’t solve the fact that we will have a significant number of buckets; however, each bucket entry will reserve the size of a pointer for the value instead of 128 bytes (8 bytes on 64-bit systems and 4 bytes on 32-bit systems).</p>
|
||||
<p>Coming back to the original scenario, let’s compare the memory consumption for each map type following each step. The following table shows the comparison.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Step</th>
|
||||
<th><code>map[int][128]byte</code></th>
|
||||
<th><code>map[int]*[128]byte</code></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Allocate an empty map</td>
|
||||
<td>0 MB</td>
|
||||
<td>0 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Add 1 million elements</td>
|
||||
<td>461 MB</td>
|
||||
<td>182 MB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Remove all the elements and run a GC</td>
|
||||
<td>293 MB</td>
|
||||
<td>38 MB</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<details class="note" open="open">
|
||||
<summary>Note</summary>
|
||||
<p>If a key or a value is over 128 bytes, Go won’t store it directly in the map bucket. Instead, Go stores a pointer to reference the key or the value.</p>
|
||||
</details>
|
||||
<p>As we have seen, adding n elements to a map and then deleting all the elements means keeping the same number of buckets in memory. So, we must remember that because a Go map can only grow in size, so does its memory consumption. There is no automated strategy to shrink it. If this leads to high memory consumption, we can try different options such as forcing Go to re-create the map or using pointers to check if it can be optimized.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<footer class="md-footer">
|
||||
|
||||
<div class="md-footer-meta md-typeset">
|
||||
<div class="md-footer-meta__inner md-grid">
|
||||
<div class="md-copyright">
|
||||
|
||||
<div class="md-copyright__highlight">
|
||||
Copyright © 2022 - 2023 Teiva Harsanyi
|
||||
</div>
|
||||
|
||||
|
||||
Made with
|
||||
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
|
||||
Material for MkDocs
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="md-social">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://twitter.com/teivah" target="_blank" rel="noopener" title="twitter.com" class="md-social__link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"/></svg>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="http://blog.teivah.io" target="_blank" rel="noopener" title="blog.teivah.io" class="md-social__link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2023 Fonticons, Inc.--><path d="M180.5 74.262C80.813 74.262 0 155.633 0 256s80.819 181.738 180.5 181.738S361 356.373 361 256 280.191 74.262 180.5 74.262Zm288.25 10.646c-49.845 0-90.245 76.619-90.245 171.095s40.406 171.1 90.251 171.1 90.251-76.619 90.251-171.1H559c0-94.503-40.4-171.095-90.248-171.095Zm139.506 17.821c-17.526 0-31.735 68.628-31.735 153.274s14.2 153.274 31.735 153.274S640 340.631 640 256c0-84.649-14.215-153.271-31.742-153.271Z"/></svg>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
<div class="md-dialog" data-md-component="dialog">
|
||||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.dff1b7c8.min.js"></script>
|
||||
|
||||
|
||||
<script>document$.subscribe(() => {const lightbox = GLightbox({"touchNavigation": true, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});})</script></body>
|
||||
</html>
|
||||
|
|
@ -553,6 +553,46 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/28-maps-memory-leaks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Maps and memory leaks (#28)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Writing inaccurate benchmarks (#89)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="/98-profiling-execution-tracing/" class="md-nav__link">
|
||||
|
||||
|
|
@ -755,7 +795,7 @@
|
|||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
<script id="__config" type="application/json">{"base": "/", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest"], "search": "/assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
<script id="__config" type="application/json">{"base": "/", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "/assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
|
||||
|
||||
<script src="/assets/javascripts/bundle.dff1b7c8.min.js"></script>
|
||||
|
|
|
|||
1257
site/89-benchmarks/index.html
Normal file
|
|
@ -14,7 +14,7 @@
|
|||
<link rel="prev" href="..">
|
||||
|
||||
|
||||
<link rel="next" href="../98-profiling-execution-tracing/">
|
||||
<link rel="next" href="../28-maps-memory-leaks/">
|
||||
|
||||
|
||||
<link rel="icon" href="../img/Go-Logo_LightBlue.svg">
|
||||
|
|
@ -89,7 +89,18 @@
|
|||
|
||||
|
||||
|
||||
</head>
|
||||
<link href="../assets/stylesheets/glightbox.min.css" rel="stylesheet"/><style>
|
||||
html.glightbox-open { overflow: initial; height: 100%; }
|
||||
.gslide-title { margin-top: 0px; user-select: text; }
|
||||
.gslide-desc { color: #666; user-select: text; }
|
||||
.gslide-image img { background: white; }
|
||||
|
||||
.gscrollbar-fixer { padding-right: 15px; }
|
||||
.gdesc-inner { font-size: 0.75rem; }
|
||||
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color);}
|
||||
</style> <script src="../assets/javascripts/glightbox.min.js"></script></head>
|
||||
|
||||
|
||||
|
||||
|
|
@ -657,6 +668,46 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../28-maps-memory-leaks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Maps and memory leaks (#28)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Writing inaccurate benchmarks (#89)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../98-profiling-execution-tracing/" class="md-nav__link">
|
||||
|
||||
|
|
@ -1062,11 +1113,11 @@
|
|||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.dff1b7c8.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
<script>document$.subscribe(() => {const lightbox = GLightbox({"touchNavigation": true, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});})</script></body>
|
||||
</html>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<link rel="canonical" href="https://100go.co/98-profiling-execution-tracing/">
|
||||
|
||||
|
||||
<link rel="prev" href="../9-generics/">
|
||||
<link rel="prev" href="../89-benchmarks/">
|
||||
|
||||
|
||||
<link rel="next" href="../zh/">
|
||||
|
|
@ -89,7 +89,18 @@
|
|||
|
||||
|
||||
|
||||
</head>
|
||||
<link href="../assets/stylesheets/glightbox.min.css" rel="stylesheet"/><style>
|
||||
html.glightbox-open { overflow: initial; height: 100%; }
|
||||
.gslide-title { margin-top: 0px; user-select: text; }
|
||||
.gslide-desc { color: #666; user-select: text; }
|
||||
.gslide-image img { background: white; }
|
||||
|
||||
.gscrollbar-fixer { padding-right: 15px; }
|
||||
.gdesc-inner { font-size: 0.75rem; }
|
||||
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color);}
|
||||
</style> <script src="../assets/javascripts/glightbox.min.js"></script></head>
|
||||
|
||||
|
||||
|
||||
|
|
@ -596,6 +607,46 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../28-maps-memory-leaks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Maps and memory leaks (#28)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Writing inaccurate benchmarks (#89)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item md-nav__item--active">
|
||||
|
|
@ -961,9 +1012,17 @@
|
|||
<div class="language-text highlight"><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a>$ go tool pprof -http=:8080 <file>
|
||||
</span></code></pre></div>
|
||||
<p>This command opens a web UI showing the call graph. The next figure shows an example taken from an application. The larger the arrow, the more it was a hot path. We can then navigate into this graph and get execution insights.</p>
|
||||
<p><img alt="" src="../img/screen-pprof-cpu.png" /></p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/screen-pprof-cpu.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/screen-pprof-cpu.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 1: The call graph of an application during 30 seconds.</figcaption>
|
||||
</figure>
|
||||
<p>For example, the graph in the next figure tells us that during 30 seconds, 0.06 seconds were spent in the <code>decode</code> method (<code>*FetchResponse</code> receiver). Of these 0.06 seconds, 0.02 were spent in <code>RecordBatch.decode</code> and 0.01 in <code>makemap</code> (creating a map).</p>
|
||||
<p><img alt="" src="../img/screen-pprof-sarama.png" /></p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/screen-pprof-sarama.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/screen-pprof-sarama.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 2: Example call graph.</figcaption>
|
||||
</figure>
|
||||
<p>We can also access this kind of information from the web UI with different representations. For example, the Top view sorts the functions per execution time, and Flame Graph visualizes the execution time hierarchy. The UI can even display the expensive parts of the source code line by line.</p>
|
||||
<details class="note" open="open">
|
||||
<summary>Note</summary>
|
||||
|
|
@ -984,7 +1043,11 @@
|
|||
<p>Heap profiling allows us to get statistics about the current heap usage. Like CPU profiling, heap profiling is sample-based. We can change this rate, but we shouldn’t be too granular because the more we decrease the rate, the more effort heap profiling will require to collect data. By default, samples are profiled at one allocation for every 512 KB of heap allocation.</p>
|
||||
<p>If we reach /debug/pprof/heap/, we get raw data that can be hard to read. However, we can download a heap profile using /debug/pprof/heap/?debug=0 and then open it with <code>go tool</code> (the same command as in the previous section) to navigate into the data using the web UI.</p>
|
||||
<p>The next figure shows an example of a heap graph. Calling the <code>MetadataResponse.decode</code> method leads to allocating 1536 KB of heap data (which represents 6.32% of the total heap). However, 0 out of these 1536 KB were allocated by this function directly, so we need to inspect the second call. The <code>TopicMetadata.decode</code> method allocated 512 KB out of the 1536 KB; the rest — 1024 KB — were allocated in another method.</p>
|
||||
<p><img alt="" src="../img/screen-pprof-heap.png" /></p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/screen-pprof-heap.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/screen-pprof-heap.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 3: A heap graph.</figcaption>
|
||||
</figure>
|
||||
<p>This is how we can navigate the call chain to understand what part of an application is responsible for most of the heap allocations. We can also look at different sample types:</p>
|
||||
<ul>
|
||||
<li><code>alloc_objects</code>— Total number of objects allocated</li>
|
||||
|
|
@ -1012,14 +1075,22 @@
|
|||
<div class="language-text highlight"><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1" href="#__codelineno-3-1"></a>$ go tool pprof -http=:8080 -diff_base <file2> <file1>
|
||||
</span></code></pre></div>
|
||||
<p>The next figure shows the kind of data we can access. For example, the amount of heap memory held by the newTopicProducer method (top left) has decreased (–513 KB). In contrast, the amount held by updateMetadata (bottom right) has increased (+512 KB). Slow increases are normal. The second heap profile may have been calculated in the middle of a service call, for example. We can repeat this process or wait longer; the important part is to track steady increases in allocations of a specific object.</p>
|
||||
<p><img alt="" src="../img/screen-pprof-heap-diff.png" /></p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/screen-pprof-heap-diff.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/screen-pprof-heap-diff.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 4: The differences between the two heap profiles.</figcaption>
|
||||
</figure>
|
||||
<details class="note" open="open">
|
||||
<summary>Note</summary>
|
||||
<p>Another type of profiling related to the heap is <code>allocs</code>, which reports allocations. Heap profiling shows the current state of the heap memory. To get insights about past memory allocations since the application started, we can use allocations profiling. As discussed, because stack allocations are cheap, they aren’t part of this profiling, which only focuses on the heap.</p>
|
||||
</details>
|
||||
<h3 id="goroutine-profiling">Goroutine Profiling</h3>
|
||||
<p>The <code>goroutine</code> profile reports the stack trace of all the current goroutines in an application. We can download a file using /debug/pprof/goroutine/?debug=0 and use go tool again. The next figure shows the kind of information we can get.</p>
|
||||
<p><img alt="" src="../img/screen-pprof-goroutines.png" /></p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/screen-pprof-goroutines.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/screen-pprof-goroutines.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 5: Goroutine graph.</figcaption>
|
||||
</figure>
|
||||
<p>We can see the current state of the application and how many goroutines were created per function. In this case, <code>withRecover</code> has created 296 ongoing goroutines (63%), and 29 were related to a call to <code>responseFeeder</code>.</p>
|
||||
<p>This kind of information is also beneficial if we suspect goroutine leaks. We can look at goroutine profiler data to know which part of a system is the suspect.</p>
|
||||
<h3 id="block-profiling">Block Profiling</h3>
|
||||
|
|
@ -1077,13 +1148,29 @@
|
|||
</span></code></pre></div>
|
||||
<p>The web browser opens, and we can click View Trace to see all the traces during a specific timeframe, as shown in the next figure. This figure represents about 150 ms. We can see multiple helpful metrics, such as the goroutine count and the heap size. The heap size grows steadily until a GC is triggered. We can also observe the activity of the Go application per CPU core. The timeframe starts with user-level code; then a “stop the
|
||||
world” is executed, which occupies the four CPU cores for approximately 40 ms.</p>
|
||||
<p><img alt="" src="../img/tracing.png" /></p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/tracing.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/tracing.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 6: Showing goroutine activity and runtime events such as a GC phase.</figcaption>
|
||||
</figure>
|
||||
<p>Regarding concurrency, we can see that this version uses all the available CPU cores on the machine. However, the next figure zooms in on a portion of 1 ms. Each bar corresponds to a single goroutine execution. Having too many small bars doesn’t look right: it means execution that is poorly parallelized.</p>
|
||||
<p><img alt="" src="../img/screen-mergesort1.png" /></p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/screen-mergesort1.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/screen-mergesort1.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 7: Too many small bars mean poorly parallelized execution.</figcaption>
|
||||
</figure>
|
||||
<p>The next figure zooms even closer to see how these goroutines are orchestrated. Roughly 50% of the CPU time isn’t spent executing application code. The white spaces represent the time the Go runtime takes to spin up and orchestrate new goroutines.</p>
|
||||
<p><img alt="" src="../img/screen-mergesort11.png" /></p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/screen-mergesort11.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/screen-mergesort11.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 8: About 50% of CPU time is spent handling goroutine switches.</figcaption>
|
||||
</figure>
|
||||
<p>Let’s compare this with the second parallel implementation, which was about an order of magnitude faster. The next figure again zooms to a 1 ms timeframe.</p>
|
||||
<p><img alt="" src="../img/screen-mergesort2.png" /></p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/screen-mergesort2.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/screen-mergesort2.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 9: The number of white spaces has been significantly reduced, proving that the CPU is more fully occupied.</figcaption>
|
||||
</figure>
|
||||
<p>Each goroutine takes more time to execute, and the number of white spaces has been significantly reduced. Hence, the CPU is much more occupied executing application code than it was in the first version. Each millisecond of CPU time is spent more efficiently, explaining the benchmark differences.</p>
|
||||
<p>Note that the granularity of the traces is per goroutine, not per function like CPU profiling. However, it’s possible to define user-level tasks to get insights per function or group of functions using the <code>runtime/trace</code> package.</p>
|
||||
<p>For example, imagine a function that computes a Fibonacci number and then writes it to a global variable using atomic. We can define two different tasks:</p>
|
||||
|
|
@ -1103,7 +1190,11 @@ world” is executed, which occupies the four CPU cores for approximately 40 ms.
|
|||
</span><span id="__span-7-14"><a id="__codelineno-7-14" name="__codelineno-7-14" href="#__codelineno-7-14"></a><span class="nx">fibStore</span><span class="p">.</span><span class="nx">End</span><span class="p">()</span>
|
||||
</span></code></pre></div>
|
||||
<p>Using <code>go tool</code>, we can get more precise information about how these two tasks perform. In the previous trace UI, we can see the boundaries for each task per goroutine. In User-Defined Tasks, we can follow the duration distribution:</p>
|
||||
<p><img alt="" src="../img/screen-tracing-user-level.png" /></p>
|
||||
<figure>
|
||||
<p><a class="glightbox" href="../img/screen-tracing-user-level.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/screen-tracing-user-level.png" /></a>
|
||||
</p>
|
||||
<figcaption>Figure 10: Distribution of user-level tasks.</figcaption>
|
||||
</figure>
|
||||
<p>We see that in most cases, the <code>fibonacci</code> task is executed in less than 15 microseconds, whereas the <code>store</code> task takes less than 6309 nanoseconds.</p>
|
||||
<p>In the previous section, we discussed the kinds of information we can get from CPU profiling. What are the main differences compared to the data we can get from user-level traces?</p>
|
||||
<ul>
|
||||
|
|
@ -1190,11 +1281,11 @@ world” is executed, which occupies the four CPU cores for approximately 40 ms.
|
|||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.dff1b7c8.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
<script>document$.subscribe(() => {const lightbox = GLightbox({"touchNavigation": true, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});})</script></body>
|
||||
</html>
|
||||
BIN
site/assets/images/social/28-maps-memory-leaks.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
site/assets/images/social/89-benchmarks.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
1
site/assets/javascripts/glightbox.min.js
vendored
Normal file
1
site/assets/stylesheets/glightbox.min.css
vendored
Normal file
|
|
@ -87,7 +87,18 @@
|
|||
|
||||
|
||||
|
||||
</head>
|
||||
<link href="../assets/stylesheets/glightbox.min.css" rel="stylesheet"/><style>
|
||||
html.glightbox-open { overflow: initial; height: 100%; }
|
||||
.gslide-title { margin-top: 0px; user-select: text; }
|
||||
.gslide-desc { color: #666; user-select: text; }
|
||||
.gslide-image img { background: white; }
|
||||
|
||||
.gscrollbar-fixer { padding-right: 15px; }
|
||||
.gdesc-inner { font-size: 0.75rem; }
|
||||
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color);}
|
||||
</style> <script src="../assets/javascripts/glightbox.min.js"></script></head>
|
||||
|
||||
|
||||
|
||||
|
|
@ -660,6 +671,46 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../28-maps-memory-leaks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Maps and memory leaks (#28)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Writing inaccurate benchmarks (#89)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../98-profiling-execution-tracing/" class="md-nav__link">
|
||||
|
||||
|
|
@ -845,7 +896,7 @@
|
|||
|
||||
<h1 id="100-go-mistakes-and-how-to-avoid-them">100 Go Mistakes and How to Avoid Them</h1>
|
||||
<p>Community space of <em>100 Go Mistakes and How to Avoid Them</em>, published by Manning in 2022.</p>
|
||||
<p><img alt="" src="../img/cover.png" /></p>
|
||||
<p><a class="glightbox" href="../img/cover.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="../img/cover.png" /></a></p>
|
||||
<h2 id="description">Description</h2>
|
||||
<p>If you're a Go developer looking to improve your skills, this book is for you. With a focus on practical examples, <em>100 Go Mistakes and How to Avoid Them</em> covers a wide range of topics from concurrency and error handling to testing and code organization. You'll learn to write more idiomatic, efficient, and maintainable code and become a proficient Go developer.</p>
|
||||
<h2 id="quotes">Quotes</h2>
|
||||
|
|
@ -949,11 +1000,11 @@
|
|||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.dff1b7c8.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
<script>document$.subscribe(() => {const lightbox = GLightbox({"touchNavigation": true, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});})</script></body>
|
||||
</html>
|
||||
|
|
@ -89,7 +89,18 @@
|
|||
|
||||
|
||||
|
||||
</head>
|
||||
<link href="../assets/stylesheets/glightbox.min.css" rel="stylesheet"/><style>
|
||||
html.glightbox-open { overflow: initial; height: 100%; }
|
||||
.gslide-title { margin-top: 0px; user-select: text; }
|
||||
.gslide-desc { color: #666; user-select: text; }
|
||||
.gslide-image img { background: white; }
|
||||
|
||||
.gscrollbar-fixer { padding-right: 15px; }
|
||||
.gdesc-inner { font-size: 0.75rem; }
|
||||
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color);}
|
||||
</style> <script src="../assets/javascripts/glightbox.min.js"></script></head>
|
||||
|
||||
|
||||
|
||||
|
|
@ -717,6 +728,46 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../28-maps-memory-leaks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Maps and memory leaks (#28)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Writing inaccurate benchmarks (#89)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../98-profiling-execution-tracing/" class="md-nav__link">
|
||||
|
||||
|
|
@ -996,7 +1047,7 @@
|
|||
<p>This book aims to help accelerate our journey toward proficiency by delving into 100 Go mistakes.</p>
|
||||
<h2 id="100-go-mistakes">100 Go mistakes</h2>
|
||||
<p>Why should we read a book about common Go mistakes? Why not deepen our knowledge with an ordinary book that would dig into different topics?</p>
|
||||
<p>In a 2011 article, neuroscientists proved that the best time for brain growth is when we’re facing mistakes. Haven’t we all experienced the process of learning from a mistake and recalling that occasion after months or even years, when some context related to it? As presented in another article, by Janet Metcalfe, this happens because mistakes have a facilitative effect. The main idea is that we can remember not only the error but also the context surrounding the mistake. This is one of the reasons why learning from mistakes is so efficient.</p>
|
||||
<p>In a 2011 article, neuroscientists proved that the best time for brain growth is when we’re facing mistakes. <sup id="fnref:1"><a class="footnote-ref" href="#fn:1">1</a></sup> Haven’t we all experienced the process of learning from a mistake and recalling that occasion after months or even years, when some context related to it? As presented in another article, by Janet Metcalfe, this happens because mistakes have a facilitative effect. <sup id="fnref:2"><a class="footnote-ref" href="#fn:2">2</a></sup> The main idea is that we can remember not only the error but also the context surrounding the mistake. This is one of the reasons why learning from mistakes is so efficient.</p>
|
||||
<p>To strengthen this facilitative effect, this book accompanies each mistake as much as possible with real-world examples. This book isn’t only about theory; it also helps us get better at avoiding mistakes and making more well-informed, conscious decisions because we now understand the rationale behind them.</p>
|
||||
<div class="admonition quote">
|
||||
<p class="admonition-title">Unknown</p>
|
||||
|
|
@ -1014,7 +1065,7 @@
|
|||
</ul>
|
||||
<p>We introduce each mistake category next.</p>
|
||||
<h3 id="bugs">Bugs</h3>
|
||||
<p>The first type of mistake and probably the most obvious is software bugs. In 2020, a study conducted by Synopsys estimated the cost of software bugs in the U.S. alone to be over $2 trillion.</p>
|
||||
<p>The first type of mistake and probably the most obvious is software bugs. In 2020, a study conducted by Synopsys estimated the cost of software bugs in the U.S. alone to be over $2 trillion. <sup id="fnref:3"><a class="footnote-ref" href="#fn:3">3</a></sup></p>
|
||||
<p>Furthermore, bugs can also lead to tragic impacts. We can, for example, mention cases such as Therac-25, a radiation therapy machine produced by Atomic Energy of Canada Limited (AECL). Because of a race condition, the machine gave its patients radiation doses that were hundreds of times greater than expected, leading to the death of three patients. Hence, software bugs aren’t only about money. As developers, we should remember how impactful our jobs are.</p>
|
||||
<p>This book covers plenty of cases that could lead to various software bugs, including data races, leaks, logic errors, and other defects. Although accurate tests should be a way to discover such bugs as early as possible, we may sometimes miss cases because of different factors such as time constraints or complexity. Therefore, as a Go developer, it’s essential to make sure we avoid common bugs.</p>
|
||||
<h3 id="needless-complexity">Needless complexity</h3>
|
||||
|
|
@ -1040,6 +1091,20 @@
|
|||
<li>Go is simple to learn but not easy to master. This is why we need to deepen our knowledge to make the most effective use of the language.</li>
|
||||
<li>Learning via mistakes and concrete examples is a powerful way to be proficient in a language. This book will accelerate our path to proficiency by exploring 100 common mistakes.</li>
|
||||
</ul>
|
||||
<div class="footnote">
|
||||
<hr />
|
||||
<ol>
|
||||
<li id="fn:1">
|
||||
<p>J. S. Moser, H. S. Schroder, et al., “Mind Your Errors: Evidence for a Neural Mechanism Linking Growth Mindset to Adaptive Posterror Adjustments,” Psychological Science, vol. 22, no. 12, pp. 1484–1489, Dec. 2011. <a class="footnote-backref" href="#fnref:1" title="Jump back to footnote 1 in the text">↩</a></p>
|
||||
</li>
|
||||
<li id="fn:2">
|
||||
<p>J. Metcalfe, “Learning from Errors,” Annual Review of Psychology, vol. 68, pp. 465–489, Jan. 2017. <a class="footnote-backref" href="#fnref:2" title="Jump back to footnote 2 in the text">↩</a></p>
|
||||
</li>
|
||||
<li id="fn:3">
|
||||
<p>Synopsys, “The Cost of Poor Software Quality in the US: A 2020 Report.” 2020. <a href="https://news.synopsys.com/2021-01-06-Synopsys-Sponsored-CISQ-Research-Estimates-Cost-of-Poor-Software-Quality-in-the-US-2-08-Trillion-in-2020">https://news.synopsys.com/2021-01-06-Synopsys-Sponsored-CISQ-Research-Estimates-Cost-of-Poor-Software-Quality-in-the-US-2-08-Trillion-in-2020</a>. <a class="footnote-backref" href="#fnref:3" title="Jump back to footnote 3 in the text">↩</a></p>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
|
@ -1109,11 +1174,11 @@
|
|||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.dff1b7c8.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
<script>document$.subscribe(() => {const lightbox = GLightbox({"touchNavigation": true, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});})</script></body>
|
||||
</html>
|
||||
57
site/external/index.html
vendored
|
|
@ -89,7 +89,18 @@
|
|||
|
||||
|
||||
|
||||
</head>
|
||||
<link href="../assets/stylesheets/glightbox.min.css" rel="stylesheet"/><style>
|
||||
html.glightbox-open { overflow: initial; height: 100%; }
|
||||
.gslide-title { margin-top: 0px; user-select: text; }
|
||||
.gslide-desc { color: #666; user-select: text; }
|
||||
.gslide-image img { background: white; }
|
||||
|
||||
.gscrollbar-fixer { padding-right: 15px; }
|
||||
.gdesc-inner { font-size: 0.75rem; }
|
||||
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color);}
|
||||
</style> <script src="../assets/javascripts/glightbox.min.js"></script></head>
|
||||
|
||||
|
||||
|
||||
|
|
@ -662,6 +673,46 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../28-maps-memory-leaks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Maps and memory leaks (#28)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Writing inaccurate benchmarks (#89)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../98-profiling-execution-tracing/" class="md-nav__link">
|
||||
|
||||
|
|
@ -938,11 +989,11 @@
|
|||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.dff1b7c8.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
<script>document$.subscribe(() => {const lightbox = GLightbox({"touchNavigation": true, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});})</script></body>
|
||||
</html>
|
||||
BIN
site/img/map-leak-1.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
site/img/map-leak-2.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
site/img/matrix.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
126
site/index.html
|
|
@ -89,7 +89,18 @@
|
|||
|
||||
|
||||
|
||||
</head>
|
||||
<link href="assets/stylesheets/glightbox.min.css" rel="stylesheet"/><style>
|
||||
html.glightbox-open { overflow: initial; height: 100%; }
|
||||
.gslide-title { margin-top: 0px; user-select: text; }
|
||||
.gslide-desc { color: #666; user-select: text; }
|
||||
.gslide-image img { background: white; }
|
||||
|
||||
.gscrollbar-fixer { padding-right: 15px; }
|
||||
.gdesc-inner { font-size: 0.75rem; }
|
||||
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color);}
|
||||
</style> <script src="assets/javascripts/glightbox.min.js"></script></head>
|
||||
|
||||
|
||||
|
||||
|
|
@ -763,11 +774,11 @@
|
|||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#slice-and-memory-leaks-26" class="md-nav__link">
|
||||
Slice and memory leaks (#26)
|
||||
<a href="#slices-and-memory-leaks-26" class="md-nav__link">
|
||||
Slices and memory leaks (#26)
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="Slice and memory leaks (#26)">
|
||||
<nav class="md-nav" aria-label="Slices and memory leaks (#26)">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
|
|
@ -797,8 +808,8 @@
|
|||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#map-and-memory-leaks-28" class="md-nav__link">
|
||||
Map and memory leaks (#28)
|
||||
<a href="#maps-and-memory-leaks-28" class="md-nav__link">
|
||||
Maps and memory leaks (#28)
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
|
@ -1504,6 +1515,46 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="28-maps-memory-leaks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Maps and memory leaks (#28)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Writing inaccurate benchmarks (#89)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="98-profiling-execution-tracing/" class="md-nav__link">
|
||||
|
||||
|
|
@ -1838,11 +1889,11 @@
|
|||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#slice-and-memory-leaks-26" class="md-nav__link">
|
||||
Slice and memory leaks (#26)
|
||||
<a href="#slices-and-memory-leaks-26" class="md-nav__link">
|
||||
Slices and memory leaks (#26)
|
||||
</a>
|
||||
|
||||
<nav class="md-nav" aria-label="Slice and memory leaks (#26)">
|
||||
<nav class="md-nav" aria-label="Slices and memory leaks (#26)">
|
||||
<ul class="md-nav__list">
|
||||
|
||||
<li class="md-nav__item">
|
||||
|
|
@ -1872,8 +1923,8 @@
|
|||
</li>
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="#map-and-memory-leaks-28" class="md-nav__link">
|
||||
Map and memory leaks (#28)
|
||||
<a href="#maps-and-memory-leaks-28" class="md-nav__link">
|
||||
Maps and memory leaks (#28)
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
|
@ -2540,7 +2591,7 @@
|
|||
|
||||
<!-- TODO Include chapter-1.md -->
|
||||
|
||||
<p><img alt="" src="img/inside-cover.png" /></p>
|
||||
<p><a class="glightbox" href="img/inside-cover.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="img/inside-cover.png" /></a></p>
|
||||
<h2 id="code-and-project-organization">Code and Project Organization</h2>
|
||||
<h3 id="unintended-variable-shadowing-1">Unintended variable shadowing (#1)</h3>
|
||||
<details class="info" open="open">
|
||||
|
|
@ -2653,8 +2704,6 @@ What’s the main problem if we overuse interfaces? The answer is that they make
|
|||
<p>Relying on generics and type parameters can prevent writing boilerplate code to factor out elements or behaviors. However, do not use type parameters prematurely, but only when you see a concrete need for them. Otherwise, they introduce unnecessary abstractions and complexity.</p>
|
||||
</details>
|
||||
<p>Read the full section <a href="9-generics/">here</a>.</p>
|
||||
<!-- TODO Include 9-generics.md file -->
|
||||
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/9-generics/main.go">Source code</a></p>
|
||||
<h3 id="not-being-aware-of-the-possible-problems-with-type-embedding-10">Not being aware of the possible problems with type embedding (#10)</h3>
|
||||
<details class="info" open="open">
|
||||
|
|
@ -2688,7 +2737,7 @@ promoted to <code>Foo</code>. Therefore, Baz becomes available from Foo.</p>
|
|||
<p>Although there are different implementations with minor variations, the main idea is as follows:
|
||||
* An unexported struct holds the configuration: options.
|
||||
* Each option is a function that returns the same type: <code>type Option func(options *options)</code> error. For example, <code>WithPort</code> accepts an <code>int</code> argument that represents the port and returns an <code>Option</code> type that represents how to update the <code>options</code> struct.</p>
|
||||
<p><img alt="" src="img/options.png" /></p>
|
||||
<p><a class="glightbox" href="img/options.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="img/options.png" /></a></p>
|
||||
<div class="language-go highlight"><pre><span></span><code><span id="__span-5-1"><a id="__codelineno-5-1" name="__codelineno-5-1" href="#__codelineno-5-1"></a><span class="kd">type</span><span class="w"> </span><span class="nx">options</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span>
|
||||
</span><span id="__span-5-2"><a id="__codelineno-5-2" name="__codelineno-5-2" href="#__codelineno-5-2"></a><span class="w"> </span><span class="nx">port</span><span class="w"> </span><span class="o">*</span><span class="kt">int</span>
|
||||
</span><span id="__span-5-3"><a id="__codelineno-5-3" name="__codelineno-5-3" href="#__codelineno-5-3"></a><span class="p">}</span>
|
||||
|
|
@ -2881,7 +2930,7 @@ Meanwhile, we should also look at golangci-lint (https://github.com/golangci/ go
|
|||
<p><code>s[low:high:max]</code> (full slice expression): This statement creates a slice similar to the one created with <code>s[low:high]</code>, except that the resulting slice’s capacity is equal to <code>max - low</code>.</p>
|
||||
</details>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/25-slice-append/main.go">Source code</a></p>
|
||||
<h3 id="slice-and-memory-leaks-26">Slice and memory leaks (#26)</h3>
|
||||
<h3 id="slices-and-memory-leaks-26">Slices and memory leaks (#26)</h3>
|
||||
<details class="info" open="open">
|
||||
<summary>TL;DR</summary>
|
||||
<p>Working with a slice of pointers or structs with pointer fields, you can avoid memory leaks by marking as nil the elements excluded by a slicing operation.</p>
|
||||
|
|
@ -2900,13 +2949,12 @@ Meanwhile, we should also look at golangci-lint (https://github.com/golangci/ go
|
|||
<p>A map provides an unordered collection of key-value pairs in which all the keys are distinct. In Go, a map is based on the hash table data structure. Internally, a hash table is an array of buckets, and each bucket is a pointer to an array of key-value pairs.</p>
|
||||
<p>If we know up front the number of elements a map will contain, we should create it by providing an initial size. Doing this avoids potential map growth, which is quite heavy computation-wise because it requires reallocating enough space and rebalancing all the elements.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/27-map-init/main_test.go">Source code</a></p>
|
||||
<h3 id="map-and-memory-leaks-28"><a href="https://teivah.medium.com/maps-and-memory-leaks-in-go-a85ebe6e7e69">Map and memory leaks</a> (#28)</h3>
|
||||
<h3 id="maps-and-memory-leaks-28">Maps and memory leaks (#28)</h3>
|
||||
<details class="info" open="open">
|
||||
<summary>TL;DR</summary>
|
||||
<p>A map can always grow in memory, but it never shrinks. Hence, if it leads to some memory issues, you can try different options, such as forcing Go to recreate the map or using pointers.</p>
|
||||
</details>
|
||||
<!-- TODO -->
|
||||
|
||||
<p>Read the full section <a href="28-maps-memory-leaks/">here</a>.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/28-map-memory-leak/main.go">Source code</a></p>
|
||||
<h3 id="comparing-values-incorrectly-29">Comparing values incorrectly (#29)</h3>
|
||||
<details class="info" open="open">
|
||||
|
|
@ -3104,7 +3152,7 @@ One additional note: we must remember that the standard library has some existin
|
|||
</ul>
|
||||
<p>Let’s start with the last observation. We already mentioned that len returns the number of bytes in a string, not the number of runes. Because we assigned a string literal to <code>s</code>, <code>s</code> is a UTF-8 string. Meanwhile, the special character "ê" isn’t encoded in a single byte; it requires 2 bytes. Therefore, calling <code>len(s)</code> returns 6.</p>
|
||||
<p>Meanwhile, in the previous example, we have to understand that we don't iterate over each rune; instead, we iterate over each starting index of a rune:</p>
|
||||
<p><img alt="" src="img/rune.png" /></p>
|
||||
<p><a class="glightbox" href="img/rune.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="img/rune.png" /></a></p>
|
||||
<p>Printing <code>s[i]</code> doesn’t print the ith rune; it prints the UTF-8 representation of the byte at index <code>i</code>. Hence, we printed "hÃllo" instead of "hêllo".</p>
|
||||
<p>If we want to print all the different runes, we can either use the value element of the <code>range</code> operator:</p>
|
||||
<div class="language-go highlight"><pre><span></span><code><span id="__span-16-1"><a id="__codelineno-16-1" name="__codelineno-16-1" href="#__codelineno-16-1"></a><span class="nx">s</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="s">"hêllo"</span>
|
||||
|
|
@ -3135,7 +3183,7 @@ One additional note: we must remember that the standard library has some existin
|
|||
<div class="language-go highlight"><pre><span></span><code><span id="__span-19-1"><a id="__codelineno-19-1" name="__codelineno-19-1" href="#__codelineno-19-1"></a><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">strings</span><span class="p">.</span><span class="nx">TrimRight</span><span class="p">(</span><span class="s">"123oxo"</span><span class="p">,</span><span class="w"> </span><span class="s">"xo"</span><span class="p">))</span>
|
||||
</span></code></pre></div>
|
||||
<p>The example prints 123:</p>
|
||||
<p><img alt="" src="img/trim.png" /></p>
|
||||
<p><a class="glightbox" href="img/trim.png" data-type="image" data-width="100%" data-height="auto" data-desc-position="bottom"><img alt="" src="img/trim.png" /></a></p>
|
||||
<p>Conversely, <code>strings.TrimLeft</code> removes all the leading runes contained in a set.</p>
|
||||
<p>On the other side, <code>strings.TrimSuffix</code> / <code>strings.TrimPrefix</code> returns a string without the provided trailing suffix / prefix.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/38-trim/main.go">Source code</a></p>
|
||||
|
|
@ -3702,28 +3750,20 @@ One additional note: we must remember that the standard library has some existin
|
|||
<li>The <code>iotest</code> package helps write io.Reader and test that an application is tolerant to errors.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/88-utility-package/iotest/main_test.go">Source code</a></p>
|
||||
<h3 id="writing-inaccurate-benchmarks-89"><a href="https://teivah.medium.com/how-to-write-accurate-benchmarks-in-go-4266d7dd1a95">Writing inaccurate benchmarks</a> (#89)</h3>
|
||||
<h3 id="writing-inaccurate-benchmarks-89">Writing inaccurate benchmarks (#89)</h3>
|
||||
<details class="info" open="open">
|
||||
<summary>TL;DR</summary>
|
||||
<p>Regarding benchmarks:</p>
|
||||
<ul>
|
||||
<li>Not resetting or pausing the timer</li>
|
||||
<li>Use time methods to preserve the accuracy of a benchmark.</li>
|
||||
<li>Increasing benchtime or using tools such as benchstat can be helpful when dealing with micro-benchmarks.</li>
|
||||
<li>Be careful with the results of a micro-benchmark if the system that ends up running the application is different from the one running the micro-bench- mark.</li>
|
||||
<li>Make sure the function under test leads to a side effect, to prevent compiler optimizations from fooling you about the benchmark results.</li>
|
||||
<li>To prevent the observer effect, force a benchmark to re-create the data used by a CPU-bound function.</li>
|
||||
</ul>
|
||||
<p>Use time methods to preserve the accuracy of a benchmark.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/timer/main_test.go">Source code</a></p>
|
||||
<ul>
|
||||
<li>Making wrong assumptions about micro-benchmarks</li>
|
||||
</ul>
|
||||
<p>Increasing <code>benchtime</code> or using tools such as <code>benchstat</code> can be helpful when dealing with micro-benchmarks.</p>
|
||||
<p>Be careful with the results of a micro-benchmark if the system that ends up running the application is different from the one running the micro-benchmark.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/wrong-assumptions/main_test.go">Source code</a></p>
|
||||
<ul>
|
||||
<li>Not being careful about compiler optimizations</li>
|
||||
</ul>
|
||||
<p>Make sure the function under test leads to a side effect, to prevent compiler optimizations from fooling you about the benchmark results.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/compiler-optimizations/main_test.go">Source code</a></p>
|
||||
<ul>
|
||||
<li>Being fooled by the observer effect</li>
|
||||
</ul>
|
||||
<p>To prevent the observer effect, force a benchmark to re-create the data used by a CPU-bound function.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/observer-effect/main_test.go">Source code</a></p>
|
||||
</details>
|
||||
<p>Read the full section <a href="89-benchmarks/">here</a>.</p>
|
||||
<p><a href="https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/">Source code</a></p>
|
||||
<h3 id="not-exploring-all-the-go-testing-features-90">Not exploring all the Go testing features (#90)</h3>
|
||||
<ul>
|
||||
<li>Code coverage</li>
|
||||
|
|
@ -3896,11 +3936,11 @@ One additional note: we must remember that the standard library has some existin
|
|||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
<script id="__config" type="application/json">{"base": ".", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest"], "search": "assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
<script id="__config" type="application/json">{"base": ".", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
|
||||
|
||||
<script src="assets/javascripts/bundle.dff1b7c8.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
<script>document$.subscribe(() => {const lightbox = GLightbox({"touchNavigation": true, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});})</script></body>
|
||||
</html>
|
||||
|
|
@ -87,7 +87,18 @@
|
|||
|
||||
|
||||
|
||||
</head>
|
||||
<link href="../assets/stylesheets/glightbox.min.css" rel="stylesheet"/><style>
|
||||
html.glightbox-open { overflow: initial; height: 100%; }
|
||||
.gslide-title { margin-top: 0px; user-select: text; }
|
||||
.gslide-desc { color: #666; user-select: text; }
|
||||
.gslide-image img { background: white; }
|
||||
|
||||
.gscrollbar-fixer { padding-right: 15px; }
|
||||
.gdesc-inner { font-size: 0.75rem; }
|
||||
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color);}
|
||||
</style> <script src="../assets/javascripts/glightbox.min.js"></script></head>
|
||||
|
||||
|
||||
|
||||
|
|
@ -592,6 +603,46 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../28-maps-memory-leaks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Maps and memory leaks (#28)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Writing inaccurate benchmarks (#89)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../98-profiling-execution-tracing/" class="md-nav__link">
|
||||
|
||||
|
|
@ -826,11 +877,11 @@
|
|||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.dff1b7c8.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
<script>document$.subscribe(() => {const lightbox = GLightbox({"touchNavigation": true, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});})</script></body>
|
||||
</html>
|
||||
|
|
@ -2,42 +2,52 @@
|
|||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://100go.co/</loc>
|
||||
<lastmod>2023-09-15</lastmod>
|
||||
<lastmod>2023-09-17</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/28-maps-memory-leaks/</loc>
|
||||
<lastmod>2023-09-17</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/89-benchmarks/</loc>
|
||||
<lastmod>2023-09-17</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/9-generics/</loc>
|
||||
<lastmod>2023-09-15</lastmod>
|
||||
<lastmod>2023-09-17</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/98-profiling-execution-tracing/</loc>
|
||||
<lastmod>2023-09-15</lastmod>
|
||||
<lastmod>2023-09-17</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/book/</loc>
|
||||
<lastmod>2023-09-15</lastmod>
|
||||
<lastmod>2023-09-17</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/chapter-1/</loc>
|
||||
<lastmod>2023-09-15</lastmod>
|
||||
<lastmod>2023-09-17</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/external/</loc>
|
||||
<lastmod>2023-09-15</lastmod>
|
||||
<lastmod>2023-09-17</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/jobs/</loc>
|
||||
<lastmod>2023-09-15</lastmod>
|
||||
<lastmod>2023-09-17</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://100go.co/zh/</loc>
|
||||
<lastmod>2023-09-15</lastmod>
|
||||
<lastmod>2023-09-17</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
</url>
|
||||
</urlset>
|
||||
|
|
@ -89,7 +89,18 @@
|
|||
|
||||
|
||||
|
||||
</head>
|
||||
<link href="../assets/stylesheets/glightbox.min.css" rel="stylesheet"/><style>
|
||||
html.glightbox-open { overflow: initial; height: 100%; }
|
||||
.gslide-title { margin-top: 0px; user-select: text; }
|
||||
.gslide-desc { color: #666; user-select: text; }
|
||||
.gslide-image img { background: white; }
|
||||
|
||||
.gscrollbar-fixer { padding-right: 15px; }
|
||||
.gdesc-inner { font-size: 0.75rem; }
|
||||
body[data-md-color-scheme="slate"] .gdesc-inner { background: var(--md-default-bg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-title { color: var(--md-default-fg-color);}
|
||||
body[data-md-color-scheme="slate"] .gslide-desc { color: var(--md-default-fg-color);}
|
||||
</style> <script src="../assets/javascripts/glightbox.min.js"></script></head>
|
||||
|
||||
|
||||
|
||||
|
|
@ -594,6 +605,46 @@
|
|||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../28-maps-memory-leaks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Maps and memory leaks (#28)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../89-benchmarks/" class="md-nav__link">
|
||||
|
||||
|
||||
<span class="md-ellipsis">
|
||||
Writing inaccurate benchmarks (#89)
|
||||
</span>
|
||||
|
||||
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<li class="md-nav__item">
|
||||
<a href="../98-profiling-execution-tracing/" class="md-nav__link">
|
||||
|
||||
|
|
@ -2893,11 +2944,11 @@
|
|||
<div class="md-dialog__inner md-typeset"></div>
|
||||
</div>
|
||||
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
<script id="__config" type="application/json">{"base": "..", "features": ["navigation.tabs", "navigation.tabs.sticky", "search.highlight", "search.share", "search.suggest", "content.code.copy"], "search": "../assets/javascripts/workers/search.dfff1995.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
|
||||
|
||||
|
||||
<script src="../assets/javascripts/bundle.dff1b7c8.min.js"></script>
|
||||
|
||||
|
||||
</body>
|
||||
<script>document$.subscribe(() => {const lightbox = GLightbox({"touchNavigation": true, "loop": false, "zoomable": true, "draggable": true, "openEffect": "zoom", "closeEffect": "zoom", "slideEffect": "slide"});})</script></body>
|
||||
</html>
|
||||