diff --git a/.cache/plugin/social/0e91ef03ecf75bfcdfc3a0756afe5eb3.png b/.cache/plugin/social/0e91ef03ecf75bfcdfc3a0756afe5eb3.png new file mode 100644 index 0000000..f2f1d65 Binary files /dev/null and b/.cache/plugin/social/0e91ef03ecf75bfcdfc3a0756afe5eb3.png differ diff --git a/.cache/plugin/social/32eacdda64c3f30ce5c3faa1182316b2.png b/.cache/plugin/social/32eacdda64c3f30ce5c3faa1182316b2.png new file mode 100644 index 0000000..c36ca11 Binary files /dev/null and b/.cache/plugin/social/32eacdda64c3f30ce5c3faa1182316b2.png differ diff --git a/.cache/plugin/social/53f24aa072dbcfbf532abf9e62945e11.png b/.cache/plugin/social/53f24aa072dbcfbf532abf9e62945e11.png new file mode 100644 index 0000000..085b61e Binary files /dev/null and b/.cache/plugin/social/53f24aa072dbcfbf532abf9e62945e11.png differ diff --git a/.cache/plugin/social/6c3afdd84225436ef1eddcf2b1c82547.png b/.cache/plugin/social/6c3afdd84225436ef1eddcf2b1c82547.png new file mode 100644 index 0000000..eae2887 Binary files /dev/null and b/.cache/plugin/social/6c3afdd84225436ef1eddcf2b1c82547.png differ diff --git a/.cache/plugin/social/c7b8072b4d90baf29b7e819cb3a02064.png b/.cache/plugin/social/c7b8072b4d90baf29b7e819cb3a02064.png new file mode 100644 index 0000000..1730d93 Binary files /dev/null and b/.cache/plugin/social/c7b8072b4d90baf29b7e819cb3a02064.png differ diff --git a/.cache/plugin/social/ea9a0a27b2b9ccc98f485e832ea2eef4.png b/.cache/plugin/social/ea9a0a27b2b9ccc98f485e832ea2eef4.png new file mode 100644 index 0000000..4a6217e Binary files /dev/null and b/.cache/plugin/social/ea9a0a27b2b9ccc98f485e832ea2eef4.png differ diff --git a/.cache/plugin/social/ee1d2159b11eef4ec692741f34e52fcd.png b/.cache/plugin/social/ee1d2159b11eef4ec692741f34e52fcd.png new file mode 100644 index 0000000..2c7d7f6 Binary files /dev/null and b/.cache/plugin/social/ee1d2159b11eef4ec692741f34e52fcd.png differ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da29abd..3ab8d5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,4 +23,5 @@ jobs: mkdocs-material- - run: pip install mkdocs-material - run: pip install pillow cairosvg + - run: pip install mkdocs-glightbox - run: mkdocs gh-deploy --force diff --git a/docs/20-slice.md b/docs/20-slice.md new file mode 100644 index 0000000..a2e4d89 --- /dev/null +++ b/docs/20-slice.md @@ -0,0 +1,135 @@ +--- +title: Not understanding slice length and capacity (#20) +--- + +# Not understanding slice length and capacity + +It’s pretty common for Go developers to mix slice length and capacity or not understand them thoroughly. Assimilating these two concepts is essential for efficiently handling core operations such as slice initialization and adding elements with append, copying, or slicing. This misunderstanding can lead to using slices suboptimally or even to memory leaks. + +In Go, a slice is backed by an array. That means the slice’s data is stored contiguously in an array data structure. A slice also handles the logic of adding an element if the backing array is full or shrinking the backing array if it’s almost empty. + +Internally, a slice holds a pointer to the backing array plus a length and a capacity. The length is the number of elements the slice contains, whereas the capacity is the number of elements in the backing array, counting from the first element in the slice. Let’s go through a few examples to make things clearer. First, let’s initialize a slice with a given length and capacity: + +```go +s := make([]int, 3, 6) // Three-length, six-capacity slice +``` + +The first argument, representing the length, is mandatory. However, the second argument representing the capacity is optional. Figure 1 shows the result of this code in memory. + +
+ ![](img/slice-1.png) +
Figure 1: A three-length, six-capacity slice.
+
+ +In this case, `make` creates an array of six elements (the capacity). But because the length was set to 3, Go initializes only the first three elements. Also, because the slice is an `[]int` type, the first three elements are initialized to the zeroed value of an `int`: 0. The grayed elements are allocated but not yet used. + +If we print this slice, we get the elements within the range of the length, `[0 0 0]`. If we set `s[1]` to 1, the second element of the slice updates without impacting its length or capacity. Figure 2 illustrates this. + +
+ ![](img/slice-2.png) +
Figure 2: Updating the slice’s second element: s[1] = 1.
+
+ +However, accessing an element outside the length range is forbidden, even though it’s already allocated in memory. For example, `s[4]` = 0 would lead to the following panic: + +``` +panic: runtime error: index out of range [4] with length 3 +``` + +How can we use the remaining space of the slice? By using the `append` built-in function: + +```go +s = append(s, 2) +``` + +This code appends to the existing `s` slice a new element. It uses the first grayed element (which was allocated but not yet used) to store element 2, as figure 3 shows. + +
+ ![](img/slice-3.png) +
Figure 3: Appending an element to s.
+
+ +The length of the slice is updated from 3 to 4 because the slice now contains four elements. Now, what happens if we add three more elements so that the backing array isn’t large enough? + +```go +s = append(s, 3, 4, 5) +fmt.Println(s) +``` + +If we run this code, we see that the slice was able to cope with our request: + +``` +[0 1 0 2 3 4 5] +``` + +Because an array is a fixed-size structure, it can store the new elements until element 4. When we want to insert element 5, the array is already full: Go internally creates another array by doubling the capacity, copying all the elements, and then inserting element 5. Figure 4 shows this process. + +
+ ![](img/slice-4.png) +
Figure 4: Because the initial backing array is full, Go creates another array and copies all the elements.
+
+ +The slice now references the new backing array. What will happen to the previous backing array? If it’s no longer referenced, it’s eventually freed by the garbage collector (GC) if allocated on the heap. (We discuss heap memory in mistake #95, “[Not understanding stack vs. heap](https://100go.co#not-understanding-stack-vs-heap-95),” and we look at how the GC works in mistake #99, “[Not understanding how the GC works](https://100go.co#not-understanding-how-the-gc-works-99).”) + +What happens with slicing? Slicing is an operation done on an array or a slice, providing a half-open range; the first index is included, whereas the second is excluded. The following example shows the impact, and figure 5 displays the result in memory: + +```go +s1 := make([]int, 3, 6) // Three-length, six-capacity slice +s2 := s1[1:3] // Slicing from indices 1 to 3 +``` + +
+ ![](img/slice-5.png) +
Figure 5: The slices s1 and s2 reference the same backing array with different lengths and capacities.
+
+ +First, `s1` is created as a three-length, six-capacity slice. When `s2` is created by slicing `s1`, both slices reference the same backing array. However, `s2` starts from a different index, 1. Therefore, its length and capacity (a two-length, five-capacity slice) differ from s1. If we update `s1[1]` or `s2[0]`, the change is made to the same array, hence, visible in both slices, as figure 6 shows. + +
+ ![](img/slice-6.png) +
Figure 6: Because s1 and s2 are backed by the same array, updating a common element makes the change visible in both slices.
+
+ +Now, what happens if we append an element to `s2`? Does the following code change `s1` as well? + +```go +s2 = append(s2, 2) +``` + +The shared backing array is modified, but only the length of `s2` changes. Figure 7 shows the result of appending an element to `s2`. + +
+ ![](img/slice-7.png) +
Figure 7: Appending an element to s2.
+
+ +`s1` remains a three-length, six-capacity slice. Therefore, if we print `s1` and `s2`, the added element is only visible for `s2`: + +```go +s1=[0 1 0], s2=[1 0 2] +``` + +It’s important to understand this behavior so that we don’t make wrong assumptions while using append. + +???+ note + + In these examples, the backing array is internal and not available directly to the Go developer. The only exception is when a slice is created from slicing an existing array. + +One last thing to note: what if we keep appending elements to `s2` until the backing array is full? What will the state be, memory-wise? Let’s add three more elements so that the backing array will not have enough capacity: + +```go +s2 = append(s2, 3) +s2 = append(s2, 4) // At this stage, the backing is already full +s2 = append(s2, 5) +``` + +This code leads to creating another backing array. Figure 8 displays the results in memory. + +
+ ![](img/slice-8.png) +
Figure 8: Appending elements to s2 until the backing array is full.
+
+ +`s1` and `s2` now reference two different arrays. As `s1` is still a three-length, six-capacity slice, it still has some available buffer, so it keeps referencing the initial array. Also, the new backing array was made by copying the initial one from the first index of `s2`. That’s why the new array starts with element 1, not 0. + +To summarize, the slice length is the number of available elements in the slice, whereas the slice capacity is the number of elements in the backing array. Adding an element to a full slice (length == capacity) leads to creating a new backing array with a new capacity, copying all the elements from the previous array, and updating the slice pointer to the new array. diff --git a/docs/28-maps-memory-leaks.md b/docs/28-maps-memory-leaks.md new file mode 100644 index 0000000..30ed3bf --- /dev/null +++ b/docs/28-maps-memory-leaks.md @@ -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. + +
+ ![](img/map-leak-1.png) +
Figure 1: A hash table example with a focus on bucket 0.
+
+ +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: + +
+ ![](img/map-leak-2.png) +
Figure 2: In case of a bucket overflow, Go allocates a new bucket and links the previous bucket to it.
+
+ + +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. diff --git a/docs/89-benchmarks.md b/docs/89-benchmarks.md new file mode 100644 index 0000000..db5d6f7 --- /dev/null +++ b/docs/89-benchmarks.md @@ -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. + +
+ ![](img/matrix.png) +
Figure 1: Computing the sum of the first eight columns.
+
+ +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. \ No newline at end of file diff --git a/docs/98-profiling-execution-tracing.md b/docs/98-profiling-execution-tracing.md index f42661c..197b755 100644 --- a/docs/98-profiling-execution-tracing.md +++ b/docs/98-profiling-execution-tracing.md @@ -67,11 +67,17 @@ $ go tool pprof -http=:8080 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. -![](img/screen-pprof-cpu.png) +
+ ![](img/screen-pprof-cpu.png) +
Figure 1: The call graph of an application during 30 seconds.
+
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). -![](img/screen-pprof-sarama.png) +
+ ![](img/screen-pprof-sarama.png) +
Figure 2: Example call graph.
+
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. -![](img/screen-pprof-heap.png) +
+ ![](img/screen-pprof-heap.png) +
Figure 3: A heap graph.
+
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 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. -![](img/screen-pprof-heap-diff.png) +
+ ![](img/screen-pprof-heap-diff.png) +
Figure 4: The differences between the two heap profiles.
+
???+ 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. -![](img/screen-pprof-goroutines.png) +
+ ![](img/screen-pprof-goroutines.png) +
Figure 5: Goroutine graph.
+
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. -![](img/tracing.png) +
+ ![](img/tracing.png) +
Figure 6: Showing goroutine activity and runtime events such as a GC phase.
+
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. -![](img/screen-mergesort1.png) +
+ ![](img/screen-mergesort1.png) +
Figure 7: Too many small bars mean poorly parallelized execution.
+
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. -![](img/screen-mergesort11.png) +
+ ![](img/screen-mergesort11.png) +
Figure 8: About 50% of CPU time is spent handling goroutine switches.
+
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. -![](img/screen-mergesort2.png) +
+ ![](img/screen-mergesort2.png) +
Figure 9: The number of white spaces has been significantly reduced, proving that the CPU is more fully occupied.
+
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: -![](img/screen-tracing-user-level.png) +
+ ![](img/screen-tracing-user-level.png) +
Figure 10: Distribution of user-level tasks.
+
+ + 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. diff --git a/docs/book.md b/docs/book.md index d00fca5..6bfa141 100644 --- a/docs/book.md +++ b/docs/book.md @@ -1,6 +1,6 @@ # 100 Go Mistakes and How to Avoid Them -Community space of _100 Go Mistakes and How to Avoid Them_, published by Manning in 2022. +Community space of 📖 _100 Go Mistakes and How to Avoid Them_, published by Manning in 2022. ![](img/cover.png) @@ -8,28 +8,47 @@ Community space of _100 Go Mistakes and How to Avoid Them_, published by Manning If you're a Go developer looking to improve your skills, this book is for you. With a focus on practical examples, _100 Go Mistakes and How to Avoid Them_ 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. -## Quotes +Read a summary of the 100 mistakes [here](https://100go.co). -> This is an exceptional book. Usually, if a book contains either high-quality explanations or is written succinctly, I consider myself lucky to have found it. This one combines these two characteristics, which is super rare. It's another Go book for me and I still had quite a lot of "a-ha!" moments while reading it, and all of that without the unnecessary fluff, just straight to the point. +## Quotes and Ratings -– Krystian (Goodreads user) +!!! quote "Krystian (Goodreads user)" -> This should be the required reading for all Golang developers before they touch code in Production... It's the Golang equivalent of the legendary 'Effective Java' by Joshua Bloch. + This is an **exceptional book**. Usually, if a book contains either high-quality explanations or is written succinctly, I consider myself lucky to have found it. This one combines these two characteristics, which is **super rare**. It's another Go book for me and I still had quite a lot of "a-ha!" moments while reading it, and all of that without the unnecessary fluff, just straight to the point. -– _Neeraj Shah_ +!!! quote "Akash Chetty" -> Not having this will be the 101st mistake a Go programmer could make. + The book is completely **exceptional**, especially the examples carved out for each topic are really great. There is one topic that I struggled to understand is Concurrency but the way it is explained in this book is truly an art of genius. -– _Anupam Sengupta_ +!!! quote "Neeraj Shah" + + This should be the **required reading** for all Golang developers before they touch code in **Production**... It's the Golang equivalent of the legendary 'Effective Java' by Joshua Bloch. + +!!! quote "Anupam Sengupta" + + Not having this will be the **101st mistake** a Go programmer could make. + +
+ ![](img/ratings-manning.png) + ![](img/ratings-goodreads.png){width="300"} + ![](img/ratings-amazon.png){width="300"} +
Manning, Goodreads, and Amazon reviews: 4.7/5 avg rating
+
## Where to Buy? +
+ ![](img/cover-en.jpg){width="200"} + ![](img/cover-jp.jpg){width="200"} +
English and Japanese front covers
+
+ * _100 Go Mistakes and How to Avoid Them_ (🇬🇧 edition: paper, digital, or audiobook) - * [Manning](https://www.manning.com/books/100-go-mistakes-and-how-to-avoid-them) + * [Manning](https://www.manning.com/books/100-go-mistakes-and-how-to-avoid-them) (make sure to use my personal discount code for -35%: `au35har`) * [O’Reilly](https://www.oreilly.com/library/view/100-go-mistakes/9781617299599/) * Amazon: [.com](https://www.amazon.com/dp/1617299596), [.co.uk](https://www.amazon.co.uk/dp/B0BBSNJR6B), [.de](https://www.amazon.de/dp/B0BBHQD8BQ), [.fr](https://www.amazon.fr/100-Mistakes-How-Avoid-Them/dp/1617299596), [.in](https://www.amazon.in/dp/B0BBHQD8BQ), [.co.jp](https://www.amazon.co.jp/dp/B0BBHQD8BQ), [.es](https://www.amazon.es/dp/B0BBHQD8BQ), [.it](https://www.amazon.it/dp/B0BBHQD8BQ), [.com.br](https://www.amazon.com.br/dp/B0BBHQD8BQ) -* _Go言語100Tips 開発者にありがちな間違いへの対処法_ (🇯🇵 edition) +* _Go言語100Tips 開発者にありがちな間違いへの対処法_ (🇯🇵 edition: paper or digital) * Amazon: [.co.jp](https://www.amazon.co.jp/exec/obidos/ASIN/4295017531/) ## About the Author diff --git a/docs/chapter-1.md b/docs/chapter-1.md index c601548..f7b8547 100644 --- a/docs/chapter-1.md +++ b/docs/chapter-1.md @@ -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. @@ -129,4 +129,8 @@ 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. \ No newline at end of file +* 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). diff --git a/docs/img/cover-en.jpg b/docs/img/cover-en.jpg new file mode 100644 index 0000000..4f892e5 Binary files /dev/null and b/docs/img/cover-en.jpg differ diff --git a/docs/img/cover-jp.jpg b/docs/img/cover-jp.jpg new file mode 100644 index 0000000..dff9547 Binary files /dev/null and b/docs/img/cover-jp.jpg differ diff --git a/docs/img/map-leak-1.png b/docs/img/map-leak-1.png new file mode 100644 index 0000000..7c47815 Binary files /dev/null and b/docs/img/map-leak-1.png differ diff --git a/docs/img/map-leak-2.png b/docs/img/map-leak-2.png new file mode 100644 index 0000000..38aca8c Binary files /dev/null and b/docs/img/map-leak-2.png differ diff --git a/docs/img/matrix.png b/docs/img/matrix.png new file mode 100644 index 0000000..d7296de Binary files /dev/null and b/docs/img/matrix.png differ diff --git a/docs/img/ratings-amazon.png b/docs/img/ratings-amazon.png new file mode 100644 index 0000000..b3063b6 Binary files /dev/null and b/docs/img/ratings-amazon.png differ diff --git a/docs/img/ratings-goodreads.png b/docs/img/ratings-goodreads.png new file mode 100644 index 0000000..9de92da Binary files /dev/null and b/docs/img/ratings-goodreads.png differ diff --git a/docs/img/ratings-manning.png b/docs/img/ratings-manning.png new file mode 100644 index 0000000..89151b1 Binary files /dev/null and b/docs/img/ratings-manning.png differ diff --git a/docs/img/slice-1.png b/docs/img/slice-1.png new file mode 100644 index 0000000..a481b78 Binary files /dev/null and b/docs/img/slice-1.png differ diff --git a/docs/img/slice-2.png b/docs/img/slice-2.png new file mode 100644 index 0000000..2e6905c Binary files /dev/null and b/docs/img/slice-2.png differ diff --git a/docs/img/slice-3.png b/docs/img/slice-3.png new file mode 100644 index 0000000..8120a8a Binary files /dev/null and b/docs/img/slice-3.png differ diff --git a/docs/img/slice-4.png b/docs/img/slice-4.png new file mode 100644 index 0000000..580383c Binary files /dev/null and b/docs/img/slice-4.png differ diff --git a/docs/img/slice-5.png b/docs/img/slice-5.png new file mode 100644 index 0000000..d89e0cd Binary files /dev/null and b/docs/img/slice-5.png differ diff --git a/docs/img/slice-6.png b/docs/img/slice-6.png new file mode 100644 index 0000000..4d1573b Binary files /dev/null and b/docs/img/slice-6.png differ diff --git a/docs/img/slice-7.png b/docs/img/slice-7.png new file mode 100644 index 0000000..2a5fb6c Binary files /dev/null and b/docs/img/slice-7.png differ diff --git a/docs/img/slice-8.png b/docs/img/slice-8.png new file mode 100644 index 0000000..a438908 Binary files /dev/null and b/docs/img/slice-8.png differ diff --git a/docs/index.md b/docs/index.md index e89465b..a47fb8a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,14 +4,10 @@ This page is a summary of all the mistakes in the 100 Go Mistakes book. Meanwhil If you want to engage with the community (asking questions, discussing options, etc.), feel free to use the [Discussions](https://github.com/teivah/100-go-mistakes/discussions) space on the GitHub repo. -???+ info +???+ warning You're currently viewing a new version that I'm enriching with significantly more content. Yet, this version is still under development; please be gentle if you find an issue, and feel free to create a PR. - - - - ![](img/inside-cover.png) ## Code and Project Organization @@ -24,7 +20,7 @@ If you want to engage with the community (asking questions, discussing options, Variable shadowing occurs when a variable name is redeclared in an inner block, but this practice is prone to mistakes. Imposing a rule to forbid shadowed variables depends on personal taste. For example, sometimes it can be convenient to reuse an existing variable name like `err` for errors. Yet, in general, we should remain cautious because we now know that we can face a scenario where the code compiles, but the variable that receives the value is not the one expected. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/1-variable-shadowing/main.go) +[Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/1-variable-shadowing/main.go) ### Unnecessary nested code (#2) @@ -77,7 +73,7 @@ if s == "" { Writing readable code is an important challenge for every developer. Striving to reduce the number of nested blocks, aligning the happy path on the left, and returning as early as possible are concrete means to improve our code’s readability. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/2-nested-code/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/2-nested-code/main.go) ### Misusing init functions (#3) @@ -88,13 +84,14 @@ Writing readable code is an important challenge for every developer. Striving to An init function is a function used to initialize the state of an application. It takes no arguments and returns no result (a `func()` function). When a package is initialized, all the constant and variable declarations in the package are evaluated. Then, the init functions are executed. Init functions can lead to some issues: + * They can limit error management. * They can complicate how to implement tests (for example, an external dependency must be set up, which may not be necessary for the scope of unit tests). * If the initialization requires us to set a state, that has to be done through global variables. We should be cautious with init functions. They can be helpful in some situations, however, such as defining static configuration. Otherwise, and in most cases, we should handle initializations through ad hoc functions. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/3-init-functions/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/3-init-functions/) ### Overusing getters and setters (#4) @@ -129,7 +126,7 @@ We should be cautious when creating abstractions in our code (abstractions shoul Let’s not try to solve a problem abstractly but solve what has to be solved now. Last, but not least, if it’s unclear how an interface makes the code better, we should probably consider removing it to make our code simpler. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/5-interface-pollution/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/5-interface-pollution/) ### Interface on the producer side (#6) @@ -141,7 +138,7 @@ Interfaces are satisfied implicitly in Go, which tends to be a gamechanger compa An interface should live on the consumer side in most cases. However, in particular contexts (for example, when we know—not foresee—that an abstraction will be helpful for consumers), we may want to have it on the producer side. If we do, we should strive to keep it as minimal as possible, increasing its reusability potential and making it more easily composable. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/6-interface-producer/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/6-interface-producer/) ### Returning interfaces (#7) @@ -159,7 +156,7 @@ In most cases, we shouldn’t return interfaces but concrete implementations. Ot The `any` type can be helpful if there is a genuine need for accepting or returning any possible type (for instance, when it comes to marshaling or formatting). In general, we should avoid overgeneralizing the code we write at all costs. Perhaps a little bit of duplicated code might occasionally be better if it improves other aspects such as code expressiveness. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/8-any/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/8-any/main.go) ### Being confused about when to use generics (#9) @@ -169,9 +166,7 @@ The `any` type can be helpful if there is a genuine need for accepting or return Read the full section [here](9-generics.md). - - - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/9-generics/main.go) + [Source code :simple-github:](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) @@ -201,12 +196,13 @@ promoted to `Foo`. Therefore, Baz becomes available from Foo. What can we say about type embedding? First, let’s note that it’s rarely a necessity, and it means that whatever the use case, we can probably solve it as well without type embedding. Type embedding is mainly used for convenience: in most cases, to promote behaviors. If we decide to use type embedding, we need to keep two main constraints in mind: + * It shouldn’t be used solely as some syntactic sugar to simplify accessing a field (such as `Foo.Baz()` instead of `Foo.Bar.Baz()`). If this is the only rationale, let’s not embed the inner type and use a field instead. * It shouldn’t promote data (fields) or a behavior (methods) we want to hide from the outside: for example, if it allows clients to access a locking behavior that should remain private to the struct. Using type embedding consciously by keeping these constraints in mind can help avoid boilerplate code with additional forwarding methods. However, let’s make sure we don’t do it solely for cosmetics and not promote elements that should remain hidden. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/10-type-embedding/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/10-type-embedding/main.go) ### Not using the functional options pattern (#11) @@ -215,6 +211,7 @@ Using type embedding consciously by keeping these constraints in mind can help a To handle options conveniently and in an API-friendly manner, use the functional options pattern. 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: `type Option func(options *options)` error. For example, `WithPort` accepts an `int` argument that represents the port and returns an `Option` type that represents how to update the `options` struct. @@ -265,7 +262,7 @@ func NewServer(addr string, opts ...Option) ( *http.Server, error) { <1> The functional options pattern provides a handy and API-friendly way to handle options. Although the builder pattern can be a valid option, it has some minor downsides (having to pass a config struct that can be empty or a less handy way to handle error management) that tend to make the functional options pattern the idiomatic way to deal with these kind of problems in Go. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/11-functional-options/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/11-functional-options/) ### Project misorganization (project structure and package organization) (#12) @@ -292,7 +289,7 @@ Organizing a project isn’t straightforward, but following these rules should h Also, bear in mind that naming a package after what it provides and not what it contains can be an efficient way to increase its expressiveness. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/13-utility-packages/stringset.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/02-code-project-organization/13-utility-packages/stringset.go) ### Ignoring package name collisions (#14) @@ -329,16 +326,17 @@ Documenting our code shouldn’t be a constraint. We should take the opportunity A linter is an automatic tool to analyze code and catch errors. The scope of this section isn’t to give an exhaustive list of the existing linters; otherwise, it will become deprecated pretty quickly. But we should understand and remember why linters are essential for most Go projects. However, if you’re not a regular user of linters, here is a list that you may want to use daily: -* https://golang.org/cmd/vet/—A standard Go analyzer -* https://github.com/kisielk/errcheck—An error checker -* https://github.com/fzipp/gocyclo—A cyclomatic complexity analyzer -* https://github.com/jgautheron/goconst—A repeated string constants analyzer + +* [https://golang.org/cmd/vet](https://golang.org/cmd/vet)—A standard Go analyzer +* [https://github.com/kisielk/errcheck](https://github.com/kisielk/errcheck)—An error checker +* [https://github.com/fzipp/gocyclo](https://github.com/fzipp/gocyclo)—A cyclomatic complexity analyzer +* [https://github.com/jgautheron/goconst](https://github.com/jgautheron/goconst)—A repeated string constants analyzer * Besides linters, we should also use code formatters to fix code style. Here is a list of some code formatters for you to try: -* https://golang.org/cmd/gofmt/—A standard Go code formatter -* https://godoc.org/golang.org/x/tools/cmd/goimports—A standard Go imports formatter +* [https://golang.org/cmd/gofmt](https://golang.org/cmd/gofmt)—A standard Go code formatter +* [https://godoc.org/golang.org/x/tools/cmd/goimports](https://godoc.org/golang.org/x/tools/cmd/goimports)—A standard Go imports formatter * -Meanwhile, we should also look at golangci-lint (https://github.com/golangci/ golangci-lint). It’s a linting tool that provides a facade on top of many useful linters and formatters. Also, it allows running the linters in parallel to improve analysis speed, which is quite handy. +Meanwhile, we should also look at golangci-lint ([https://github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint)). It’s a linting tool that provides a facade on top of many useful linters and formatters. Also, it allows running the linters in parallel to improve analysis speed, which is quite handy. Linters and formatters are a powerful way to improve the quality and consistency of our codebase. Let’s take the time to understand which one we should use and make sure we automate their execution (such as a CI or Git precommit hook). @@ -348,18 +346,19 @@ Linters and formatters are a powerful way to improve the quality and consistency ???+ info "TL;DR" - When reading existing code, bear in mind that integer literals starting with 0 are octal numbers. Also, to improve readability, make octal integers explicit by prefixing them with `0o`. + When reading existing code, bear in mind that integer literals starting with `0` are octal numbers. Also, to improve readability, make octal integers explicit by prefixing them with `0o`. Octal numbers start with a 0 (e.g., `010` is equal to 8 in base 10). To improve readability and avoid potential mistakes for future code readers, we should make octal numbers explicit using the `0o` prefix (e.g., `0o10`). We should also note the other integer literal representations: + * _Binary_—Uses a `0b` or `0B` prefix (for example, `0b100` is equal to 4 in base 10) * _Hexadecimal_—Uses an `0x` or `0X` prefix (for example, `0xF` is equal to 15 in base 10) * _Imaginary_—Uses an `i` suffix (for example, `3i`) We can also use an underscore character (_) as a separator for readability. For example, we can write 1 billion this way: `1_000_000_000`. We can also use the underscore character with other representations (for example, `0b00_00_01`). - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/17-octal-literals/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/17-octal-literals/main.go) ### Neglecting integer overflows (#18) @@ -379,7 +378,7 @@ constant 2147483648 overflows int32 However, at run time, an integer overflow or underflow is silent; this does not lead to an application panic. It is essential to keep this behavior in mind, because it can lead to sneaky bugs (for example, an integer increment or addition of positive integers that leads to a negative result). - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/18-integer-overflows) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/18-integer-overflows) ### Not understanding floating-points (#19) @@ -399,11 +398,12 @@ fmt.Println(n * n) We may expect this code to print the result of 1.0001 * 1.0001 = 1.00020001, right? However, running it on most x86 processors prints 1.0002, instead. Because Go’s `float32` and `float64` types are approximations, we have to bear a few rules in mind: + * When comparing two floating-point numbers, check that their difference is within an acceptable range. * When performing additions or subtractions, group operations with a similar order of magnitude for better accuracy. * To favor accuracy, if a sequence of operations requires addition, subtraction, multiplication, or division, perform the multiplication and division operations first. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/19-floating-points/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/19-floating-points/main.go) ### Not understanding slice length and capacity (#20) @@ -411,9 +411,9 @@ Because Go’s `float32` and `float64` types are approximations, we have to bear Understanding the difference between slice length and capacity should be part of a Go developer’s core knowledge. The slice length is the number of available elements in the slice, whereas the slice capacity is the number of elements in the backing array. - +Read the full section [here](20-slice.md). - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/20-slice-length-cap/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/20-slice-length-cap/main.go) ### Inefficient slice initialization (#21) @@ -425,7 +425,7 @@ While initializing a slice using `make`, we can provide a length and an optional Our options are to allocate a slice with either a given capacity or a given length. Of these two solutions, we have seen that the second tends to be slightly faster. But using a given capacity and append can be easier to implement and read in some contexts. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/21-slice-init/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/21-slice-init/main.go) ### Being confused about nil vs. empty slice (#22) @@ -441,7 +441,7 @@ In Go, there is a distinction between nil and empty slices. A nil slice is equal The last option, `[]string{}`, should be avoided if we initialize the slice without elements. Finally, let’s check whether the libraries we use make the distinctions between nil and empty slices to prevent unexpected behaviors. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/22-nil-empty-slice/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/22-nil-empty-slice/) ### Not properly checking if a slice is empty (#23) @@ -453,7 +453,7 @@ To determine whether a slice has elements, we can either do it by checking if th Meanwhile, when designing interfaces, we should avoid distinguishing nil and empty slices, which leads to subtle programming errors. When returning slices, it should make neither a semantic nor a technical difference if we return a nil or empty slice. Both should mean the same thing for the callers. This principle is the same with maps. To check if a map is empty, check its length, not whether it’s nil. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/23-checking-slice-empty/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/23-checking-slice-empty/main.go) ### Not making slice copies correctly (#24) @@ -463,7 +463,7 @@ Meanwhile, when designing interfaces, we should avoid distinguishing nil and emp Copying elements from one slice to another is a reasonably frequent operation. When using copy, we must recall that the number of elements copied to the destination corresponds to the minimum between the two slices’ lengths. Also bear in mind that other alternatives exist to copy a slice, so we shouldn’t be surprised if we find them in a codebase. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/24-slice-copy/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/24-slice-copy/main.go) ### Unexpected side effects using slice append (#25) @@ -477,9 +477,9 @@ When using slicing, we must remember that we can face a situation leading to uni `s[low:high:max]` (full slice expression): This statement creates a slice similar to the one created with `s[low:high]`, except that the resulting slice’s capacity is equal to `max - low`. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/25-slice-append/main.go) + [Source code :simple-github:](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" @@ -489,13 +489,13 @@ When using slicing, we must remember that we can face a situation leading to uni Remember that slicing a large slice or array can lead to potential high memory consumption. The remaining space won’t be reclaimed by the GC, and we can keep a large backing array despite using only a few elements. Using a slice copy is the solution to prevent such a case. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/26-slice-memory-leak/capacity-leak) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/26-slice-memory-leak/capacity-leak) #### Slice and pointers When we use the slicing operation with pointers or structs with pointer fields, we need to know that the GC won’t reclaim these elements. In that case, the two options are to either perform a copy or explicitly mark the remaining elements or their fields to `nil`. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/26-slice-memory-leak/slice-pointers) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/26-slice-memory-leak/slice-pointers) ### Inefficient map initialization (#27) @@ -507,17 +507,17 @@ A map provides an unordered collection of key-value pairs in which all the keys 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. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/27-map-init/main_test.go) + [Source code :simple-github:](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. - +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) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/28-map-memory-leak/main.go) ### Comparing values incorrectly (#29) @@ -526,6 +526,7 @@ If we know up front the number of elements a map will contain, we should create To compare types in Go, you can use the == and != operators if two types are comparable: Booleans, numerals, strings, pointers, channels, and structs are composed entirely of comparable types. Otherwise, you can either use `reflect.DeepEqual` and pay the price of reflection or use custom implementations and libraries. It’s essential to understand how to use `==` and `!=` to make comparisons effectively. We can use these operators on operands that are comparable: + * _Booleans_—Compare whether two Booleans are equal. * _Numerics (int, float, and complex types)_—Compare whether two numerics are equal. * _Strings_—Compare whether two strings are equal. @@ -544,7 +545,7 @@ If performance is crucial at run time, implementing our custom method might be t One additional note: we must remember that the standard library has some existing comparison methods. For example, we can use the optimized `bytes.Compare` function to compare two slices of bytes. Before implementing a custom method, we need to make sure we don’t reinvent the wheel. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/29-comparing-values/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/03-data-types/29-comparing-values/main.go) ## Control Structures @@ -555,6 +556,7 @@ One additional note: we must remember that the standard library has some existin The value element in a `range` loop is a copy. Therefore, to mutate a struct, for example, access it via its index or via a classic `for` loop (unless the element or the field you want to modify is a pointer). A range loop allows iterating over different data structures: + * String * Array * Pointer to an array @@ -566,7 +568,7 @@ Compared to a classic for `loop`, a `range` loop is a convenient way to iterate Yet, we should remember that the value element in a range loop is a copy. Therefore, if the value is a struct we need to mutate, we will only update the copy, not the element itself, unless the value or field we modify is a pointer. The favored options are to access the element via the index using a range loop or a classic for loop. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/30-range-loop-element-copied/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/30-range-loop-element-copied/) ### Ignoring how arguments are evaluated in `range` loops (channels and arrays) (#31) @@ -588,7 +590,7 @@ for i, v := range a { This code updates the last index to 10. However, if we run this code, it does not print 10; it prints 2. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/31-range-loop-arg-evaluation/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/31-range-loop-arg-evaluation/) ### Ignoring the impacts of using pointer elements in `range` loops (#32) @@ -598,13 +600,14 @@ This code updates the last index to 10. However, if we run this code, it does no When iterating over a data structure using a `range` loop, we must recall that all the values are assigned to a unique variable with a single unique address. Therefore, if we store a pointer referencing this variable during each iteration, we will end up in a situation where we store the same pointer referencing the same element: the latest one. We can overcome this issue by forcing the creation of a local variable in the loop’s scope or creating a pointer referencing a slice element via its index. Both solutions are fine. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/32-range-loop-pointers/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/32-range-loop-pointers/) ### Making wrong assumptions during map iterations (ordering and map insert during iteration) (#33) ???+ info "TL;DR" To ensure predictable outputs when using maps, remember that a map data structure: + * Doesn’t order the data by keys * Doesn’t preserve the insertion order * Doesn’t have a deterministic iteration order @@ -612,7 +615,7 @@ When iterating over a data structure using a `range` loop, we must recall that a - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/33-map-iteration/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/33-map-iteration/main.go) ### Ignoring how the `break` statement works (#34) @@ -655,7 +658,7 @@ loop: Here, we associate the `loop` label with the `for` loop. Then, because we provide the `loop` label to the `break` statement, it breaks the loop, not the switch. Therefore, this new version will print `0 1 2`, as we expected. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/34-break/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/34-break/main.go) ### Using `defer` inside a loop (#35) @@ -712,7 +715,7 @@ func readFile(path string) error { Another solution is to make the `readFile` function a closure but intrinsically, this remains the same solution: adding another surrounding function to execute the `defer` calls during each iteration. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/35-defer-loop/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/04-control-structures/35-defer-loop/main.go) ## Strings @@ -731,7 +734,7 @@ As runes are everywhere in Go, it's important to understand the following: * Using UTF-8, a Unicode code point can be encoded into 1 to 4 bytes. * Using `len()` on a string in Go returns the number of bytes, not the number of runes. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/36-rune/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/36-rune/main.go) ### Inaccurate string iteration (#37) @@ -803,7 +806,7 @@ r := []rune(s)[4] fmt.Printf("%c\n", r) // o ``` - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/37-string-iteration/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/37-string-iteration/main.go) ### Misusing trim functions (#38) @@ -825,7 +828,7 @@ Conversely, `strings.TrimLeft` removes all the leading runes contained in a set. On the other side, `strings.TrimSuffix` / `strings.TrimPrefix` returns a string without the provided trailing suffix / prefix. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/38-trim/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/38-trim/main.go) ### Under-optimized strings concatenation (#39) @@ -895,7 +898,7 @@ As we can see, the latest version is by far the most efficient: 99% faster than `strings.Builder` is the recommended solution to concatenate a list of strings. Usually, this solution should be used within a loop. Indeed, if we just have to concatenate a few strings (such as a name and a surname), using `strings.Builder` is not recommended as doing so will make the code a bit less readable than using the `+=` operator or `fmt.Sprintf`. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/39-string-concat/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/39-string-concat/) ### Useless string conversions (#40) @@ -907,7 +910,7 @@ When choosing to work with a string or a `[]byte`, most programmers tend to favo When we’re wondering whether we should work with strings or `[]byte`, let’s recall that working with `[]byte` isn’t necessarily less convenient. Indeed, all the exported functions of the strings package also have alternatives in the `bytes` package: `Split`, `Count`, `Contains`, `Index`, and so on. Hence, whether we’re doing I/O or not, we should first check whether we could implement a whole workflow using bytes instead of strings and avoid the price of additional conversions. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/40-string-conversion/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/40-string-conversion/main.go) ### Substring and memory leaks (#41) @@ -919,7 +922,7 @@ In mistake [#26, “Slices and memory leaks,”](#slice-and-memory-leaks--26-) w We need to keep two things in mind while using the substring operation in Go. First, the interval provided is based on the number of bytes, not the number of runes. Second, a substring operation may lead to a memory leak as the resulting substring will share the same backing array as the initial string. The solutions to prevent this case from happening are to perform a string copy manually or to use `strings.Clone` from Go 1.18. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/41-substring-memory-leak/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/05-strings/41-substring-memory-leak/main.go) ## Functions and Methods @@ -963,7 +966,7 @@ A receiver _should_ be a value Of course, it’s impossible to be exhaustive, as there will always be edge cases, but this section’s goal was to provide guidance to cover most cases. By default, we can choose to go with a value receiver unless there’s a good reason not to do so. In doubt, we should use a pointer receiver. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/42-receiver/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/42-receiver/) ### Never using named result parameters (#43) @@ -986,7 +989,7 @@ In this example, we attach a name to the result parameter: `b`. When we call ret In some cases, named result parameters can also increase readability: for example, if two parameters have the same type. In other cases, they can also be used for convenience. Therefore, we should use named result parameters sparingly when there’s a clear benefit. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/43-named-result-parameters/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/43-named-result-parameters/main.go) ### Unintended side effects with named result parameters (#44) @@ -1017,7 +1020,7 @@ The error might not be obvious at first glance. Here, the error returned in the When using named result parameters, we must recall that each parameter is initialized to its zero value. As we have seen in this section, this can lead to subtle bugs that aren’t always straightforward to spot while reading code. Therefore, let’s remain cautious when using named result parameters, to avoid potential side effects. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/44-side-effects-named-result-parameters/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/44-side-effects-named-result-parameters/main.go) ### Returning a nil receiver (#45) @@ -1027,7 +1030,7 @@ When using named result parameters, we must recall that each parameter is initia - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/45-nil-receiver/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/45-nil-receiver/main.go) ### Using a filename as a function input (#46) @@ -1037,7 +1040,7 @@ When using named result parameters, we must recall that each parameter is initia Accepting a filename as a function input to read from a file should, in most cases, be considered a code smell (except in specific functions such as `os.Open`). Indeed, it makes unit tests more complex because we may have to create multiple files. It also reduces the reusability of a function (although not all functions are meant to be reused). Using the `io.Reader` interface abstracts the data source. Regardless of whether the input is a file, a string, an HTTP request, or a gRPC request, the implementation can be reused and easily tested. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/46-function-input/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/46-function-input/) ### Ignoring how `defer` arguments and receivers are evaluated (argument evaluation, pointer, and value receivers) (#47) @@ -1122,7 +1125,7 @@ Here, we wrap the calls to both `notify` and `incrementCounter` within a closure Let's also note this behavior applies with method receiver: the receiver is evaluated immediately. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/47-defer-evaluation/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/06-functions-methods/47-defer-evaluation/) ## Error Management @@ -1155,7 +1158,7 @@ main.main() Panicking in Go should be used sparingly. There are two prominent cases, one to signal a programmer error (e.g., [`sql.Register`](https://cs.opensource.google/go/go/+/refs/tags/go1.20.7:src/database/sql/sql.go;l=44) that panics if the driver is `nil` or has already been register) and another where our application fails to create a mandatory dependency. Hence, exceptional conditions that lead us to stop the application. In most other cases, error management should be done with a function that returns a proper error type as the last return argument. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/48-panic/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/48-panic/main.go) ### Ignoring when to wrap an error (#49) @@ -1170,7 +1173,7 @@ Since Go 1.13, the %w directive allows us to wrap errors conveniently. Error wra When handling an error, we can decide to wrap it. Wrapping is about adding additional context to an error and/or marking an error as a specific type. If we need to mark an error, we should create a custom error type. However, if we just want to add extra context, we should use fmt.Errorf with the %w directive as it doesn’t require creating a new error type. Yet, error wrapping creates potential coupling as it makes the source error available for the caller. If we want to prevent it, we shouldn’t use error wrapping but error transformation, for example, using fmt.Errorf with the %v directive. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/49-error-wrapping/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/49-error-wrapping/main.go) ### Comparing an error type inaccurately (#50) @@ -1180,7 +1183,7 @@ When handling an error, we can decide to wrap it. Wrapping is about adding addit - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/50-compare-error-type/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/50-compare-error-type/main.go) ### Comparing an error value inaccurately (#51) @@ -1203,7 +1206,7 @@ In general, the convention is to start with `Err` followed by the error type: he If we use error wrapping in our application with the `%w` directive and `fmt.Errorf`, checking an error against a specific value should be done using `errors.Is` instead of `==`. Thus, even if the sentinel error is wrapped, `errors.Is` can recursively unwrap it and compare each error in the chain against the provided value. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/51-comparing-error-value/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/51-comparing-error-value/main.go) ### Handling an error twice (#52) @@ -1215,7 +1218,7 @@ Handling an error multiple times is a mistake made frequently by developers, not Let's remind us that handling an error should be done only once. Logging an error is handling an error. Hence, we should either log or return an error. By doing this, we simplify our code and gain better insights into the error situation. Using error wrapping is the most convenient approach as it allows us to propagate the source error and add context to an error. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/52-handling-error-twice/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/52-handling-error-twice/main.go) ### Not handling an error (#53) @@ -1223,7 +1226,7 @@ Let's remind us that handling an error should be done only once. Logging an erro Ignoring an error, whether during a function call or in a `defer` function, should be done explicitly using the blank identifier. Otherwise, future readers may be confused about whether it was intentional or a miss. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/53-not-handling-error/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/53-not-handling-error/main.go) ### Not handling `defer` errors (#54) @@ -1260,7 +1263,7 @@ In terms of compilation and run time, this approach doesn’t change anything co _ = notify() ``` - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/54-defer-errors/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/07-error-management/54-defer-errors/main.go) ## Concurrency: Foundations @@ -1285,9 +1288,7 @@ In summary, concurrency provides a structure to solve a problem with parts that To be a proficient developer, you must acknowledge that concurrency isn’t always faster. Solutions involving parallelization of minimal workloads may not necessarily be faster than a sequential implementation. Benchmarking sequential versus concurrent solutions should be the way to validate assumptions. - - - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/56-faster/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/56-faster/) ### Being puzzled about when to use channels or mutexes (#57) @@ -1340,7 +1341,7 @@ A race condition occurs when the behavior depends on the sequence or the timing In summary, when we work in concurrent applications, it’s essential to understand that a data race is different from a race condition. A data race occurs when multiple goroutines simultaneously access the same memory location and at least one of them is writing. A data race means unexpected behavior. However, a data-race-free application doesn’t necessarily mean deterministic results. An application can be free of data races but still have behavior that depends on uncontrolled events (such as goroutine execution, how fast a message is published to a channel, or how long a call to a database lasts); this is a race condition. Understanding both concepts is crucial to becoming proficient in designing concurrent applications. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/58-races/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/58-races/) ### Not understanding the concurrency impacts of a workload type (#59) @@ -1360,8 +1361,7 @@ In programming, the execution time of a workload is limited by one of the follow If the workload executed by the workers is I/O-bound, the value mainly depends on the external system. Conversely, if the workload is CPU-bound, the optimal number of goroutines is close to the number of available CPU cores (a best practice can be to use `runtime.GOMAXPROCS`). Knowing the workload type (I/O or CPU) is crucial when designing concurrent applications. - - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/59-workload-type/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/59-workload-type/main.go) ### Misunderstanding Go contexts (#60) @@ -1403,7 +1403,7 @@ One thing to note is that the internal channel should be closed when a context i In summary, to be a proficient Go developer, we have to understand what a context is and how to use it. In general, a function that users wait for should take a context, as doing so allows upstream callers to decide when calling this function should be aborted. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/60-contexts/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/08-concurrency-foundations/60-contexts/main.go) ## Concurrency: Practice @@ -1449,7 +1449,7 @@ In the latter case, calling publish will return an error because we returned the In summary, propagating a context should be done cautiously. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/61-inappropriate-context/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/61-inappropriate-context/main.go) ### Starting a goroutine without knowing when to stop it (#62) @@ -1519,7 +1519,7 @@ Instead of signaling `watcher` that it’s time to close its resources, we now c In summary, let’s be mindful that a goroutine is a resource like any other that must eventually be closed to free memory or other resources. Starting a goroutine without knowing when to stop it is a design issue. Whenever a goroutine is started, we should have a clear plan about when it will stop. Last but not least, if a goroutine creates resources and its lifetime is bound to the lifetime of the application, it’s probably safer to wait for this goroutine to complete before exiting the application. This way, we can ensure that the resources can be freed. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/62-starting-goroutine/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/62-starting-goroutine/) ### Not being careful with goroutines and loop variables (#63) @@ -1527,7 +1527,7 @@ In summary, let’s be mindful that a goroutine is a resource like any other tha To avoid bugs with goroutines and loop variables, create local variables or call functions instead of closures. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/63-goroutines-loop-variables/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/63-goroutines-loop-variables/main.go) ### Expecting a deterministic behavior using select and channels (#64) @@ -1535,7 +1535,7 @@ In summary, let’s be mindful that a goroutine is a resource like any other tha Understanding that `select` with multiple channels chooses the case randomly if multiple options are possible prevents making wrong assumptions that can lead to subtle concurrency bugs. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/64-select-behavior/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/64-select-behavior/main.go) ### Not using notification channels (#65) @@ -1549,7 +1549,7 @@ In summary, let’s be mindful that a goroutine is a resource like any other tha Using nil channels should be part of your concurrency toolset because it allows you to _remove_ cases from `select` statements, for example. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/66-nil-channels/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/66-nil-channels/main.go) ### Being puzzled about channel size (#67) @@ -1565,7 +1565,7 @@ You should have a good reason to specify a channel size other than one for buffe Being aware that string formatting may lead to calling existing functions means watching out for possible deadlocks and other data races. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/68-string-formatting/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/68-string-formatting/main.go) ### Creating data races with append (#69) @@ -1573,7 +1573,7 @@ You should have a good reason to specify a channel size other than one for buffe Calling `append` isn’t always data-race-free; hence, it shouldn’t be used concurrently on a shared slice. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/69-data-race-append/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/69-data-race-append/main.go) ### Using mutexes inaccurately with slices and maps (#70) @@ -1581,7 +1581,7 @@ You should have a good reason to specify a channel size other than one for buffe Remembering that slices and maps are pointers can prevent common data races. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/70-mutex-slices-maps/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/70-mutex-slices-maps/main.go) ### Misusing `sync.WaitGroup` (#71) @@ -1589,7 +1589,7 @@ You should have a good reason to specify a channel size other than one for buffe To accurately use `sync.WaitGroup`, call the `Add` method before spinning up goroutines. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/71-wait-group/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/71-wait-group/main.go) ### Forgetting about `sync.Cond` (#72) @@ -1597,7 +1597,7 @@ You should have a good reason to specify a channel size other than one for buffe You can send repeated notifications to multiple goroutines with `sync.Cond`. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/72-cond/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/72-cond/main.go) ### Not using `errgroup` (#73) @@ -1605,7 +1605,7 @@ You should have a good reason to specify a channel size other than one for buffe You can synchronize a group of goroutines and handle errors and contexts with the `errgroup` package. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/73-errgroup/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/73-errgroup/main.go) ### Copying a `sync` type (#74) @@ -1613,7 +1613,7 @@ You should have a good reason to specify a channel size other than one for buffe `sync` types shouldn’t be copied. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/74-copying-sync/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/09-concurrency-practice/74-copying-sync/main.go) ## Standard Library @@ -1623,7 +1623,7 @@ You should have a good reason to specify a channel size other than one for buffe Remain cautious with functions accepting a `time.Duration`. Even though passing an integer is allowed, strive to use the time API to prevent any possible confusion. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/75-wrong-time-duration/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/75-wrong-time-duration/main.go) ### `time.After` and memory leaks (#76) @@ -1631,7 +1631,7 @@ You should have a good reason to specify a channel size other than one for buffe Avoiding calls to `time.After` in repeated functions (such as loops or HTTP handlers) can avoid peak memory consumption. The resources created by `time.After` are released only when the timer expires. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/76-time-after/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/76-time-after/main.go) ### JSON handling common mistakes (#77) @@ -1639,19 +1639,19 @@ You should have a good reason to specify a channel size other than one for buffe Be careful about using embedded fields in Go structs. Doing so may lead to sneaky bugs like an embedded time.Time field implementing the `json.Marshaler` interface, hence overriding the default marshaling behavior. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/77-json-handling/type-embedding/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/77-json-handling/type-embedding/main.go) * JSON and the monotonic clock When comparing two `time.Time` structs, recall that `time.Time` contains both a wall clock and a monotonic clock, and the comparison using the == operator is done on both clocks. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/77-json-handling/monotonic-clock/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/77-json-handling/monotonic-clock/main.go) * Map of `any` To avoid wrong assumptions when you provide a map while unmarshaling JSON data, remember that numerics are converted to `float64` by default. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/77-json-handling/map-any/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/77-json-handling/map-any/main.go) ### Common SQL mistakes (#78) @@ -1659,7 +1659,7 @@ You should have a good reason to specify a channel size other than one for buffe Call the `Ping` or `PingContext` method if you need to test your configuration and make sure a database is reachable. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/sql-open) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/sql-open) * Forgetting about connections pooling @@ -1669,19 +1669,19 @@ You should have a good reason to specify a channel size other than one for buffe Using SQL prepared statements makes queries more efficient and more secure. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/prepared-statements) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/prepared-statements) * Mishandling null values Deal with nullable columns in tables using pointers or `sql.NullXXX` types. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/null-values/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/null-values/main.go) * Not handling rows iteration errors Call the `Err` method of `sql.Rows` after row iterations to ensure that you haven’t missed an error while preparing the next row. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/rows-iterations-errors) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/78-sql/rows-iterations-errors) ### Not closing transient resources (HTTP body, `sql.Rows`, and `os.File`) (#79) @@ -1689,7 +1689,7 @@ You should have a good reason to specify a channel size other than one for buffe Eventually close all structs implementing `io.Closer` to avoid possible leaks. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/79-closing-resources/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/79-closing-resources/) ### Forgetting the return statement after replying to an HTTP request (#80) @@ -1697,7 +1697,7 @@ You should have a good reason to specify a channel size other than one for buffe To avoid unexpected behaviors in HTTP handler implementations, make sure you don’t miss the `return` statement if you want a handler to stop after `http.Error`. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/80-http-return/main.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/80-http-return/main.go) ### Using the default HTTP client and server (#81) @@ -1705,7 +1705,7 @@ You should have a good reason to specify a channel size other than one for buffe For production-grade applications, don’t use the default HTTP client and server implementations. These implementations are missing timeouts and behaviors that should be mandatory in production. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/81-default-http-client-server/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/10-standard-lib/81-default-http-client-server/) ## Testing @@ -1715,7 +1715,7 @@ You should have a good reason to specify a channel size other than one for buffe Categorizing tests using build flags, environment variables, or short mode makes the testing process more efficient. You can create test categories using build flags or environment variables (for example, unit versus integration tests) and differentiate short from long-running tests to decide which kinds of tests to execute. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/82-categorizing-tests/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/82-categorizing-tests/) ### Not enabling the race flag (#83) @@ -1735,7 +1735,7 @@ You should have a good reason to specify a channel size other than one for buffe Table-driven tests are an efficient way to group a set of similar tests to prevent code duplication and make future updates easier to handle. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/85-table-driven-tests/main_test.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/85-table-driven-tests/main_test.go) ### Sleeping in unit tests (#86) @@ -1743,7 +1743,7 @@ You should have a good reason to specify a channel size other than one for buffe Avoid sleeps using synchronization to make a test less flaky and more robust. If synchronization isn’t possible, consider a retry approach. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/86-sleeping/main_test.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/86-sleeping/main_test.go) ### Not dealing with the time API efficiently (#87) @@ -1751,45 +1751,33 @@ You should have a good reason to specify a channel size other than one for buffe Understanding how to deal with functions using the time API is another way to make a test less flaky. You can use standard techniques such as handling the time as part of a hidden dependency or asking clients to provide it. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/87-time-api/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/87-time-api/) ### Not using testing utility packages (`httptest` and `iotest`) (#88) * The `httptest` package is helpful for dealing with HTTP applications. It provides a set of utilities to test both clients and servers. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/88-utility-package/httptest/main_test.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/88-utility-package/httptest/main_test.go) * The `iotest` package helps write io.Reader and test that an application is tolerant to errors. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/88-utility-package/iotest/main_test.go) + [Source code :simple-github:](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 :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/89-benchmark/) ### Not exploring all the Go testing features (#90) @@ -1801,19 +1789,19 @@ You should have a good reason to specify a channel size other than one for buffe Place unit tests in a different package to enforce writing tests that focus on an exposed behavior, not internals. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/90-testing-features/different-package/main_test.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/90-testing-features/different-package/main_test.go) * Utility functions Handling errors using the `*testing.T` variable instead of the classic `if err != nil` makes code shorter and easier to read. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/90-testing-features/utility-function/main_test.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/90-testing-features/utility-function/main_test.go) * Setup and teardown You can use setup and teardown functions to configure a complex environment, such as in the case of integration tests. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/90-testing-features/setup-teardown/main_test.go) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/11-testing/90-testing-features/setup-teardown/main_test.go) ### Not using fuzzing (community mistake) @@ -1835,19 +1823,19 @@ Credits: [@jeromedoucet](https://github.com/jeromedoucet) Being conscious of the cache line concept is critical to understanding how to organize data in data-intensive applications. A CPU doesn’t fetch memory word by word; instead, it usually copies a memory block to a 64-byte cache line. To get the most out of each individual cache line, enforce spatial locality. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/91-cpu-caches/cache-line/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/91-cpu-caches/cache-line/) * Slice of structs vs. struct of slices - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/91-cpu-caches/slice-structs/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/91-cpu-caches/slice-structs/) * Predictability Making code predictable for the CPU can also be an efficient way to optimize certain functions. For example, a unit or constant stride is predictable for the CPU, but a non-unit stride (for example, a linked list) isn’t predictable. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/91-cpu-caches/predictability/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/91-cpu-caches/predictability/) * Cache placement policy @@ -1859,7 +1847,7 @@ Credits: [@jeromedoucet](https://github.com/jeromedoucet) Knowing that lower levels of CPU caches aren’t shared across all the cores helps avoid performance-degrading patterns such as false sharing while writing concurrency code. Sharing memory is an illusion. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/92-false-sharing/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/92-false-sharing/) ### Not taking into account instruction-level parallelism (#93) @@ -1867,7 +1855,7 @@ Credits: [@jeromedoucet](https://github.com/jeromedoucet) Use instruction-level parallelism (ILP) to optimize specific parts of your code to allow a CPU to execute as many parallel instructions as possible. Identifying data hazards is one of the main steps. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/93-instruction-level-parallelism/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/93-instruction-level-parallelism/) ### Not being aware of data alignment (#94) @@ -1875,7 +1863,7 @@ Credits: [@jeromedoucet](https://github.com/jeromedoucet) You can avoid common mistakes by remembering that in Go, basic types are aligned with their own size. For example, keep in mind that reorganizing the fields of a struct by size in descending order can lead to more compact structs (less memory allocation and potentially a better spatial locality). - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/94-data-alignment/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/94-data-alignment/) ### Not understanding stack vs. heap (#95) @@ -1883,7 +1871,7 @@ Credits: [@jeromedoucet](https://github.com/jeromedoucet) Understanding the fundamental differences between heap and stack should also be part of your core knowledge when optimizing a Go application. Stack allocations are almost free, whereas heap allocations are slower and rely on the GC to clean the memory. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/95-stack-heap/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/95-stack-heap/) ### Not knowing how to reduce allocations (API change, compiler optimizations, and `sync.Pool`) (#96) @@ -1891,7 +1879,7 @@ Credits: [@jeromedoucet](https://github.com/jeromedoucet) Reducing allocations is also an essential aspect of optimizing a Go application. This can be done in different ways, such as designing the API carefully to prevent sharing up, understanding the common Go compiler optimizations, and using `sync.Pool`. - [Source code](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/96-reduce-allocations/) + [Source code :simple-github:](https://github.com/teivah/100-go-mistakes/tree/master/src/12-optimizations/96-reduce-allocations/) ### Not relying on inlining (#97) @@ -1899,7 +1887,7 @@ Credits: [@jeromedoucet](https://github.com/jeromedoucet) Use the fast-path inlining technique to efficiently reduce the amortized time to call a function. -### [Not using Go diagnostics tooling](https://medium.com/@teivah/profiling-and-execution-tracing-in-go-a5e646970f5b) (#98) +### Not using Go diagnostics tooling (#98) ???+ info "TL;DR" diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000..c8fe0a7 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,3 @@ +.md-typeset figure img { + display: inline; +} diff --git a/mkdocs.yml b/mkdocs.yml index 48a9fde..a178f7c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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 @@ -50,6 +52,8 @@ extra: link: https://twitter.com/teivah - icon: fontawesome/brands/medium link: http://blog.teivah.io +extra_css: + - stylesheets/extra.css nav: - Book: - book.md @@ -59,6 +63,9 @@ nav: - index.md - Full Sections: - 9-generics.md + - 20-slice.md + - 28-maps-memory-leaks.md + - 89-benchmarks.md - 98-profiling-execution-tracing.md - zh.md - ❤️ Go Jobs: @@ -73,4 +80,11 @@ markdown_extensions: - pymdownx.inlinehilite - pymdownx.snippets - pymdownx.superfences + - tables + - attr_list + - md_in_html + - footnotes + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg copyright: Copyright © 2022 - 2023 Teiva Harsanyi diff --git a/site/20-slice/index.html b/site/20-slice/index.html new file mode 100644 index 0000000..de42ba9 --- /dev/null +++ b/site/20-slice/index.html @@ -0,0 +1,995 @@ + + + + + + + + + + + + + + + + + + + + + + + + 100 Go Mistakes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

Not understanding slice length and capacity

+

It’s pretty common for Go developers to mix slice length and capacity or not understand them thoroughly. Assimilating these two concepts is essential for efficiently handling core operations such as slice initialization and adding elements with append, copying, or slicing. This misunderstanding can lead to using slices suboptimally or even to memory leaks.

+

In Go, a slice is backed by an array. That means the slice’s data is stored contiguously in an array data structure. A slice also handles the logic of adding an element if the backing array is full or shrinking the backing array if it’s almost empty.

+

Internally, a slice holds a pointer to the backing array plus a length and a capacity. The length is the number of elements the slice contains, whereas the capacity is the number of elements in the backing array, counting from the first element in the slice. Let’s go through a few examples to make things clearer. First, let’s initialize a slice with a given length and capacity:

+
s := make([]int, 3, 6) // Three-length, six-capacity slice
+
+

The first argument, representing the length, is mandatory. However, the second argument representing the capacity is optional. Figure 1 shows the result of this code in memory.

+
+

+

+
Figure 1: A three-length, six-capacity slice.
+
+

In this case, make creates an array of six elements (the capacity). But because the length was set to 3, Go initializes only the first three elements. Also, because the slice is an []int type, the first three elements are initialized to the zeroed value of an int: 0. The grayed elements are allocated but not yet used.

+

If we print this slice, we get the elements within the range of the length, [0 0 0]. If we set s[1] to 1, the second element of the slice updates without impacting its length or capacity. Figure 2 illustrates this.

+
+

+

+
Figure 2: Updating the slice’s second element: s[1] = 1.
+
+

However, accessing an element outside the length range is forbidden, even though it’s already allocated in memory. For example, s[4] = 0 would lead to the following panic:

+
panic: runtime error: index out of range [4] with length 3
+
+

How can we use the remaining space of the slice? By using the append built-in function:

+
s = append(s, 2)
+
+

This code appends to the existing s slice a new element. It uses the first grayed element (which was allocated but not yet used) to store element 2, as figure 3 shows.

+
+

+

+
Figure 3: Appending an element to s.
+
+

The length of the slice is updated from 3 to 4 because the slice now contains four elements. Now, what happens if we add three more elements so that the backing array isn’t large enough?

+
s = append(s, 3, 4, 5)
+fmt.Println(s)
+
+

If we run this code, we see that the slice was able to cope with our request:

+
[0 1 0 2 3 4 5]
+
+

Because an array is a fixed-size structure, it can store the new elements until element 4. When we want to insert element 5, the array is already full: Go internally creates another array by doubling the capacity, copying all the elements, and then inserting element 5. Figure 4 shows this process.

+
+

+

+
Figure 4: Because the initial backing array is full, Go creates another array and copies all the elements.
+
+

The slice now references the new backing array. What will happen to the previous backing array? If it’s no longer referenced, it’s eventually freed by the garbage collector (GC) if allocated on the heap. (We discuss heap memory in mistake #95, “Not understanding stack vs. heap,” and we look at how the GC works in mistake #99, “Not understanding how the GC works.”)

+

What happens with slicing? Slicing is an operation done on an array or a slice, providing a half-open range; the first index is included, whereas the second is excluded. The following example shows the impact, and figure 5 displays the result in memory:

+
s1 := make([]int, 3, 6) // Three-length, six-capacity slice
+s2 := s1[1:3] // Slicing from indices 1 to 3
+
+
+

+

+
Figure 5: The slices s1 and s2 reference the same backing array with different lengths and capacities.
+
+

First, s1 is created as a three-length, six-capacity slice. When s2 is created by slicing s1, both slices reference the same backing array. However, s2 starts from a different index, 1. Therefore, its length and capacity (a two-length, five-capacity slice) differ from s1. If we update s1[1] or s2[0], the change is made to the same array, hence, visible in both slices, as figure 6 shows.

+
+

+

+
Figure 6: Because s1 and s2 are backed by the same array, updating a common element makes the change visible in both slices.
+
+

Now, what happens if we append an element to s2? Does the following code change s1 as well?

+
s2 = append(s2, 2)
+
+

The shared backing array is modified, but only the length of s2 changes. Figure 7 shows the result of appending an element to s2.

+
+

+

+
Figure 7: Appending an element to s2.
+
+

s1 remains a three-length, six-capacity slice. Therefore, if we print s1 and s2, the added element is only visible for s2:

+
s1=[0 1 0], s2=[1 0 2]
+
+

It’s important to understand this behavior so that we don’t make wrong assumptions while using append.

+
+Note +

In these examples, the backing array is internal and not available directly to the Go developer. The only exception is when a slice is created from slicing an existing array.

+
+

One last thing to note: what if we keep appending elements to s2 until the backing array is full? What will the state be, memory-wise? Let’s add three more elements so that the backing array will not have enough capacity:

+
s2 = append(s2, 3)
+s2 = append(s2, 4) // At this stage, the backing is already full
+s2 = append(s2, 5)
+
+

This code leads to creating another backing array. Figure 8 displays the results in memory.

+
+

+

+
Figure 8: Appending elements to s2 until the backing array is full.
+
+

s1 and s2 now reference two different arrays. As s1 is still a three-length, six-capacity slice, it still has some available buffer, so it keeps referencing the initial array. Also, the new backing array was made by copying the initial one from the first index of s2. That’s why the new array starts with element 1, not 0.

+

To summarize, the slice length is the number of available elements in the slice, whereas the slice capacity is the number of elements in the backing array. Adding an element to a full slice (length == capacity) leads to creating a new backing array with a new capacity, copying all the elements from the previous array, and updating the slice pointer to the new array.

+ + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/site/28-maps-memory-leaks/index.html b/site/28-maps-memory-leaks/index.html new file mode 100644 index 0000000..fc7fc68 --- /dev/null +++ b/site/28-maps-memory-leaks/index.html @@ -0,0 +1,1006 @@ + + + + + + + + + + + + + + + + + + + + + + + + 100 Go Mistakes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Skip to content + + +
+
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + +

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:

+
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. +
  3. Add 1 million elements.
  4. +
  5. Remove all the elements, and run a Garbage Collection (GC).
  6. +
+

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:

+
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 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 1: A hash table example with a focus on bucket 0.
+
+

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 2: In case of a bucket overflow, Go allocates a new bucket and links the previous bucket to it.
+
+

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:

+
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.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Stepmap[int][128]bytemap[int]*[128]byte
Allocate an empty map0 MB0 MB
Add 1 million elements461 MB182 MB
Remove all the elements and run a GC293 MB38 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.

+ + + + + + + + +
+
+ + +
+ +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/site/404.html b/site/404.html index 6834d85..3be8016 100644 --- a/site/404.html +++ b/site/404.html @@ -40,6 +40,8 @@ + + @@ -553,6 +555,66 @@ +
  • + + + + + Not understanding slice length and capacity (#20) + + + + +
  • + + + + + + + + + +
  • + + + + + Maps and memory leaks (#28) + + + + +
  • + + + + + + + + + +
  • + + + + + Writing inaccurate benchmarks (#89) + + + + +
  • + + + + + + + + +
  • @@ -755,7 +817,7 @@
    - + diff --git a/site/89-benchmarks/index.html b/site/89-benchmarks/index.html new file mode 100644 index 0000000..1259850 --- /dev/null +++ b/site/89-benchmarks/index.html @@ -0,0 +1,1279 @@ + + + + + + + + + + + + + + + + + + + + + + + + 100 Go Mistakes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + Skip to content + + +
    +
    + +
    + + + + + + +
    + + + + + + + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + + + + + +
    +
    + + + + + + + +

    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:

    +
    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:

    +
    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:

    +
    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?

    +
    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:

    +
    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:

    +
    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):

    +
    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:

    +
    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:

    +
    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. +
    3. Assign the latest result to a global variable.
    4. +
    +

    In our case, we write the following benchmark:

    +
    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”). 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 1: Computing the sum of the first eight columns.
    +
    +

    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:

    +
    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:

    +
    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.

    +
    +

    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:

    +
    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.

    + + + + + + + + +
    +
    + + +
    + +
    + + + +
    +
    +
    +
    + + + + + + + + + \ No newline at end of file diff --git a/site/9-generics/index.html b/site/9-generics/index.html index 1517bd1..a8aca38 100644 --- a/site/9-generics/index.html +++ b/site/9-generics/index.html @@ -14,7 +14,7 @@ - + @@ -46,6 +46,8 @@ + + @@ -89,7 +91,18 @@ - + @@ -657,6 +670,66 @@ +
  • + + + + + Not understanding slice length and capacity (#20) + + + + +
  • + + + + + + + + + +
  • + + + + + Maps and memory leaks (#28) + + + + +
  • + + + + + + + + + +
  • + + + + + Writing inaccurate benchmarks (#89) + + + + +
  • + + + + + + + + +
  • @@ -1115,11 +1188,11 @@
    - + - + \ No newline at end of file diff --git a/site/98-profiling-execution-tracing/index.html b/site/98-profiling-execution-tracing/index.html index 6109472..0f030e5 100644 --- a/site/98-profiling-execution-tracing/index.html +++ b/site/98-profiling-execution-tracing/index.html @@ -11,7 +11,7 @@ - + @@ -46,6 +46,8 @@ + + @@ -89,7 +91,18 @@ - + @@ -595,6 +608,66 @@ + +
  • + + + + + Not understanding slice length and capacity (#20) + + + + +
  • + + + + + + + + + +
  • + + + + + Maps and memory leaks (#28) + + + + +
  • + + + + + + + + + +
  • + + + + + Writing inaccurate benchmarks (#89) + + + + +
  • + + + + + + + + @@ -961,9 +1034,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 1: The call graph of an application during 30 seconds.
    +

    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 2: Example call graph.
    +

    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.

    Note @@ -984,7 +1065,11 @@

    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.

    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 go tool (the same command as in the previous section) to navigate into the data using the web UI.

    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 3: A heap graph.
    +

    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:

    • alloc_objects— Total number of objects allocated
    • @@ -1012,14 +1097,22 @@
      $ 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 4: The differences between the two heap profiles.
      +
      Note

      Another type of profiling related to the heap is allocs, 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.

      Goroutine Profiling

      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 5: Goroutine graph.
      +

      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.

      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.

      Block Profiling

      @@ -1077,13 +1170,29 @@

      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 6: Showing goroutine activity and runtime events such as a GC phase.
      +

      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 7: Too many small bars mean poorly parallelized execution.
      +

      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 8: About 50% of CPU time is spent handling goroutine switches.
      +

      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 9: The number of white spaces has been significantly reduced, proving that the CPU is more fully occupied.
      +

      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.

      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 runtime/trace package.

      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:

      @@ -1103,7 +1212,11 @@ world” is executed, which occupies the four CPU cores for approximately 40 ms. 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 10: Distribution of user-level tasks.
      +

      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.

      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?

        @@ -1190,11 +1303,11 @@ world” is executed, which occupies the four CPU cores for approximately 40 ms.
        - + - + \ No newline at end of file diff --git a/site/assets/images/social/20-slice.png b/site/assets/images/social/20-slice.png new file mode 100644 index 0000000..c36ca11 Binary files /dev/null and b/site/assets/images/social/20-slice.png differ diff --git a/site/assets/images/social/28-maps-memory-leaks.png b/site/assets/images/social/28-maps-memory-leaks.png new file mode 100644 index 0000000..eae2887 Binary files /dev/null and b/site/assets/images/social/28-maps-memory-leaks.png differ diff --git a/site/assets/images/social/89-benchmarks.png b/site/assets/images/social/89-benchmarks.png new file mode 100644 index 0000000..1730d93 Binary files /dev/null and b/site/assets/images/social/89-benchmarks.png differ diff --git a/site/assets/javascripts/glightbox.min.js b/site/assets/javascripts/glightbox.min.js new file mode 100644 index 0000000..614fb18 --- /dev/null +++ b/site/assets/javascripts/glightbox.min.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).GLightbox=t()}(this,(function(){"use strict";function e(t){return(e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(t)}function t(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){for(var i=0;i1&&void 0!==arguments[1]?arguments[1]:null,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,n=e[s]=e[s]||[],l={all:n,evt:null,found:null};return t&&i&&P(n)>0&&o(n,(function(e,n){if(e.eventName==t&&e.fn.toString()==i.toString())return l.found=!0,l.evt=n,!1})),l}function a(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=t.onElement,n=t.withCallback,s=t.avoidDuplicate,l=void 0===s||s,a=t.once,h=void 0!==a&&a,d=t.useCapture,c=void 0!==d&&d,u=arguments.length>2?arguments[2]:void 0,g=i||[];function v(e){T(n)&&n.call(u,e,this),h&&v.destroy()}return C(g)&&(g=document.querySelectorAll(g)),v.destroy=function(){o(g,(function(t){var i=r(t,e,v);i.found&&i.all.splice(i.evt,1),t.removeEventListener&&t.removeEventListener(e,v,c)}))},o(g,(function(t){var i=r(t,e,v);(t.addEventListener&&l&&!i.found||!l)&&(t.addEventListener(e,v,c),i.all.push({eventName:e,fn:v}))})),v}function h(e,t){o(t.split(" "),(function(t){return e.classList.add(t)}))}function d(e,t){o(t.split(" "),(function(t){return e.classList.remove(t)}))}function c(e,t){return e.classList.contains(t)}function u(e,t){for(;e!==document.body;){if(!(e=e.parentElement))return!1;if("function"==typeof e.matches?e.matches(t):e.msMatchesSelector(t))return e}}function g(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",i=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(!e||""===t)return!1;if("none"==t)return T(i)&&i(),!1;var n=x(),s=t.split(" ");o(s,(function(t){h(e,"g"+t)})),a(n,{onElement:e,avoidDuplicate:!1,once:!0,withCallback:function(e,t){o(s,(function(e){d(t,"g"+e)})),T(i)&&i()}})}function v(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";if(""==t)return e.style.webkitTransform="",e.style.MozTransform="",e.style.msTransform="",e.style.OTransform="",e.style.transform="",!1;e.style.webkitTransform=t,e.style.MozTransform=t,e.style.msTransform=t,e.style.OTransform=t,e.style.transform=t}function f(e){e.style.display="block"}function p(e){e.style.display="none"}function m(e){var t=document.createDocumentFragment(),i=document.createElement("div");for(i.innerHTML=e;i.firstChild;)t.appendChild(i.firstChild);return t}function y(){return{width:window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,height:window.innerHeight||document.documentElement.clientHeight||document.body.clientHeight}}function x(){var e,t=document.createElement("fakeelement"),i={animation:"animationend",OAnimation:"oAnimationEnd",MozAnimation:"animationend",WebkitAnimation:"webkitAnimationEnd"};for(e in i)if(void 0!==t.style[e])return i[e]}function b(e,t,i,n){if(e())t();else{var s;i||(i=100);var l=setInterval((function(){e()&&(clearInterval(l),s&&clearTimeout(s),t())}),i);n&&(s=setTimeout((function(){clearInterval(l)}),n))}}function S(e,t,i){if(I(e))console.error("Inject assets error");else if(T(t)&&(i=t,t=!1),C(t)&&t in window)T(i)&&i();else{var n;if(-1!==e.indexOf(".css")){if((n=document.querySelectorAll('link[href="'+e+'"]'))&&n.length>0)return void(T(i)&&i());var s=document.getElementsByTagName("head")[0],l=s.querySelectorAll('link[rel="stylesheet"]'),o=document.createElement("link");return o.rel="stylesheet",o.type="text/css",o.href=e,o.media="all",l?s.insertBefore(o,l[0]):s.appendChild(o),void(T(i)&&i())}if((n=document.querySelectorAll('script[src="'+e+'"]'))&&n.length>0){if(T(i)){if(C(t))return b((function(){return void 0!==window[t]}),(function(){i()})),!1;i()}}else{var r=document.createElement("script");r.type="text/javascript",r.src=e,r.onload=function(){if(T(i)){if(C(t))return b((function(){return void 0!==window[t]}),(function(){i()})),!1;i()}},document.body.appendChild(r)}}}function w(){return"navigator"in window&&window.navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(Android)|(PlayBook)|(BB10)|(BlackBerry)|(Opera Mini)|(IEMobile)|(webOS)|(MeeGo)/i)}function T(e){return"function"==typeof e}function C(e){return"string"==typeof e}function k(e){return!(!e||!e.nodeType||1!=e.nodeType)}function E(e){return Array.isArray(e)}function A(e){return e&&e.length&&isFinite(e.length)}function L(t){return"object"===e(t)&&null!=t&&!T(t)&&!E(t)}function I(e){return null==e}function O(e,t){return null!==e&&hasOwnProperty.call(e,t)}function P(e){if(L(e)){if(e.keys)return e.keys().length;var t=0;for(var i in e)O(e,i)&&t++;return t}return e.length}function M(e){return!isNaN(parseFloat(e))&&isFinite(e)}function z(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1,t=document.querySelectorAll(".gbtn[data-taborder]:not(.disabled)");if(!t.length)return!1;if(1==t.length)return t[0];"string"==typeof e&&(e=parseInt(e));var i=[];o(t,(function(e){i.push(e.getAttribute("data-taborder"))}));var n=Math.max.apply(Math,i.map((function(e){return parseInt(e)}))),s=e<0?1:e+1;s>n&&(s="1");var l=i.filter((function(e){return e>=parseInt(s)})),r=l.sort()[0];return document.querySelector('.gbtn[data-taborder="'.concat(r,'"]'))}function X(e){if(e.events.hasOwnProperty("keyboard"))return!1;e.events.keyboard=a("keydown",{onElement:window,withCallback:function(t,i){var n=(t=t||window.event).keyCode;if(9==n){var s=document.querySelector(".gbtn.focused");if(!s){var l=!(!document.activeElement||!document.activeElement.nodeName)&&document.activeElement.nodeName.toLocaleLowerCase();if("input"==l||"textarea"==l||"button"==l)return}t.preventDefault();var o=document.querySelectorAll(".gbtn[data-taborder]");if(!o||o.length<=0)return;if(!s){var r=z();return void(r&&(r.focus(),h(r,"focused")))}var a=z(s.getAttribute("data-taborder"));d(s,"focused"),a&&(a.focus(),h(a,"focused"))}39==n&&e.nextSlide(),37==n&&e.prevSlide(),27==n&&e.close()}})}function Y(e){return Math.sqrt(e.x*e.x+e.y*e.y)}function q(e,t){var i=function(e,t){var i=Y(e)*Y(t);if(0===i)return 0;var n=function(e,t){return e.x*t.x+e.y*t.y}(e,t)/i;return n>1&&(n=1),Math.acos(n)}(e,t);return function(e,t){return e.x*t.y-t.x*e.y}(e,t)>0&&(i*=-1),180*i/Math.PI}var N=function(){function e(i){t(this,e),this.handlers=[],this.el=i}return n(e,[{key:"add",value:function(e){this.handlers.push(e)}},{key:"del",value:function(e){e||(this.handlers=[]);for(var t=this.handlers.length;t>=0;t--)this.handlers[t]===e&&this.handlers.splice(t,1)}},{key:"dispatch",value:function(){for(var e=0,t=this.handlers.length;e=0)console.log("ignore drag for this touched element",e.target.nodeName.toLowerCase());else{this.now=Date.now(),this.x1=e.touches[0].pageX,this.y1=e.touches[0].pageY,this.delta=this.now-(this.last||this.now),this.touchStart.dispatch(e,this.element),null!==this.preTapPosition.x&&(this.isDoubleTap=this.delta>0&&this.delta<=250&&Math.abs(this.preTapPosition.x-this.x1)<30&&Math.abs(this.preTapPosition.y-this.y1)<30,this.isDoubleTap&&clearTimeout(this.singleTapTimeout)),this.preTapPosition.x=this.x1,this.preTapPosition.y=this.y1,this.last=this.now;var t=this.preV;if(e.touches.length>1){this._cancelLongTap(),this._cancelSingleTap();var i={x:e.touches[1].pageX-this.x1,y:e.touches[1].pageY-this.y1};t.x=i.x,t.y=i.y,this.pinchStartLen=Y(t),this.multipointStart.dispatch(e,this.element)}this._preventTap=!1,this.longTapTimeout=setTimeout(function(){this.longTap.dispatch(e,this.element),this._preventTap=!0}.bind(this),750)}}}},{key:"move",value:function(e){if(e.touches){var t=this.preV,i=e.touches.length,n=e.touches[0].pageX,s=e.touches[0].pageY;if(this.isDoubleTap=!1,i>1){var l=e.touches[1].pageX,o=e.touches[1].pageY,r={x:e.touches[1].pageX-n,y:e.touches[1].pageY-s};null!==t.x&&(this.pinchStartLen>0&&(e.zoom=Y(r)/this.pinchStartLen,this.pinch.dispatch(e,this.element)),e.angle=q(r,t),this.rotate.dispatch(e,this.element)),t.x=r.x,t.y=r.y,null!==this.x2&&null!==this.sx2?(e.deltaX=(n-this.x2+l-this.sx2)/2,e.deltaY=(s-this.y2+o-this.sy2)/2):(e.deltaX=0,e.deltaY=0),this.twoFingerPressMove.dispatch(e,this.element),this.sx2=l,this.sy2=o}else{if(null!==this.x2){e.deltaX=n-this.x2,e.deltaY=s-this.y2;var a=Math.abs(this.x1-this.x2),h=Math.abs(this.y1-this.y2);(a>10||h>10)&&(this._preventTap=!0)}else e.deltaX=0,e.deltaY=0;this.pressMove.dispatch(e,this.element)}this.touchMove.dispatch(e,this.element),this._cancelLongTap(),this.x2=n,this.y2=s,i>1&&e.preventDefault()}}},{key:"end",value:function(e){if(e.changedTouches){this._cancelLongTap();var t=this;e.touches.length<2&&(this.multipointEnd.dispatch(e,this.element),this.sx2=this.sy2=null),this.x2&&Math.abs(this.x1-this.x2)>30||this.y2&&Math.abs(this.y1-this.y2)>30?(e.direction=this._swipeDirection(this.x1,this.x2,this.y1,this.y2),this.swipeTimeout=setTimeout((function(){t.swipe.dispatch(e,t.element)}),0)):(this.tapTimeout=setTimeout((function(){t._preventTap||t.tap.dispatch(e,t.element),t.isDoubleTap&&(t.doubleTap.dispatch(e,t.element),t.isDoubleTap=!1)}),0),t.isDoubleTap||(t.singleTapTimeout=setTimeout((function(){t.singleTap.dispatch(e,t.element)}),250))),this.touchEnd.dispatch(e,this.element),this.preV.x=0,this.preV.y=0,this.zoom=1,this.pinchStartLen=null,this.x1=this.x2=this.y1=this.y2=null}}},{key:"cancelAll",value:function(){this._preventTap=!0,clearTimeout(this.singleTapTimeout),clearTimeout(this.tapTimeout),clearTimeout(this.longTapTimeout),clearTimeout(this.swipeTimeout)}},{key:"cancel",value:function(e){this.cancelAll(),this.touchCancel.dispatch(e,this.element)}},{key:"_cancelLongTap",value:function(){clearTimeout(this.longTapTimeout)}},{key:"_cancelSingleTap",value:function(){clearTimeout(this.singleTapTimeout)}},{key:"_swipeDirection",value:function(e,t,i,n){return Math.abs(e-t)>=Math.abs(i-n)?e-t>0?"Left":"Right":i-n>0?"Up":"Down"}},{key:"on",value:function(e,t){this[e]&&this[e].add(t)}},{key:"off",value:function(e,t){this[e]&&this[e].del(t)}},{key:"destroy",value:function(){return this.singleTapTimeout&&clearTimeout(this.singleTapTimeout),this.tapTimeout&&clearTimeout(this.tapTimeout),this.longTapTimeout&&clearTimeout(this.longTapTimeout),this.swipeTimeout&&clearTimeout(this.swipeTimeout),this.element.removeEventListener("touchstart",this.start),this.element.removeEventListener("touchmove",this.move),this.element.removeEventListener("touchend",this.end),this.element.removeEventListener("touchcancel",this.cancel),this.rotate.del(),this.touchStart.del(),this.multipointStart.del(),this.multipointEnd.del(),this.pinch.del(),this.swipe.del(),this.tap.del(),this.doubleTap.del(),this.longTap.del(),this.singleTap.del(),this.pressMove.del(),this.twoFingerPressMove.del(),this.touchMove.del(),this.touchEnd.del(),this.touchCancel.del(),this.preV=this.pinchStartLen=this.zoom=this.isDoubleTap=this.delta=this.last=this.now=this.tapTimeout=this.singleTapTimeout=this.longTapTimeout=this.swipeTimeout=this.x1=this.x2=this.y1=this.y2=this.preTapPosition=this.rotate=this.touchStart=this.multipointStart=this.multipointEnd=this.pinch=this.swipe=this.tap=this.doubleTap=this.longTap=this.singleTap=this.pressMove=this.touchMove=this.touchEnd=this.touchCancel=this.twoFingerPressMove=null,window.removeEventListener("scroll",this._cancelAllHandler),null}}]),e}();function W(e){var t=function(){var e,t=document.createElement("fakeelement"),i={transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"};for(e in i)if(void 0!==t.style[e])return i[e]}(),i=window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth,n=c(e,"gslide-media")?e:e.querySelector(".gslide-media"),s=u(n,".ginner-container"),l=e.querySelector(".gslide-description");i>769&&(n=s),h(n,"greset"),v(n,"translate3d(0, 0, 0)"),a(t,{onElement:n,once:!0,withCallback:function(e,t){d(n,"greset")}}),n.style.opacity="",l&&(l.style.opacity="")}function B(e){if(e.events.hasOwnProperty("touch"))return!1;var t,i,n,s=y(),l=s.width,o=s.height,r=!1,a=null,g=null,f=null,p=!1,m=1,x=1,b=!1,S=!1,w=null,T=null,C=null,k=null,E=0,A=0,L=!1,I=!1,O={},P={},M=0,z=0,X=document.getElementById("glightbox-slider"),Y=document.querySelector(".goverlay"),q=new _(X,{touchStart:function(t){if(r=!0,(c(t.targetTouches[0].target,"ginner-container")||u(t.targetTouches[0].target,".gslide-desc")||"a"==t.targetTouches[0].target.nodeName.toLowerCase())&&(r=!1),u(t.targetTouches[0].target,".gslide-inline")&&!c(t.targetTouches[0].target.parentNode,"gslide-inline")&&(r=!1),r){if(P=t.targetTouches[0],O.pageX=t.targetTouches[0].pageX,O.pageY=t.targetTouches[0].pageY,M=t.targetTouches[0].clientX,z=t.targetTouches[0].clientY,a=e.activeSlide,g=a.querySelector(".gslide-media"),n=a.querySelector(".gslide-inline"),f=null,c(g,"gslide-image")&&(f=g.querySelector("img")),(window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth)>769&&(g=a.querySelector(".ginner-container")),d(Y,"greset"),t.pageX>20&&t.pageXo){var a=O.pageX-P.pageX;if(Math.abs(a)<=13)return!1}p=!0;var h,d=s.targetTouches[0].clientX,c=s.targetTouches[0].clientY,u=M-d,m=z-c;if(Math.abs(u)>Math.abs(m)?(L=!1,I=!0):(I=!1,L=!0),t=P.pageX-O.pageX,E=100*t/l,i=P.pageY-O.pageY,A=100*i/o,L&&f&&(h=1-Math.abs(i)/o,Y.style.opacity=h,e.settings.touchFollowAxis&&(E=0)),I&&(h=1-Math.abs(t)/l,g.style.opacity=h,e.settings.touchFollowAxis&&(A=0)),!f)return v(g,"translate3d(".concat(E,"%, 0, 0)"));v(g,"translate3d(".concat(E,"%, ").concat(A,"%, 0)"))}},touchEnd:function(){if(r){if(p=!1,S||b)return C=w,void(k=T);var t=Math.abs(parseInt(A)),i=Math.abs(parseInt(E));if(!(t>29&&f))return t<29&&i<25?(h(Y,"greset"),Y.style.opacity=1,W(g)):void 0;e.close()}},multipointEnd:function(){setTimeout((function(){b=!1}),50)},multipointStart:function(){b=!0,m=x||1},pinch:function(e){if(!f||p)return!1;b=!0,f.scaleX=f.scaleY=m*e.zoom;var t=m*e.zoom;if(S=!0,t<=1)return S=!1,t=1,k=null,C=null,w=null,T=null,void f.setAttribute("style","");t>4.5&&(t=4.5),f.style.transform="scale3d(".concat(t,", ").concat(t,", 1)"),x=t},pressMove:function(e){if(S&&!b){var t=P.pageX-O.pageX,i=P.pageY-O.pageY;C&&(t+=C),k&&(i+=k),w=t,T=i;var n="translate3d(".concat(t,"px, ").concat(i,"px, 0)");x&&(n+=" scale3d(".concat(x,", ").concat(x,", 1)")),v(f,n)}},swipe:function(t){if(!S)if(b)b=!1;else{if("Left"==t.direction){if(e.index==e.elements.length-1)return W(g);e.nextSlide()}if("Right"==t.direction){if(0==e.index)return W(g);e.prevSlide()}}}});e.events.touch=q}var H=function(){function e(i,n){var s=this,l=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;if(t(this,e),this.img=i,this.slide=n,this.onclose=l,this.img.setZoomEvents)return!1;this.active=!1,this.zoomedIn=!1,this.dragging=!1,this.currentX=null,this.currentY=null,this.initialX=null,this.initialY=null,this.xOffset=0,this.yOffset=0,this.img.addEventListener("mousedown",(function(e){return s.dragStart(e)}),!1),this.img.addEventListener("mouseup",(function(e){return s.dragEnd(e)}),!1),this.img.addEventListener("mousemove",(function(e){return s.drag(e)}),!1),this.img.addEventListener("click",(function(e){return s.slide.classList.contains("dragging-nav")?(s.zoomOut(),!1):s.zoomedIn?void(s.zoomedIn&&!s.dragging&&s.zoomOut()):s.zoomIn()}),!1),this.img.setZoomEvents=!0}return n(e,[{key:"zoomIn",value:function(){var e=this.widowWidth();if(!(this.zoomedIn||e<=768)){var t=this.img;if(t.setAttribute("data-style",t.getAttribute("style")),t.style.maxWidth=t.naturalWidth+"px",t.style.maxHeight=t.naturalHeight+"px",t.naturalWidth>e){var i=e/2-t.naturalWidth/2;this.setTranslate(this.img.parentNode,i,0)}this.slide.classList.add("zoomed"),this.zoomedIn=!0}}},{key:"zoomOut",value:function(){this.img.parentNode.setAttribute("style",""),this.img.setAttribute("style",this.img.getAttribute("data-style")),this.slide.classList.remove("zoomed"),this.zoomedIn=!1,this.currentX=null,this.currentY=null,this.initialX=null,this.initialY=null,this.xOffset=0,this.yOffset=0,this.onclose&&"function"==typeof this.onclose&&this.onclose()}},{key:"dragStart",value:function(e){e.preventDefault(),this.zoomedIn?("touchstart"===e.type?(this.initialX=e.touches[0].clientX-this.xOffset,this.initialY=e.touches[0].clientY-this.yOffset):(this.initialX=e.clientX-this.xOffset,this.initialY=e.clientY-this.yOffset),e.target===this.img&&(this.active=!0,this.img.classList.add("dragging"))):this.active=!1}},{key:"dragEnd",value:function(e){var t=this;e.preventDefault(),this.initialX=this.currentX,this.initialY=this.currentY,this.active=!1,setTimeout((function(){t.dragging=!1,t.img.isDragging=!1,t.img.classList.remove("dragging")}),100)}},{key:"drag",value:function(e){this.active&&(e.preventDefault(),"touchmove"===e.type?(this.currentX=e.touches[0].clientX-this.initialX,this.currentY=e.touches[0].clientY-this.initialY):(this.currentX=e.clientX-this.initialX,this.currentY=e.clientY-this.initialY),this.xOffset=this.currentX,this.yOffset=this.currentY,this.img.isDragging=!0,this.dragging=!0,this.setTranslate(this.img,this.currentX,this.currentY))}},{key:"onMove",value:function(e){if(this.zoomedIn){var t=e.clientX-this.img.naturalWidth/2,i=e.clientY-this.img.naturalHeight/2;this.setTranslate(this.img,t,i)}}},{key:"setTranslate",value:function(e,t,i){e.style.transform="translate3d("+t+"px, "+i+"px, 0)"}},{key:"widowWidth",value:function(){return window.innerWidth||document.documentElement.clientWidth||document.body.clientWidth}}]),e}(),V=function(){function e(){var i=this,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};t(this,e);var s=n.dragEl,l=n.toleranceX,o=void 0===l?40:l,r=n.toleranceY,a=void 0===r?65:r,h=n.slide,d=void 0===h?null:h,c=n.instance,u=void 0===c?null:c;this.el=s,this.active=!1,this.dragging=!1,this.currentX=null,this.currentY=null,this.initialX=null,this.initialY=null,this.xOffset=0,this.yOffset=0,this.direction=null,this.lastDirection=null,this.toleranceX=o,this.toleranceY=a,this.toleranceReached=!1,this.dragContainer=this.el,this.slide=d,this.instance=u,this.el.addEventListener("mousedown",(function(e){return i.dragStart(e)}),!1),this.el.addEventListener("mouseup",(function(e){return i.dragEnd(e)}),!1),this.el.addEventListener("mousemove",(function(e){return i.drag(e)}),!1)}return n(e,[{key:"dragStart",value:function(e){if(this.slide.classList.contains("zoomed"))this.active=!1;else{"touchstart"===e.type?(this.initialX=e.touches[0].clientX-this.xOffset,this.initialY=e.touches[0].clientY-this.yOffset):(this.initialX=e.clientX-this.xOffset,this.initialY=e.clientY-this.yOffset);var t=e.target.nodeName.toLowerCase();e.target.classList.contains("nodrag")||u(e.target,".nodrag")||-1!==["input","select","textarea","button","a"].indexOf(t)?this.active=!1:(e.preventDefault(),(e.target===this.el||"img"!==t&&u(e.target,".gslide-inline"))&&(this.active=!0,this.el.classList.add("dragging"),this.dragContainer=u(e.target,".ginner-container")))}}},{key:"dragEnd",value:function(e){var t=this;e&&e.preventDefault(),this.initialX=0,this.initialY=0,this.currentX=null,this.currentY=null,this.initialX=null,this.initialY=null,this.xOffset=0,this.yOffset=0,this.active=!1,this.doSlideChange&&(this.instance.preventOutsideClick=!0,"right"==this.doSlideChange&&this.instance.prevSlide(),"left"==this.doSlideChange&&this.instance.nextSlide()),this.doSlideClose&&this.instance.close(),this.toleranceReached||this.setTranslate(this.dragContainer,0,0,!0),setTimeout((function(){t.instance.preventOutsideClick=!1,t.toleranceReached=!1,t.lastDirection=null,t.dragging=!1,t.el.isDragging=!1,t.el.classList.remove("dragging"),t.slide.classList.remove("dragging-nav"),t.dragContainer.style.transform="",t.dragContainer.style.transition=""}),100)}},{key:"drag",value:function(e){if(this.active){e.preventDefault(),this.slide.classList.add("dragging-nav"),"touchmove"===e.type?(this.currentX=e.touches[0].clientX-this.initialX,this.currentY=e.touches[0].clientY-this.initialY):(this.currentX=e.clientX-this.initialX,this.currentY=e.clientY-this.initialY),this.xOffset=this.currentX,this.yOffset=this.currentY,this.el.isDragging=!0,this.dragging=!0,this.doSlideChange=!1,this.doSlideClose=!1;var t=Math.abs(this.currentX),i=Math.abs(this.currentY);if(t>0&&t>=Math.abs(this.currentY)&&(!this.lastDirection||"x"==this.lastDirection)){this.yOffset=0,this.lastDirection="x",this.setTranslate(this.dragContainer,this.currentX,0);var n=this.shouldChange();if(!this.instance.settings.dragAutoSnap&&n&&(this.doSlideChange=n),this.instance.settings.dragAutoSnap&&n)return this.instance.preventOutsideClick=!0,this.toleranceReached=!0,this.active=!1,this.instance.preventOutsideClick=!0,this.dragEnd(null),"right"==n&&this.instance.prevSlide(),void("left"==n&&this.instance.nextSlide())}if(this.toleranceY>0&&i>0&&i>=t&&(!this.lastDirection||"y"==this.lastDirection)){this.xOffset=0,this.lastDirection="y",this.setTranslate(this.dragContainer,0,this.currentY);var s=this.shouldClose();return!this.instance.settings.dragAutoSnap&&s&&(this.doSlideClose=!0),void(this.instance.settings.dragAutoSnap&&s&&this.instance.close())}}}},{key:"shouldChange",value:function(){var e=!1;if(Math.abs(this.currentX)>=this.toleranceX){var t=this.currentX>0?"right":"left";("left"==t&&this.slide!==this.slide.parentNode.lastChild||"right"==t&&this.slide!==this.slide.parentNode.firstChild)&&(e=t)}return e}},{key:"shouldClose",value:function(){var e=!1;return Math.abs(this.currentY)>=this.toleranceY&&(e=!0),e}},{key:"setTranslate",value:function(e,t,i){var n=arguments.length>3&&void 0!==arguments[3]&&arguments[3];e.style.transition=n?"all .2s ease":"",e.style.transform="translate3d(".concat(t,"px, ").concat(i,"px, 0)")}}]),e}();function j(e,t,i,n){var s=e.querySelector(".gslide-media"),l=new Image,o="gSlideTitle_"+i,r="gSlideDesc_"+i;l.addEventListener("load",(function(){T(n)&&n()}),!1),l.src=t.href,""!=t.sizes&&""!=t.srcset&&(l.sizes=t.sizes,l.srcset=t.srcset),l.alt="",I(t.alt)||""===t.alt||(l.alt=t.alt),""!==t.title&&l.setAttribute("aria-labelledby",o),""!==t.description&&l.setAttribute("aria-describedby",r),t.hasOwnProperty("_hasCustomWidth")&&t._hasCustomWidth&&(l.style.width=t.width),t.hasOwnProperty("_hasCustomHeight")&&t._hasCustomHeight&&(l.style.height=t.height),s.insertBefore(l,s.firstChild)}function F(e,t,i,n){var s=this,l=e.querySelector(".ginner-container"),o="gvideo"+i,r=e.querySelector(".gslide-media"),a=this.getAllPlayers();h(l,"gvideo-container"),r.insertBefore(m('
        '),r.firstChild);var d=e.querySelector(".gvideo-wrapper");S(this.settings.plyr.css,"Plyr");var c=t.href,u=location.protocol.replace(":",""),g="",v="",f=!1;"file"==u&&(u="http"),r.style.maxWidth=t.width,S(this.settings.plyr.js,"Plyr",(function(){if(c.match(/vimeo\.com\/([0-9]*)/)){var l=/vimeo.*\/(\d+)/i.exec(c);g="vimeo",v=l[1]}if(c.match(/(youtube\.com|youtube-nocookie\.com)\/watch\?v=([a-zA-Z0-9\-_]+)/)||c.match(/youtu\.be\/([a-zA-Z0-9\-_]+)/)||c.match(/(youtube\.com|youtube-nocookie\.com)\/embed\/([a-zA-Z0-9\-_]+)/)){var r=function(e){var t="";t=void 0!==(e=e.replace(/(>|<)/gi,"").split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/))[2]?(t=e[2].split(/[^0-9a-z_\-]/i))[0]:e;return t}(c);g="youtube",v=r}if(null!==c.match(/\.(mp4|ogg|webm|mov)$/)){g="local";var u='")}var w=f||m('
        '));h(d,"".concat(g,"-video gvideo")),d.appendChild(w),d.setAttribute("data-id",o),d.setAttribute("data-index",i);var C=O(s.settings.plyr,"config")?s.settings.plyr.config:{},k=new Plyr("#"+o,C);k.on("ready",(function(e){var t=e.detail.plyr;a[o]=t,T(n)&&n()})),b((function(){return e.querySelector("iframe")&&"true"==e.querySelector("iframe").dataset.ready}),(function(){s.resize(e)})),k.on("enterfullscreen",R),k.on("exitfullscreen",R)}))}function R(e){var t=u(e.target,".gslide-media");"enterfullscreen"==e.type&&h(t,"fullscreen"),"exitfullscreen"==e.type&&d(t,"fullscreen")}function G(e,t,i,n){var s,l=this,o=e.querySelector(".gslide-media"),r=!(!O(t,"href")||!t.href)&&t.href.split("#").pop().trim(),d=!(!O(t,"content")||!t.content)&&t.content;if(d&&(C(d)&&(s=m('
        '.concat(d,"
        "))),k(d))){"none"==d.style.display&&(d.style.display="block");var c=document.createElement("div");c.className="ginlined-content",c.appendChild(d),s=c}if(r){var u=document.getElementById(r);if(!u)return!1;var g=u.cloneNode(!0);g.style.height=t.height,g.style.maxWidth=t.width,h(g,"ginlined-content"),s=g}if(!s)return console.error("Unable to append inline slide content",t),!1;o.style.height=t.height,o.style.width=t.width,o.appendChild(s),this.events["inlineclose"+r]=a("click",{onElement:o.querySelectorAll(".gtrigger-close"),withCallback:function(e){e.preventDefault(),l.close()}}),T(n)&&n()}function Z(e,t,i,n){var s=e.querySelector(".gslide-media"),l=function(e){var t=e.url,i=e.allow,n=e.callback,s=e.appendTo,l=document.createElement("iframe");return l.className="vimeo-video gvideo",l.src=t,l.style.width="100%",l.style.height="100%",i&&l.setAttribute("allow",i),l.onload=function(){h(l,"node-ready"),T(n)&&n()},s&&s.appendChild(l),l}({url:t.href,callback:n});s.parentNode.style.maxWidth=t.width,s.parentNode.style.height=t.height,s.appendChild(l)}var $=function(){function e(){var i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};t(this,e),this.defaults={href:"",sizes:"",srcset:"",title:"",type:"",description:"",alt:"",descPosition:"bottom",effect:"",width:"",height:"",content:!1,zoomable:!0,draggable:!0},L(i)&&(this.defaults=l(this.defaults,i))}return n(e,[{key:"sourceType",value:function(e){var t=e;if(null!==(e=e.toLowerCase()).match(/\.(jpeg|jpg|jpe|gif|png|apn|webp|avif|svg)/))return"image";if(e.match(/(youtube\.com|youtube-nocookie\.com)\/watch\?v=([a-zA-Z0-9\-_]+)/)||e.match(/youtu\.be\/([a-zA-Z0-9\-_]+)/)||e.match(/(youtube\.com|youtube-nocookie\.com)\/embed\/([a-zA-Z0-9\-_]+)/))return"video";if(e.match(/vimeo\.com\/([0-9]*)/))return"video";if(null!==e.match(/\.(mp4|ogg|webm|mov)/))return"video";if(null!==e.match(/\.(mp3|wav|wma|aac|ogg)/))return"audio";if(e.indexOf("#")>-1&&""!==t.split("#").pop().trim())return"inline";return e.indexOf("goajax=true")>-1?"ajax":"external"}},{key:"parseConfig",value:function(e,t){var i=this,n=l({descPosition:t.descPosition},this.defaults);if(L(e)&&!k(e)){O(e,"type")||(O(e,"content")&&e.content?e.type="inline":O(e,"href")&&(e.type=this.sourceType(e.href)));var s=l(n,e);return this.setSize(s,t),s}var r="",a=e.getAttribute("data-glightbox"),h=e.nodeName.toLowerCase();if("a"===h&&(r=e.href),"img"===h&&(r=e.src,n.alt=e.alt),n.href=r,o(n,(function(s,l){O(t,l)&&"width"!==l&&(n[l]=t[l]);var o=e.dataset[l];I(o)||(n[l]=i.sanitizeValue(o))})),n.content&&(n.type="inline"),!n.type&&r&&(n.type=this.sourceType(r)),I(a)){if(!n.title&&"a"==h){var d=e.title;I(d)||""===d||(n.title=d)}if(!n.title&&"img"==h){var c=e.alt;I(c)||""===c||(n.title=c)}}else{var u=[];o(n,(function(e,t){u.push(";\\s?"+t)})),u=u.join("\\s?:|"),""!==a.trim()&&o(n,(function(e,t){var s=a,l=new RegExp("s?"+t+"s?:s?(.*?)("+u+"s?:|$)"),o=s.match(l);if(o&&o.length&&o[1]){var r=o[1].trim().replace(/;\s*$/,"");n[t]=i.sanitizeValue(r)}}))}if(n.description&&"."===n.description.substring(0,1)){var g;try{g=document.querySelector(n.description).innerHTML}catch(e){if(!(e instanceof DOMException))throw e}g&&(n.description=g)}if(!n.description){var v=e.querySelector(".glightbox-desc");v&&(n.description=v.innerHTML)}return this.setSize(n,t,e),this.slideConfig=n,n}},{key:"setSize",value:function(e,t){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null,n="video"==e.type?this.checkSize(t.videosWidth):this.checkSize(t.width),s=this.checkSize(t.height);return e.width=O(e,"width")&&""!==e.width?this.checkSize(e.width):n,e.height=O(e,"height")&&""!==e.height?this.checkSize(e.height):s,i&&"image"==e.type&&(e._hasCustomWidth=!!i.dataset.width,e._hasCustomHeight=!!i.dataset.height),e}},{key:"checkSize",value:function(e){return M(e)?"".concat(e,"px"):e}},{key:"sanitizeValue",value:function(e){return"true"!==e&&"false"!==e?e:"true"===e}}]),e}(),U=function(){function e(i,n,s){t(this,e),this.element=i,this.instance=n,this.index=s}return n(e,[{key:"setContent",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1];if(c(t,"loaded"))return!1;var n=this.instance.settings,s=this.slideConfig,l=w();T(n.beforeSlideLoad)&&n.beforeSlideLoad({index:this.index,slide:t,player:!1});var o=s.type,r=s.descPosition,a=t.querySelector(".gslide-media"),d=t.querySelector(".gslide-title"),u=t.querySelector(".gslide-desc"),g=t.querySelector(".gdesc-inner"),v=i,f="gSlideTitle_"+this.index,p="gSlideDesc_"+this.index;if(T(n.afterSlideLoad)&&(v=function(){T(i)&&i(),n.afterSlideLoad({index:e.index,slide:t,player:e.instance.getSlidePlayerInstance(e.index)})}),""==s.title&&""==s.description?g&&g.parentNode.parentNode.removeChild(g.parentNode):(d&&""!==s.title?(d.id=f,d.innerHTML=s.title):d.parentNode.removeChild(d),u&&""!==s.description?(u.id=p,l&&n.moreLength>0?(s.smallDescription=this.slideShortDesc(s.description,n.moreLength,n.moreText),u.innerHTML=s.smallDescription,this.descriptionEvents(u,s)):u.innerHTML=s.description):u.parentNode.removeChild(u),h(a.parentNode,"desc-".concat(r)),h(g.parentNode,"description-".concat(r))),h(a,"gslide-".concat(o)),h(t,"loaded"),"video"!==o){if("external"!==o)return"inline"===o?(G.apply(this.instance,[t,s,this.index,v]),void(s.draggable&&new V({dragEl:t.querySelector(".gslide-inline"),toleranceX:n.dragToleranceX,toleranceY:n.dragToleranceY,slide:t,instance:this.instance}))):void("image"!==o?T(v)&&v():j(t,s,this.index,(function(){var i=t.querySelector("img");s.draggable&&new V({dragEl:i,toleranceX:n.dragToleranceX,toleranceY:n.dragToleranceY,slide:t,instance:e.instance}),s.zoomable&&i.naturalWidth>i.offsetWidth&&(h(i,"zoomable"),new H(i,t,(function(){e.instance.resize()}))),T(v)&&v()})));Z.apply(this,[t,s,this.index,v])}else F.apply(this.instance,[t,s,this.index,v])}},{key:"slideShortDesc",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:50,i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=document.createElement("div");n.innerHTML=e;var s=n.innerText,l=i;if((e=s.trim()).length<=t)return e;var o=e.substr(0,t-1);return l?(n=null,o+'... '+i+""):o}},{key:"descriptionEvents",value:function(e,t){var i=this,n=e.querySelector(".desc-more");if(!n)return!1;a("click",{onElement:n,withCallback:function(e,n){e.preventDefault();var s=document.body,l=u(n,".gslide-desc");if(!l)return!1;l.innerHTML=t.description,h(s,"gdesc-open");var o=a("click",{onElement:[s,u(l,".gslide-description")],withCallback:function(e,n){"a"!==e.target.nodeName.toLowerCase()&&(d(s,"gdesc-open"),h(s,"gdesc-closed"),l.innerHTML=t.smallDescription,i.descriptionEvents(l,t),setTimeout((function(){d(s,"gdesc-closed")}),400),o.destroy())}})}})}},{key:"create",value:function(){return m(this.instance.settings.slideHTML)}},{key:"getConfig",value:function(){k(this.element)||this.element.hasOwnProperty("draggable")||(this.element.draggable=this.instance.settings.draggable);var e=new $(this.instance.settings.slideExtraAttributes);return this.slideConfig=e.parseConfig(this.element,this.instance.settings),this.slideConfig}}]),e}(),J=w(),K=null!==w()||void 0!==document.createTouch||"ontouchstart"in window||"onmsgesturechange"in window||navigator.msMaxTouchPoints,Q=document.getElementsByTagName("html")[0],ee={selector:".glightbox",elements:null,skin:"clean",theme:"clean",closeButton:!0,startAt:null,autoplayVideos:!0,autofocusVideos:!0,descPosition:"bottom",width:"900px",height:"506px",videosWidth:"960px",beforeSlideChange:null,afterSlideChange:null,beforeSlideLoad:null,afterSlideLoad:null,slideInserted:null,slideRemoved:null,slideExtraAttributes:null,onOpen:null,onClose:null,loop:!1,zoomable:!0,draggable:!0,dragAutoSnap:!1,dragToleranceX:40,dragToleranceY:65,preload:!0,oneSlidePerOpen:!1,touchNavigation:!0,touchFollowAxis:!0,keyboardNavigation:!0,closeOnOutsideClick:!0,plugins:!1,plyr:{css:"https://cdn.plyr.io/3.6.8/plyr.css",js:"https://cdn.plyr.io/3.6.8/plyr.js",config:{ratio:"16:9",fullscreen:{enabled:!0,iosNative:!0},youtube:{noCookie:!0,rel:0,showinfo:0,iv_load_policy:3},vimeo:{byline:!1,portrait:!1,title:!1,transparent:!1}}},openEffect:"zoom",closeEffect:"zoom",slideEffect:"slide",moreText:"See more",moreLength:60,cssEfects:{fade:{in:"fadeIn",out:"fadeOut"},zoom:{in:"zoomIn",out:"zoomOut"},slide:{in:"slideInRight",out:"slideOutLeft"},slideBack:{in:"slideInLeft",out:"slideOutRight"},none:{in:"none",out:"none"}},svg:{close:'',next:' ',prev:''},slideHTML:'
        \n
        \n
        \n
        \n
        \n
        \n
        \n

        \n
        \n
        \n
        \n
        \n
        \n
        ',lightboxHTML:''},te=function(){function e(){var i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};t(this,e),this.customOptions=i,this.settings=l(ee,i),this.effectsClasses=this.getAnimationClasses(),this.videoPlayers={},this.apiEvents=[],this.fullElementsList=!1}return n(e,[{key:"init",value:function(){var e=this,t=this.getSelector();t&&(this.baseEvents=a("click",{onElement:t,withCallback:function(t,i){t.preventDefault(),e.open(i)}})),this.elements=this.getElements()}},{key:"open",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if(0==this.elements.length)return!1;this.activeSlide=null,this.prevActiveSlideIndex=null,this.prevActiveSlide=null;var i=M(t)?t:this.settings.startAt;if(k(e)){var n=e.getAttribute("data-gallery");n&&(this.fullElementsList=this.elements,this.elements=this.getGalleryElements(this.elements,n)),I(i)&&(i=this.getElementIndex(e))<0&&(i=0)}M(i)||(i=0),this.build(),g(this.overlay,"none"==this.settings.openEffect?"none":this.settings.cssEfects.fade.in);var s=document.body,l=window.innerWidth-document.documentElement.clientWidth;if(l>0){var o=document.createElement("style");o.type="text/css",o.className="gcss-styles",o.innerText=".gscrollbar-fixer {margin-right: ".concat(l,"px}"),document.head.appendChild(o),h(s,"gscrollbar-fixer")}h(s,"glightbox-open"),h(Q,"glightbox-open"),J&&(h(document.body,"glightbox-mobile"),this.settings.slideEffect="slide"),this.showSlide(i,!0),1==this.elements.length?(h(this.prevButton,"glightbox-button-hidden"),h(this.nextButton,"glightbox-button-hidden")):(d(this.prevButton,"glightbox-button-hidden"),d(this.nextButton,"glightbox-button-hidden")),this.lightboxOpen=!0,this.trigger("open"),T(this.settings.onOpen)&&this.settings.onOpen(),K&&this.settings.touchNavigation&&B(this),this.settings.keyboardNavigation&&X(this)}},{key:"openAt",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0;this.open(null,e)}},{key:"showSlide",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1];f(this.loader),this.index=parseInt(t);var n=this.slidesContainer.querySelector(".current");n&&d(n,"current"),this.slideAnimateOut();var s=this.slidesContainer.querySelectorAll(".gslide")[t];if(c(s,"loaded"))this.slideAnimateIn(s,i),p(this.loader);else{f(this.loader);var l=this.elements[t],o={index:this.index,slide:s,slideNode:s,slideConfig:l.slideConfig,slideIndex:this.index,trigger:l.node,player:null};this.trigger("slide_before_load",o),l.instance.setContent(s,(function(){p(e.loader),e.resize(),e.slideAnimateIn(s,i),e.trigger("slide_after_load",o)}))}this.slideDescription=s.querySelector(".gslide-description"),this.slideDescriptionContained=this.slideDescription&&c(this.slideDescription.parentNode,"gslide-media"),this.settings.preload&&(this.preloadSlide(t+1),this.preloadSlide(t-1)),this.updateNavigationClasses(),this.activeSlide=s}},{key:"preloadSlide",value:function(e){var t=this;if(e<0||e>this.elements.length-1)return!1;if(I(this.elements[e]))return!1;var i=this.slidesContainer.querySelectorAll(".gslide")[e];if(c(i,"loaded"))return!1;var n=this.elements[e],s=n.type,l={index:e,slide:i,slideNode:i,slideConfig:n.slideConfig,slideIndex:e,trigger:n.node,player:null};this.trigger("slide_before_load",l),"video"==s||"external"==s?setTimeout((function(){n.instance.setContent(i,(function(){t.trigger("slide_after_load",l)}))}),200):n.instance.setContent(i,(function(){t.trigger("slide_after_load",l)}))}},{key:"prevSlide",value:function(){this.goToSlide(this.index-1)}},{key:"nextSlide",value:function(){this.goToSlide(this.index+1)}},{key:"goToSlide",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0];if(this.prevActiveSlide=this.activeSlide,this.prevActiveSlideIndex=this.index,!this.loop()&&(e<0||e>this.elements.length-1))return!1;e<0?e=this.elements.length-1:e>=this.elements.length&&(e=0),this.showSlide(e)}},{key:"insertSlide",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:-1;t<0&&(t=this.elements.length);var i=new U(e,this,t),n=i.getConfig(),s=l({},n),o=i.create(),r=this.elements.length-1;s.index=t,s.node=!1,s.instance=i,s.slideConfig=n,this.elements.splice(t,0,s);var a=null,h=null;if(this.slidesContainer){if(t>r)this.slidesContainer.appendChild(o);else{var d=this.slidesContainer.querySelectorAll(".gslide")[t];this.slidesContainer.insertBefore(o,d)}(this.settings.preload&&0==this.index&&0==t||this.index-1==t||this.index+1==t)&&this.preloadSlide(t),0==this.index&&0==t&&(this.index=1),this.updateNavigationClasses(),a=this.slidesContainer.querySelectorAll(".gslide")[t],h=this.getSlidePlayerInstance(t),s.slideNode=a}this.trigger("slide_inserted",{index:t,slide:a,slideNode:a,slideConfig:n,slideIndex:t,trigger:null,player:h}),T(this.settings.slideInserted)&&this.settings.slideInserted({index:t,slide:a,player:h})}},{key:"removeSlide",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:-1;if(e<0||e>this.elements.length-1)return!1;var t=this.slidesContainer&&this.slidesContainer.querySelectorAll(".gslide")[e];t&&(this.getActiveSlideIndex()==e&&(e==this.elements.length-1?this.prevSlide():this.nextSlide()),t.parentNode.removeChild(t)),this.elements.splice(e,1),this.trigger("slide_removed",e),T(this.settings.slideRemoved)&&this.settings.slideRemoved(e)}},{key:"slideAnimateIn",value:function(e,t){var i=this,n=e.querySelector(".gslide-media"),s=e.querySelector(".gslide-description"),l={index:this.prevActiveSlideIndex,slide:this.prevActiveSlide,slideNode:this.prevActiveSlide,slideIndex:this.prevActiveSlide,slideConfig:I(this.prevActiveSlideIndex)?null:this.elements[this.prevActiveSlideIndex].slideConfig,trigger:I(this.prevActiveSlideIndex)?null:this.elements[this.prevActiveSlideIndex].node,player:this.getSlidePlayerInstance(this.prevActiveSlideIndex)},o={index:this.index,slide:this.activeSlide,slideNode:this.activeSlide,slideConfig:this.elements[this.index].slideConfig,slideIndex:this.index,trigger:this.elements[this.index].node,player:this.getSlidePlayerInstance(this.index)};if(n.offsetWidth>0&&s&&(p(s),s.style.display=""),d(e,this.effectsClasses),t)g(e,this.settings.cssEfects[this.settings.openEffect].in,(function(){i.settings.autoplayVideos&&i.slidePlayerPlay(e),i.trigger("slide_changed",{prev:l,current:o}),T(i.settings.afterSlideChange)&&i.settings.afterSlideChange.apply(i,[l,o])}));else{var r=this.settings.slideEffect,a="none"!==r?this.settings.cssEfects[r].in:r;this.prevActiveSlideIndex>this.index&&"slide"==this.settings.slideEffect&&(a=this.settings.cssEfects.slideBack.in),g(e,a,(function(){i.settings.autoplayVideos&&i.slidePlayerPlay(e),i.trigger("slide_changed",{prev:l,current:o}),T(i.settings.afterSlideChange)&&i.settings.afterSlideChange.apply(i,[l,o])}))}setTimeout((function(){i.resize(e)}),100),h(e,"current")}},{key:"slideAnimateOut",value:function(){if(!this.prevActiveSlide)return!1;var e=this.prevActiveSlide;d(e,this.effectsClasses),h(e,"prev");var t=this.settings.slideEffect,i="none"!==t?this.settings.cssEfects[t].out:t;this.slidePlayerPause(e),this.trigger("slide_before_change",{prev:{index:this.prevActiveSlideIndex,slide:this.prevActiveSlide,slideNode:this.prevActiveSlide,slideIndex:this.prevActiveSlideIndex,slideConfig:I(this.prevActiveSlideIndex)?null:this.elements[this.prevActiveSlideIndex].slideConfig,trigger:I(this.prevActiveSlideIndex)?null:this.elements[this.prevActiveSlideIndex].node,player:this.getSlidePlayerInstance(this.prevActiveSlideIndex)},current:{index:this.index,slide:this.activeSlide,slideNode:this.activeSlide,slideIndex:this.index,slideConfig:this.elements[this.index].slideConfig,trigger:this.elements[this.index].node,player:this.getSlidePlayerInstance(this.index)}}),T(this.settings.beforeSlideChange)&&this.settings.beforeSlideChange.apply(this,[{index:this.prevActiveSlideIndex,slide:this.prevActiveSlide,player:this.getSlidePlayerInstance(this.prevActiveSlideIndex)},{index:this.index,slide:this.activeSlide,player:this.getSlidePlayerInstance(this.index)}]),this.prevActiveSlideIndex>this.index&&"slide"==this.settings.slideEffect&&(i=this.settings.cssEfects.slideBack.out),g(e,i,(function(){var t=e.querySelector(".ginner-container"),i=e.querySelector(".gslide-media"),n=e.querySelector(".gslide-description");t.style.transform="",i.style.transform="",d(i,"greset"),i.style.opacity="",n&&(n.style.opacity=""),d(e,"prev")}))}},{key:"getAllPlayers",value:function(){return this.videoPlayers}},{key:"getSlidePlayerInstance",value:function(e){var t="gvideo"+e,i=this.getAllPlayers();return!(!O(i,t)||!i[t])&&i[t]}},{key:"stopSlideVideo",value:function(e){if(k(e)){var t=e.querySelector(".gvideo-wrapper");t&&(e=t.getAttribute("data-index"))}console.log("stopSlideVideo is deprecated, use slidePlayerPause");var i=this.getSlidePlayerInstance(e);i&&i.playing&&i.pause()}},{key:"slidePlayerPause",value:function(e){if(k(e)){var t=e.querySelector(".gvideo-wrapper");t&&(e=t.getAttribute("data-index"))}var i=this.getSlidePlayerInstance(e);i&&i.playing&&i.pause()}},{key:"playSlideVideo",value:function(e){if(k(e)){var t=e.querySelector(".gvideo-wrapper");t&&(e=t.getAttribute("data-index"))}console.log("playSlideVideo is deprecated, use slidePlayerPlay");var i=this.getSlidePlayerInstance(e);i&&!i.playing&&i.play()}},{key:"slidePlayerPlay",value:function(e){if(k(e)){var t=e.querySelector(".gvideo-wrapper");t&&(e=t.getAttribute("data-index"))}var i=this.getSlidePlayerInstance(e);i&&!i.playing&&(i.play(),this.settings.autofocusVideos&&i.elements.container.focus())}},{key:"setElements",value:function(e){var t=this;this.settings.elements=!1;var i=[];e&&e.length&&o(e,(function(e,n){var s=new U(e,t,n),o=s.getConfig(),r=l({},o);r.slideConfig=o,r.instance=s,r.index=n,i.push(r)})),this.elements=i,this.lightboxOpen&&(this.slidesContainer.innerHTML="",this.elements.length&&(o(this.elements,(function(){var e=m(t.settings.slideHTML);t.slidesContainer.appendChild(e)})),this.showSlide(0,!0)))}},{key:"getElementIndex",value:function(e){var t=!1;return o(this.elements,(function(i,n){if(O(i,"node")&&i.node==e)return t=n,!0})),t}},{key:"getElements",value:function(){var e=this,t=[];this.elements=this.elements?this.elements:[],!I(this.settings.elements)&&E(this.settings.elements)&&this.settings.elements.length&&o(this.settings.elements,(function(i,n){var s=new U(i,e,n),o=s.getConfig(),r=l({},o);r.node=!1,r.index=n,r.instance=s,r.slideConfig=o,t.push(r)}));var i=!1;return this.getSelector()&&(i=document.querySelectorAll(this.getSelector())),i?(o(i,(function(i,n){var s=new U(i,e,n),o=s.getConfig(),r=l({},o);r.node=i,r.index=n,r.instance=s,r.slideConfig=o,r.gallery=i.getAttribute("data-gallery"),t.push(r)})),t):t}},{key:"getGalleryElements",value:function(e,t){return e.filter((function(e){return e.gallery==t}))}},{key:"getSelector",value:function(){return!this.settings.elements&&(this.settings.selector&&"data-"==this.settings.selector.substring(0,5)?"*[".concat(this.settings.selector,"]"):this.settings.selector)}},{key:"getActiveSlide",value:function(){return this.slidesContainer.querySelectorAll(".gslide")[this.index]}},{key:"getActiveSlideIndex",value:function(){return this.index}},{key:"getAnimationClasses",value:function(){var e=[];for(var t in this.settings.cssEfects)if(this.settings.cssEfects.hasOwnProperty(t)){var i=this.settings.cssEfects[t];e.push("g".concat(i.in)),e.push("g".concat(i.out))}return e.join(" ")}},{key:"build",value:function(){var e=this;if(this.built)return!1;var t=document.body.childNodes,i=[];o(t,(function(e){e.parentNode==document.body&&"#"!==e.nodeName.charAt(0)&&e.hasAttribute&&!e.hasAttribute("aria-hidden")&&(i.push(e),e.setAttribute("aria-hidden","true"))}));var n=O(this.settings.svg,"next")?this.settings.svg.next:"",s=O(this.settings.svg,"prev")?this.settings.svg.prev:"",l=O(this.settings.svg,"close")?this.settings.svg.close:"",r=this.settings.lightboxHTML;r=m(r=(r=(r=r.replace(/{nextSVG}/g,n)).replace(/{prevSVG}/g,s)).replace(/{closeSVG}/g,l)),document.body.appendChild(r);var d=document.getElementById("glightbox-body");this.modal=d;var g=d.querySelector(".gclose");this.prevButton=d.querySelector(".gprev"),this.nextButton=d.querySelector(".gnext"),this.overlay=d.querySelector(".goverlay"),this.loader=d.querySelector(".gloader"),this.slidesContainer=document.getElementById("glightbox-slider"),this.bodyHiddenChildElms=i,this.events={},h(this.modal,"glightbox-"+this.settings.skin),this.settings.closeButton&&g&&(this.events.close=a("click",{onElement:g,withCallback:function(t,i){t.preventDefault(),e.close()}})),g&&!this.settings.closeButton&&g.parentNode.removeChild(g),this.nextButton&&(this.events.next=a("click",{onElement:this.nextButton,withCallback:function(t,i){t.preventDefault(),e.nextSlide()}})),this.prevButton&&(this.events.prev=a("click",{onElement:this.prevButton,withCallback:function(t,i){t.preventDefault(),e.prevSlide()}})),this.settings.closeOnOutsideClick&&(this.events.outClose=a("click",{onElement:d,withCallback:function(t,i){e.preventOutsideClick||c(document.body,"glightbox-mobile")||u(t.target,".ginner-container")||u(t.target,".gbtn")||c(t.target,"gnext")||c(t.target,"gprev")||e.close()}})),o(this.elements,(function(t,i){e.slidesContainer.appendChild(t.instance.create()),t.slideNode=e.slidesContainer.querySelectorAll(".gslide")[i]})),K&&h(document.body,"glightbox-touch"),this.events.resize=a("resize",{onElement:window,withCallback:function(){e.resize()}}),this.built=!0}},{key:"resize",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;if((e=e||this.activeSlide)&&!c(e,"zoomed")){var t=y(),i=e.querySelector(".gvideo-wrapper"),n=e.querySelector(".gslide-image"),s=this.slideDescription,l=t.width,o=t.height;if(l<=768?h(document.body,"glightbox-mobile"):d(document.body,"glightbox-mobile"),i||n){var r=!1;if(s&&(c(s,"description-bottom")||c(s,"description-top"))&&!c(s,"gabsolute")&&(r=!0),n)if(l<=768)n.querySelector("img");else if(r){var a=s.offsetHeight,u=n.querySelector("img");u.setAttribute("style","max-height: calc(100vh - ".concat(a,"px)")),s.setAttribute("style","max-width: ".concat(u.offsetWidth,"px;"))}if(i){var g=O(this.settings.plyr.config,"ratio")?this.settings.plyr.config.ratio:"";if(!g){var v=i.clientWidth,f=i.clientHeight,p=v/f;g="".concat(v/p,":").concat(f/p)}var m=g.split(":"),x=this.settings.videosWidth,b=this.settings.videosWidth,S=(b=M(x)||-1!==x.indexOf("px")?parseInt(x):-1!==x.indexOf("vw")?l*parseInt(x)/100:-1!==x.indexOf("vh")?o*parseInt(x)/100:-1!==x.indexOf("%")?l*parseInt(x)/100:parseInt(i.clientWidth))/(parseInt(m[0])/parseInt(m[1]));if(S=Math.floor(S),r&&(o-=s.offsetHeight),b>l||S>o||ob){var w=i.offsetWidth,T=i.offsetHeight,C=o/T,k={width:w*C,height:T*C};i.parentNode.setAttribute("style","max-width: ".concat(k.width,"px")),r&&s.setAttribute("style","max-width: ".concat(k.width,"px;"))}else i.parentNode.style.maxWidth="".concat(x),r&&s.setAttribute("style","max-width: ".concat(x,";"))}}}}},{key:"reload",value:function(){this.init()}},{key:"updateNavigationClasses",value:function(){var e=this.loop();d(this.nextButton,"disabled"),d(this.prevButton,"disabled"),0==this.index&&this.elements.length-1==0?(h(this.prevButton,"disabled"),h(this.nextButton,"disabled")):0!==this.index||e?this.index!==this.elements.length-1||e||h(this.nextButton,"disabled"):h(this.prevButton,"disabled")}},{key:"loop",value:function(){var e=O(this.settings,"loopAtEnd")?this.settings.loopAtEnd:null;return e=O(this.settings,"loop")?this.settings.loop:e,e}},{key:"close",value:function(){var e=this;if(!this.lightboxOpen){if(this.events){for(var t in this.events)this.events.hasOwnProperty(t)&&this.events[t].destroy();this.events=null}return!1}if(this.closing)return!1;this.closing=!0,this.slidePlayerPause(this.activeSlide),this.fullElementsList&&(this.elements=this.fullElementsList),this.bodyHiddenChildElms.length&&o(this.bodyHiddenChildElms,(function(e){e.removeAttribute("aria-hidden")})),h(this.modal,"glightbox-closing"),g(this.overlay,"none"==this.settings.openEffect?"none":this.settings.cssEfects.fade.out),g(this.activeSlide,this.settings.cssEfects[this.settings.closeEffect].out,(function(){if(e.activeSlide=null,e.prevActiveSlideIndex=null,e.prevActiveSlide=null,e.built=!1,e.events){for(var t in e.events)e.events.hasOwnProperty(t)&&e.events[t].destroy();e.events=null}var i=document.body;d(Q,"glightbox-open"),d(i,"glightbox-open touching gdesc-open glightbox-touch glightbox-mobile gscrollbar-fixer"),e.modal.parentNode.removeChild(e.modal),e.trigger("close"),T(e.settings.onClose)&&e.settings.onClose();var n=document.querySelector(".gcss-styles");n&&n.parentNode.removeChild(n),e.lightboxOpen=!1,e.closing=null}))}},{key:"destroy",value:function(){this.close(),this.clearAllEvents(),this.baseEvents&&this.baseEvents.destroy()}},{key:"on",value:function(e,t){var i=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(!e||!T(t))throw new TypeError("Event name and callback must be defined");this.apiEvents.push({evt:e,once:i,callback:t})}},{key:"once",value:function(e,t){this.on(e,t,!0)}},{key:"trigger",value:function(e){var t=this,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=[];o(this.apiEvents,(function(t,s){var l=t.evt,o=t.once,r=t.callback;l==e&&(r(i),o&&n.push(s))})),n.length&&o(n,(function(e){return t.apiEvents.splice(e,1)}))}},{key:"clearAllEvents",value:function(){this.apiEvents.splice(0,this.apiEvents.length)}},{key:"version",value:function(){return"3.1.1"}}]),e}();return function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=new te(e);return t.init(),t}})); diff --git a/site/assets/stylesheets/glightbox.min.css b/site/assets/stylesheets/glightbox.min.css new file mode 100644 index 0000000..3c9ff87 --- /dev/null +++ b/site/assets/stylesheets/glightbox.min.css @@ -0,0 +1 @@ +.glightbox-container{width:100%;height:100%;position:fixed;top:0;left:0;z-index:999999!important;overflow:hidden;-ms-touch-action:none;touch-action:none;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;outline:0}.glightbox-container.inactive{display:none}.glightbox-container .gcontainer{position:relative;width:100%;height:100%;z-index:9999;overflow:hidden}.glightbox-container .gslider{-webkit-transition:-webkit-transform .4s ease;transition:-webkit-transform .4s ease;transition:transform .4s ease;transition:transform .4s ease,-webkit-transform .4s ease;height:100%;left:0;top:0;width:100%;position:relative;overflow:hidden;display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.glightbox-container .gslide{width:100%;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;opacity:0}.glightbox-container .gslide.current{opacity:1;z-index:99999;position:relative}.glightbox-container .gslide.prev{opacity:1;z-index:9999}.glightbox-container .gslide-inner-content{width:100%}.glightbox-container .ginner-container{position:relative;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;max-width:100%;margin:auto;height:100vh}.glightbox-container .ginner-container.gvideo-container{width:100%}.glightbox-container .ginner-container.desc-bottom,.glightbox-container .ginner-container.desc-top{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.glightbox-container .ginner-container.desc-left,.glightbox-container .ginner-container.desc-right{max-width:100%!important}.gslide iframe,.gslide video{outline:0!important;border:none;min-height:165px;-webkit-overflow-scrolling:touch;-ms-touch-action:auto;touch-action:auto}.gslide:not(.current){pointer-events:none}.gslide-image{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.gslide-image img{max-height:100vh;display:block;padding:0;float:none;outline:0;border:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;max-width:100vw;width:auto;height:auto;-o-object-fit:cover;object-fit:cover;-ms-touch-action:none;touch-action:none;margin:auto;min-width:200px}.desc-bottom .gslide-image img,.desc-top .gslide-image img{width:auto}.desc-left .gslide-image img,.desc-right .gslide-image img{width:auto;max-width:100%}.gslide-image img.zoomable{position:relative}.gslide-image img.dragging{cursor:-webkit-grabbing!important;cursor:grabbing!important;-webkit-transition:none;transition:none}.gslide-video{position:relative;max-width:100vh;width:100%!important}.gslide-video .plyr__poster-enabled.plyr--loading .plyr__poster{display:none}.gslide-video .gvideo-wrapper{width:100%;margin:auto}.gslide-video::before{content:'';position:absolute;width:100%;height:100%;background:rgba(255,0,0,.34);display:none}.gslide-video.playing::before{display:none}.gslide-video.fullscreen{max-width:100%!important;min-width:100%;height:75vh}.gslide-video.fullscreen video{max-width:100%!important;width:100%!important}.gslide-inline{background:#fff;text-align:left;max-height:calc(100vh - 40px);overflow:auto;max-width:100%;margin:auto}.gslide-inline .ginlined-content{padding:20px;width:100%}.gslide-inline .dragging{cursor:-webkit-grabbing!important;cursor:grabbing!important;-webkit-transition:none;transition:none}.ginlined-content{overflow:auto;display:block!important;opacity:1}.gslide-external{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;min-width:100%;background:#fff;padding:0;overflow:auto;max-height:75vh;height:100%}.gslide-media{display:-webkit-box;display:-ms-flexbox;display:flex;width:auto}.zoomed .gslide-media{-webkit-box-shadow:none!important;box-shadow:none!important}.desc-bottom .gslide-media,.desc-top .gslide-media{margin:0 auto;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.gslide-description{position:relative;-webkit-box-flex:1;-ms-flex:1 0 100%;flex:1 0 100%}.gslide-description.description-left,.gslide-description.description-right{max-width:100%}.gslide-description.description-bottom,.gslide-description.description-top{margin:0 auto;width:100%}.gslide-description p{margin-bottom:12px}.gslide-description p:last-child{margin-bottom:0}.zoomed .gslide-description{display:none}.glightbox-button-hidden{display:none}.glightbox-mobile .glightbox-container .gslide-description{height:auto!important;width:100%;position:absolute;bottom:0;padding:19px 11px;max-width:100vw!important;-webkit-box-ordinal-group:3!important;-ms-flex-order:2!important;order:2!important;max-height:78vh;overflow:auto!important;background:-webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0)),to(rgba(0,0,0,.75)));background:linear-gradient(to bottom,rgba(0,0,0,0) 0,rgba(0,0,0,.75) 100%);-webkit-transition:opacity .3s linear;transition:opacity .3s linear;padding-bottom:50px}.glightbox-mobile .glightbox-container .gslide-title{color:#fff;font-size:1em}.glightbox-mobile .glightbox-container .gslide-desc{color:#a1a1a1}.glightbox-mobile .glightbox-container .gslide-desc a{color:#fff;font-weight:700}.glightbox-mobile .glightbox-container .gslide-desc *{color:inherit}.glightbox-mobile .glightbox-container .gslide-desc .desc-more{color:#fff;opacity:.4}.gdesc-open .gslide-media{-webkit-transition:opacity .5s ease;transition:opacity .5s ease;opacity:.4}.gdesc-open .gdesc-inner{padding-bottom:30px}.gdesc-closed .gslide-media{-webkit-transition:opacity .5s ease;transition:opacity .5s ease;opacity:1}.greset{-webkit-transition:all .3s ease;transition:all .3s ease}.gabsolute{position:absolute}.grelative{position:relative}.glightbox-desc{display:none!important}.glightbox-open{overflow:hidden}.gloader{height:25px;width:25px;-webkit-animation:lightboxLoader .8s infinite linear;animation:lightboxLoader .8s infinite linear;border:2px solid #fff;border-right-color:transparent;border-radius:50%;position:absolute;display:block;z-index:9999;left:0;right:0;margin:0 auto;top:47%}.goverlay{width:100%;height:calc(100vh + 1px);position:fixed;top:-1px;left:0;background:#000;will-change:opacity}.glightbox-mobile .goverlay{background:#000}.gclose,.gnext,.gprev{z-index:99999;cursor:pointer;width:26px;height:44px;border:none;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.gclose svg,.gnext svg,.gprev svg{display:block;width:25px;height:auto;margin:0;padding:0}.gclose.disabled,.gnext.disabled,.gprev.disabled{opacity:.1}.gclose .garrow,.gnext .garrow,.gprev .garrow{stroke:#fff}.gbtn.focused{outline:2px solid #0f3d81}iframe.wait-autoplay{opacity:0}.glightbox-closing .gclose,.glightbox-closing .gnext,.glightbox-closing .gprev{opacity:0!important}.glightbox-clean .gslide-description{background:#fff}.glightbox-clean .gdesc-inner{padding:22px 20px}.glightbox-clean .gslide-title{font-size:1em;font-weight:400;font-family:arial;color:#000;margin-bottom:19px;line-height:1.4em}.glightbox-clean .gslide-desc{font-size:.86em;margin-bottom:0;font-family:arial;line-height:1.4em}.glightbox-clean .gslide-video{background:#000}.glightbox-clean .gclose,.glightbox-clean .gnext,.glightbox-clean .gprev{background-color:rgba(0,0,0,.75);border-radius:4px}.glightbox-clean .gclose path,.glightbox-clean .gnext path,.glightbox-clean .gprev path{fill:#fff}.glightbox-clean .gprev{position:absolute;top:-100%;left:30px;width:40px;height:50px}.glightbox-clean .gnext{position:absolute;top:-100%;right:30px;width:40px;height:50px}.glightbox-clean .gclose{width:35px;height:35px;top:15px;right:10px;position:absolute}.glightbox-clean .gclose svg{width:18px;height:auto}.glightbox-clean .gclose:hover{opacity:1}.gfadeIn{-webkit-animation:gfadeIn .5s ease;animation:gfadeIn .5s ease}.gfadeOut{-webkit-animation:gfadeOut .5s ease;animation:gfadeOut .5s ease}.gslideOutLeft{-webkit-animation:gslideOutLeft .3s ease;animation:gslideOutLeft .3s ease}.gslideInLeft{-webkit-animation:gslideInLeft .3s ease;animation:gslideInLeft .3s ease}.gslideOutRight{-webkit-animation:gslideOutRight .3s ease;animation:gslideOutRight .3s ease}.gslideInRight{-webkit-animation:gslideInRight .3s ease;animation:gslideInRight .3s ease}.gzoomIn{-webkit-animation:gzoomIn .5s ease;animation:gzoomIn .5s ease}.gzoomOut{-webkit-animation:gzoomOut .5s ease;animation:gzoomOut .5s ease}@-webkit-keyframes lightboxLoader{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes lightboxLoader{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes gfadeIn{from{opacity:0}to{opacity:1}}@keyframes gfadeIn{from{opacity:0}to{opacity:1}}@-webkit-keyframes gfadeOut{from{opacity:1}to{opacity:0}}@keyframes gfadeOut{from{opacity:1}to{opacity:0}}@-webkit-keyframes gslideInLeft{from{opacity:0;-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0)}to{visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes gslideInLeft{from{opacity:0;-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0)}to{visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes gslideOutLeft{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0);opacity:0;visibility:hidden}}@keyframes gslideOutLeft{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0);opacity:0;visibility:hidden}}@-webkit-keyframes gslideInRight{from{opacity:0;visibility:visible;-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0)}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes gslideInRight{from{opacity:0;visibility:visible;-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0)}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes gslideOutRight{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0);opacity:0}}@keyframes gslideOutRight{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0);opacity:0}}@-webkit-keyframes gzoomIn{from{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:1}}@keyframes gzoomIn{from{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:1}}@-webkit-keyframes gzoomOut{from{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:0}}@keyframes gzoomOut{from{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:0}}@media (min-width:769px){.glightbox-container .ginner-container{width:auto;height:auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.glightbox-container .ginner-container.desc-top .gslide-description{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.glightbox-container .ginner-container.desc-top .gslide-image,.glightbox-container .ginner-container.desc-top .gslide-image img{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.glightbox-container .ginner-container.desc-left .gslide-description{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.glightbox-container .ginner-container.desc-left .gslide-image{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.gslide-image img{max-height:97vh;max-width:100%}.gslide-image img.zoomable{cursor:-webkit-zoom-in;cursor:zoom-in}.zoomed .gslide-image img.zoomable{cursor:-webkit-grab;cursor:grab}.gslide-inline{max-height:95vh}.gslide-external{max-height:100vh}.gslide-description.description-left,.gslide-description.description-right{max-width:275px}.glightbox-open{height:auto}.goverlay{background:rgba(0,0,0,.92)}.glightbox-clean .gslide-media{-webkit-box-shadow:1px 2px 9px 0 rgba(0,0,0,.65);box-shadow:1px 2px 9px 0 rgba(0,0,0,.65)}.glightbox-clean .description-left .gdesc-inner,.glightbox-clean .description-right .gdesc-inner{position:absolute;height:100%;overflow-y:auto}.glightbox-clean .gclose,.glightbox-clean .gnext,.glightbox-clean .gprev{background-color:rgba(0,0,0,.32)}.glightbox-clean .gclose:hover,.glightbox-clean .gnext:hover,.glightbox-clean .gprev:hover{background-color:rgba(0,0,0,.7)}.glightbox-clean .gprev{top:45%}.glightbox-clean .gnext{top:45%}}@media (min-width:992px){.glightbox-clean .gclose{opacity:.7;right:20px}}@media screen and (max-height:420px){.goverlay{background:#000}} \ No newline at end of file diff --git a/site/book/index.html b/site/book/index.html index 5232bde..ca98e96 100644 --- a/site/book/index.html +++ b/site/book/index.html @@ -44,6 +44,8 @@ + + @@ -87,7 +89,18 @@ - + @@ -471,8 +484,8 @@
      • - - Quotes + + Quotes and Ratings
      • @@ -660,6 +673,66 @@ +
      • + + + + + Not understanding slice length and capacity (#20) + + + + +
      • + + + + + + + + + +
      • + + + + + Maps and memory leaks (#28) + + + + +
      • + + + + + + + + + +
      • + + + + + Writing inaccurate benchmarks (#89) + + + + +
      • + + + + + + + + +
      • @@ -805,8 +878,8 @@
      • - - Quotes + + Quotes and Ratings
      • @@ -844,35 +917,53 @@

        100 Go Mistakes and How to Avoid Them

        -

        Community space of 100 Go Mistakes and How to Avoid Them, published by Manning in 2022.

        -

        +

        Community space of 📖 100 Go Mistakes and How to Avoid Them, published by Manning in 2022.

        +

        Description

        If you're a Go developer looking to improve your skills, this book is for you. With a focus on practical examples, 100 Go Mistakes and How to Avoid Them 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.

        -

        Quotes

        -
        -

        This is an exceptional book. Usually, if a book contains either high-quality explanations or is written succinctly, I consider myself lucky to have found it. This one combines these two characteristics, which is super rare. It's another Go book for me and I still had quite a lot of "a-ha!" moments while reading it, and all of that without the unnecessary fluff, just straight to the point.

        -
        -

        – Krystian (Goodreads user)

        -
        -

        This should be the required reading for all Golang developers before they touch code in Production... It's the Golang equivalent of the legendary 'Effective Java' by Joshua Bloch.

        -
        -

        Neeraj Shah

        -
        -

        Not having this will be the 101st mistake a Go programmer could make.

        -
        -

        Anupam Sengupta

        +

        Read a summary of the 100 mistakes here.

        +

        Quotes and Ratings

        +
        +

        Krystian (Goodreads user)

        +

        This is an exceptional book. Usually, if a book contains either high-quality explanations or is written succinctly, I consider myself lucky to have found it. This one combines these two characteristics, which is super rare. It's another Go book for me and I still had quite a lot of "a-ha!" moments while reading it, and all of that without the unnecessary fluff, just straight to the point.

        +
        +
        +

        Akash Chetty

        +

        The book is completely exceptional, especially the examples carved out for each topic are really great. There is one topic that I struggled to understand is Concurrency but the way it is explained in this book is truly an art of genius.

        +
        +
        +

        Neeraj Shah

        +

        This should be the required reading for all Golang developers before they touch code in Production... It's the Golang equivalent of the legendary 'Effective Java' by Joshua Bloch.

        +
        +
        +

        Anupam Sengupta

        +

        Not having this will be the 101st mistake a Go programmer could make.

        +
        +
        +

        + + +

        +
        Manning, Goodreads, and Amazon reviews: 4.7/5 avg rating
        +

        Where to Buy?

        +
        +

        + +

        +
        English and Japanese front covers
        +

        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.

        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.

        Needless complexity

        @@ -1040,6 +1113,20 @@
      • 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. +
      3. +

        J. Metcalfe, “Learning from Errors,” Annual Review of Psychology, vol. 68, pp. 465–489, Jan. 2017. 

        +
      4. +
      5. +

        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

        +
      6. +
      +
      @@ -1109,11 +1196,11 @@
      - + - + \ No newline at end of file diff --git a/site/external/index.html b/site/external/index.html index c178cfe..d4af5e3 100644 --- a/site/external/index.html +++ b/site/external/index.html @@ -46,6 +46,8 @@ + + @@ -89,7 +91,18 @@ - + @@ -662,6 +675,66 @@ +
    • + + + + + Not understanding slice length and capacity (#20) + + + + +
    • + + + + + + + + + +
    • + + + + + Maps and memory leaks (#28) + + + + +
    • + + + + + + + + + +
    • + + + + + Writing inaccurate benchmarks (#89) + + + + +
    • + + + + + + + + +
    • @@ -938,11 +1011,11 @@
      - + - + \ No newline at end of file diff --git a/site/img/cover-en.jpg b/site/img/cover-en.jpg new file mode 100644 index 0000000..4f892e5 Binary files /dev/null and b/site/img/cover-en.jpg differ diff --git a/site/img/cover-jp.jpg b/site/img/cover-jp.jpg new file mode 100644 index 0000000..dff9547 Binary files /dev/null and b/site/img/cover-jp.jpg differ diff --git a/site/img/map-leak-1.png b/site/img/map-leak-1.png new file mode 100644 index 0000000..7c47815 Binary files /dev/null and b/site/img/map-leak-1.png differ diff --git a/site/img/map-leak-2.png b/site/img/map-leak-2.png new file mode 100644 index 0000000..38aca8c Binary files /dev/null and b/site/img/map-leak-2.png differ diff --git a/site/img/matrix.png b/site/img/matrix.png new file mode 100644 index 0000000..d7296de Binary files /dev/null and b/site/img/matrix.png differ diff --git a/site/img/ratings-amazon.png b/site/img/ratings-amazon.png new file mode 100644 index 0000000..b3063b6 Binary files /dev/null and b/site/img/ratings-amazon.png differ diff --git a/site/img/ratings-goodreads.png b/site/img/ratings-goodreads.png new file mode 100644 index 0000000..9de92da Binary files /dev/null and b/site/img/ratings-goodreads.png differ diff --git a/site/img/ratings-manning.png b/site/img/ratings-manning.png new file mode 100644 index 0000000..89151b1 Binary files /dev/null and b/site/img/ratings-manning.png differ diff --git a/site/img/slice-1.png b/site/img/slice-1.png new file mode 100644 index 0000000..a481b78 Binary files /dev/null and b/site/img/slice-1.png differ diff --git a/site/img/slice-2.png b/site/img/slice-2.png new file mode 100644 index 0000000..2e6905c Binary files /dev/null and b/site/img/slice-2.png differ diff --git a/site/img/slice-3.png b/site/img/slice-3.png new file mode 100644 index 0000000..8120a8a Binary files /dev/null and b/site/img/slice-3.png differ diff --git a/site/img/slice-4.png b/site/img/slice-4.png new file mode 100644 index 0000000..580383c Binary files /dev/null and b/site/img/slice-4.png differ diff --git a/site/img/slice-5.png b/site/img/slice-5.png new file mode 100644 index 0000000..d89e0cd Binary files /dev/null and b/site/img/slice-5.png differ diff --git a/site/img/slice-6.png b/site/img/slice-6.png new file mode 100644 index 0000000..4d1573b Binary files /dev/null and b/site/img/slice-6.png differ diff --git a/site/img/slice-7.png b/site/img/slice-7.png new file mode 100644 index 0000000..2a5fb6c Binary files /dev/null and b/site/img/slice-7.png differ diff --git a/site/img/slice-8.png b/site/img/slice-8.png new file mode 100644 index 0000000..a438908 Binary files /dev/null and b/site/img/slice-8.png differ diff --git a/site/jobs/index.html b/site/jobs/index.html index 9ddc75e..53ee94a 100644 --- a/site/jobs/index.html +++ b/site/jobs/index.html @@ -44,6 +44,8 @@ + + @@ -87,7 +89,18 @@ - + @@ -592,6 +605,66 @@ +
    • + + + + + Not understanding slice length and capacity (#20) + + + + +
    • + + + + + + + + + +
    • + + + + + Maps and memory leaks (#28) + + + + +
    • + + + + + + + + + +
    • + + + + + Writing inaccurate benchmarks (#89) + + + + +
    • + + + + + + + + +
    • @@ -826,11 +899,11 @@
      - + - + \ No newline at end of file diff --git a/site/stylesheets/extra.css b/site/stylesheets/extra.css new file mode 100644 index 0000000..05f364c --- /dev/null +++ b/site/stylesheets/extra.css @@ -0,0 +1,15 @@ +.md-typeset figure img { + display: inline; +} + +.md-tabs__link { + color: red; +} + +.md-tabs__link:hover { + color: green; +} + +.md-tabs__link:focus { + color: blue; +} \ No newline at end of file diff --git a/site/zh/index.html b/site/zh/index.html index 4647319..39bbb7f 100644 --- a/site/zh/index.html +++ b/site/zh/index.html @@ -46,6 +46,8 @@ + + @@ -89,7 +91,18 @@ - + @@ -594,6 +607,66 @@ +
    • + + + + + Not understanding slice length and capacity (#20) + + + + +
    • + + + + + + + + + +
    • + + + + + Maps and memory leaks (#28) + + + + +
    • + + + + + + + + + +
    • + + + + + Writing inaccurate benchmarks (#89) + + + + +
    • + + + + + + + + +
    • @@ -2893,11 +2966,11 @@
      - + - + \ No newline at end of file diff --git a/src/03-data-types/25-slice-append/25-slice-append.go b/src/03-data-types/25-slice-append/main.go similarity index 100% rename from src/03-data-types/25-slice-append/25-slice-append.go rename to src/03-data-types/25-slice-append/main.go