diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..156deea --- /dev/null +++ b/.gitattributes @@ -0,0 +1,26 @@ +* text=auto +*.adoc text +*.bat text eol=crlf +*.bazel text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bzl text +*.css text eol=lf +*.env text +*.go text +*.html text eol=lf +*.jar filter=lfs diff=lfs merge=lfs -text +*.java text +*.js text eol=lf +*.json text +*.md text +*.patch text +*.png filter=lfs diff=lfs merge=lfs -text +*.proto text linguist-detectable +*.scala text +*.ts text eol=lf +*.yaml text +*.zip filter=lfs diff=lfs merge=lfs -text +go.mod text +go.sum text +BUILD text -linguist-detectable +WORKSPACE text -linguist-detectable diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..03cdc5a --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,8 @@ +{ + "automerge": true, + "automergeType": "branch", + "extends": [ + "config:base", + ":disableDependencyDashboard" + ] +} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c5fe156 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +--- +name: Test +"on": + push: + branches: + - main + pull_request: + branches: + - main +jobs: + test: + runs-on: ubuntu-latest + permissions: + checks: write + contents: read + pull-requests: read + statuses: write + steps: + - name: ✔ Check out + uses: actions/checkout@v4 + - name: 🐹 Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.22" + check-latest: true + - name: 🧸 golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: v1.57.2 + - name: 🔨 Test + run: go test -short ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a9d573 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.* +!/.buildkite/ +!/.codeclimate.yml +!/.envrc +!/.gitattributes +!/.github/ +!/.gitignore +!/.golangci.yaml +!/.markdownlint.yaml +!/.mockery.yaml +!/.yamlfmt +!/.yamllint +/bin/ +/cover*.out +/test.xml +/trace*.out diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..3bd2cf0 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,45 @@ +--- +run: + modules-download-mode: readonly +linters: + enable-all: true + disable: + # deprecated + - deadcode + - exhaustivestruct + - golint + - ifshort + - interfacer + - maligned + - nosnakecase + - scopelint + - structcheck + - varcheck + # disabled + - depguard + - dogsled + - exhaustruct + - forbidigo + - gomnd + - ireturn + - nonamedreturns + - prealloc + - varnamelen + - wrapcheck + - wsl +linters-settings: + testifylint: + enable-all: true + disable: + - require-error + paralleltest: + ignore-missing: true +issues: + exclude-rules: + - path: _test\.go$ + linters: + - revive + text: "dot-imports" + - path: _test\.go$ + linters: + - containedctx diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..bc7c370 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,8 @@ +--- +no-hard-tabs: + code_blocks: false + ignore_code_languages: + - go + spaces_per_tab: 4 +line-length: + line_length: 120 diff --git a/.yamlfmt b/.yamlfmt new file mode 100644 index 0000000..135dd95 --- /dev/null +++ b/.yamlfmt @@ -0,0 +1,8 @@ +--- +formatter: + type: basic + include_document_start: true + retain_line_breaks: true + scan_folded_as_literal: true + max_line_length: 100 + pad_line_comments: 2 diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..8a110bb --- /dev/null +++ b/.yamllint @@ -0,0 +1,7 @@ +--- +extends: default +rules: + empty-lines: + max: 1 + line-length: + max: 120 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cfac34d --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Go Concurrency + +[![Test](https://github.com/fillmore-labs/blog-goroutines/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/fillmore-labs/blog-goroutines/actions/workflows/test.yml) +[![License](https://img.shields.io/github/license/fillmore-labs/blog-goroutines)](https://www.apache.org/licenses/LICENSE-2.0) + +Code for articles about Go concurrency. +See more on the [Fillmore Labs Blog](https://blog.fillmore-labs.com/posts/goroutines-1/). diff --git a/cmd/gc/main.go b/cmd/gc/main.go new file mode 100644 index 0000000..f781f9b --- /dev/null +++ b/cmd/gc/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "time" + + "fillmore-labs.com/blog/goroutines/pkg/gc" +) + +func main() { + runConcurrent() + runNonConcurrent() +} + +func runConcurrent() { + start := time.Now() + + reachable := gc.ConcurrentMark(gc.MakeRootC(), gc.Lookupc) + + fmt.Printf("Concurrent: Found %d reachable nodes in %v\n", len(reachable), time.Since(start)) +} + +func runNonConcurrent() { + start := time.Now() + + reachable := gc.Mark(gc.MakeRootC(), gc.Lookupc) + + fmt.Printf("Non-Concurrent: Found %d reachable nodes in %v\n", len(reachable), time.Since(start)) +} diff --git a/cmd/try1/main.go b/cmd/try1/main.go new file mode 100644 index 0000000..3ab54c8 --- /dev/null +++ b/cmd/try1/main.go @@ -0,0 +1,52 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "time" + + "fillmore-labs.com/blog/goroutines/pkg/fibonacci" + "fillmore-labs.com/blog/goroutines/pkg/mean" +) + +const ( + Count = 1_000 + sequence = 27 +) + +func main() { + start := time.Now() + + m := Run1(Count) + + fmt.Printf("*** Finished %d runs in %v - avg %v, stddev %v\n", Count, time.Since(start), m.Avg, m.Dev) +} + +func Run1(c int) mean.Mean { + s := mean.New() + + for range c { + queryStart := time.Now() + + _ = fibonacci.Slow(sequence) + + s.Add(time.Since(queryStart)) + } + + return s.Result() +} diff --git a/cmd/try1/main_test.go b/cmd/try1/main_test.go new file mode 100644 index 0000000..eceea64 --- /dev/null +++ b/cmd/try1/main_test.go @@ -0,0 +1,39 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/cmd/try1" + "go.uber.org/goleak" +) + +func TestRun1(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + defer goleak.VerifyNone(t) + + m := Run1(Count) + + t.Logf("*** Finished %d runs - avg %v, stddev %v\n", Count, m.Avg, m.Dev) +} + +func BenchmarkRun1(b *testing.B) { + _ = Run1(b.N) +} diff --git a/cmd/try2/main.go b/cmd/try2/main.go new file mode 100644 index 0000000..a0056ad --- /dev/null +++ b/cmd/try2/main.go @@ -0,0 +1,52 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "time" + + "fillmore-labs.com/blog/goroutines/pkg/fibonacci" + "fillmore-labs.com/blog/goroutines/pkg/mean" +) + +const ( + Count = 1_000 + sequence = 27 +) + +func main() { + start := time.Now() + + m := Run2(Count) + + fmt.Printf("*** Finished %d runs in %v - avg %v, stddev %v\n", Count, time.Since(start), m.Avg, m.Dev) +} + +func Run2(c int) mean.Mean { + s := mean.New() + + for range c { + queryStart := time.Now() + + _ = fibonacci.Parallel1(sequence) + + s.Add(time.Since(queryStart)) + } + + return s.Result() +} diff --git a/cmd/try2/main_test.go b/cmd/try2/main_test.go new file mode 100644 index 0000000..242fe3e --- /dev/null +++ b/cmd/try2/main_test.go @@ -0,0 +1,39 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/cmd/try2" + "go.uber.org/goleak" +) + +func TestRun2(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + defer goleak.VerifyNone(t) + + m := Run2(Count) + + t.Logf("*** Finished %d runs - avg %v, stddev %v\n", Count, m.Avg, m.Dev) +} + +func BenchmarkRun2(b *testing.B) { + _ = Run2(b.N) +} diff --git a/cmd/try3/main.go b/cmd/try3/main.go new file mode 100644 index 0000000..6ecc62a --- /dev/null +++ b/cmd/try3/main.go @@ -0,0 +1,52 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "time" + + "fillmore-labs.com/blog/goroutines/pkg/fibonacci" + "fillmore-labs.com/blog/goroutines/pkg/mean" +) + +const ( + Count = 1_000 + sequence = 27 +) + +func main() { + start := time.Now() + + m := Run3(Count) + + fmt.Printf("*** Finished %d runs in %v - avg %v, stddev %v\n", Count, time.Since(start), m.Avg, m.Dev) +} + +func Run3(c int) mean.Mean { + s := mean.New() + + for range c { + queryStart := time.Now() + + _ = fibonacci.Parallel2(sequence) + + s.Add(time.Since(queryStart)) + } + + return s.Result() +} diff --git a/cmd/try3/main_test.go b/cmd/try3/main_test.go new file mode 100644 index 0000000..c34ed3e --- /dev/null +++ b/cmd/try3/main_test.go @@ -0,0 +1,39 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/cmd/try3" + "go.uber.org/goleak" +) + +func TestRun3(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + defer goleak.VerifyNone(t) + + m := Run3(Count) + + t.Logf("*** Finished %d runs - avg %v, stddev %v\n", Count, m.Avg, m.Dev) +} + +func BenchmarkRun3(b *testing.B) { + _ = Run3(b.N) +} diff --git a/cmd/try4/main.go b/cmd/try4/main.go new file mode 100644 index 0000000..b20d3f4 --- /dev/null +++ b/cmd/try4/main.go @@ -0,0 +1,60 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "sync" + "time" + + "fillmore-labs.com/blog/goroutines/pkg/fibonacci" + "fillmore-labs.com/blog/goroutines/pkg/mean" +) + +const ( + Count = 1_000 + sequence = 27 +) + +func main() { + start := time.Now() + + m := Run4(Count) + + fmt.Printf("*** Finished %d runs in %v - avg %v, stddev %v\n", Count, time.Since(start), m.Avg, m.Dev) +} + +func Run4(c int) mean.Mean { + s := mean.New() + + var wg sync.WaitGroup + for range c { + queryStart := time.Now() + + wg.Add(1) + go func() { + defer wg.Done() + + _ = fibonacci.Slow(sequence) + + s.Add(time.Since(queryStart)) + }() + } + wg.Wait() + + return s.Result() +} diff --git a/cmd/try4/main_test.go b/cmd/try4/main_test.go new file mode 100644 index 0000000..86f7ac4 --- /dev/null +++ b/cmd/try4/main_test.go @@ -0,0 +1,39 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/cmd/try4" + "go.uber.org/goleak" +) + +func TestRun4(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + defer goleak.VerifyNone(t) + + m := Run4(Count) + + t.Logf("*** Finished %d runs - avg %v, stddev %v\n", Count, m.Avg, m.Dev) +} + +func BenchmarkRun4(b *testing.B) { + _ = Run4(b.N) +} diff --git a/cmd/try5/main.go b/cmd/try5/main.go new file mode 100644 index 0000000..067d8f3 --- /dev/null +++ b/cmd/try5/main.go @@ -0,0 +1,64 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + "fmt" + "runtime" + "time" + + "fillmore-labs.com/blog/goroutines/pkg/fibonacci" + "fillmore-labs.com/blog/goroutines/pkg/mean" + "golang.org/x/sync/semaphore" +) + +const ( + Count = 1_000 + sequence = 27 +) + +func main() { + start := time.Now() + + m := Run5(Count) + + fmt.Printf("*** Finished %d runs in %v - avg %v, stddev %v\n", Count, time.Since(start), m.Avg, m.Dev) +} + +func Run5(c int) mean.Mean { + ctx := context.Background() + s := mean.New() + + numCPU := int64(runtime.GOMAXPROCS(0)) + pool := semaphore.NewWeighted(numCPU) + for range c { + queryStart := time.Now() + + _ = pool.Acquire(ctx, 1) + go func() { + defer pool.Release(1) + + _ = fibonacci.Slow(sequence) + + s.Add(time.Since(queryStart)) + }() + } + _ = pool.Acquire(ctx, numCPU) + + return s.Result() +} diff --git a/cmd/try5/main_test.go b/cmd/try5/main_test.go new file mode 100644 index 0000000..ae0b1e6 --- /dev/null +++ b/cmd/try5/main_test.go @@ -0,0 +1,39 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/cmd/try5" + "go.uber.org/goleak" +) + +func TestRun5(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + defer goleak.VerifyNone(t) + + m := Run5(Count) + + t.Logf("*** Finished %d runs - avg %v, stddev %v\n", Count, m.Avg, m.Dev) +} + +func BenchmarkRun5(b *testing.B) { + _ = Run5(b.N) +} diff --git a/cmd/try6/main.go b/cmd/try6/main.go new file mode 100644 index 0000000..093e7d6 --- /dev/null +++ b/cmd/try6/main.go @@ -0,0 +1,77 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + "fmt" + "runtime" + "sync/atomic" + "time" + + "fillmore-labs.com/blog/goroutines/pkg/fibonacci" + "fillmore-labs.com/blog/goroutines/pkg/mean" + "golang.org/x/sync/semaphore" +) + +const ( + Count = 1_000 + sequence = 27 + Timeout = 100 * time.Millisecond +) + +func main() { + start := time.Now() + + ctx, cancel := context.WithTimeout(context.Background(), Timeout) + defer cancel() + + d, f, m := Run6(ctx, Count) + + fmt.Printf("*** Finished %d runs (%d failed) in %v - avg %v, stddev %v\n", d, f, time.Since(start), m.Avg, m.Dev) +} + +func Run6(ctx context.Context, c int) (d, f int64, r mean.Mean) { + s := mean.New() + var done, failed atomic.Int64 + + numCPU := int64(runtime.GOMAXPROCS(0)) + pool := semaphore.NewWeighted(numCPU) + for range c { + queryStart := time.Now() + + if err := pool.Acquire(ctx, 1); err != nil { + break + } + go func() { + defer pool.Release(1) + + _, err := fibonacci.SlowCtx(ctx, sequence) + + if err == nil { + s.Add(time.Since(queryStart)) + + done.Add(1) + } else { + failed.Add(1) + } + }() + } + _ = pool.Acquire(context.WithoutCancel(ctx), numCPU) + + return done.Load(), failed.Load(), s.Result() +} diff --git a/cmd/try6/main_test.go b/cmd/try6/main_test.go new file mode 100644 index 0000000..a40da30 --- /dev/null +++ b/cmd/try6/main_test.go @@ -0,0 +1,42 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main_test + +import ( + "context" + "testing" + + . "fillmore-labs.com/blog/goroutines/cmd/try6" + "go.uber.org/goleak" +) + +func TestRun6(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + defer goleak.VerifyNone(t) + + ctx, cancel := context.WithTimeout(context.Background(), Timeout) + defer cancel() + s, f, m := Run6(ctx, Count) + + t.Logf("*** Finished %d runs (%d failed) - avg %v, stddev %v\n", s, f, m.Avg, m.Dev) +} + +func BenchmarkRun6(b *testing.B) { + _, _, _ = Run6(context.Background(), b.N) +} diff --git a/cmd/try6_mod/main.go b/cmd/try6_mod/main.go new file mode 100644 index 0000000..1593722 --- /dev/null +++ b/cmd/try6_mod/main.go @@ -0,0 +1,76 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + "fmt" + "sync/atomic" + "time" + + "fillmore-labs.com/blog/goroutines/pkg/fibonacci" + "fillmore-labs.com/blog/goroutines/pkg/mean" + "golang.org/x/sync/semaphore" +) + +const ( + Count = 1_000 + sequence = 27 + Timeout = 100 * time.Millisecond +) + +func main() { + start := time.Now() + + ctx, cancel := context.WithTimeout(context.Background(), Timeout) + defer cancel() + + d, f, m := Run6(ctx, Count) + + fmt.Printf("*** Finished %d runs (%d failed) in %v - avg %v, stddev %v\n", d, f, time.Since(start), m.Avg, m.Dev) +} + +func Run6(ctx context.Context, c int) (d, f int64, r mean.Mean) { + s := mean.New() + var done, failed atomic.Int64 + + numCPU := int64(Count) + pool := semaphore.NewWeighted(numCPU) + for range c { + queryStart := time.Now() + + if err := pool.Acquire(ctx, 1); err != nil { + break + } + go func() { + defer pool.Release(1) + + _, err := fibonacci.SlowCtx(ctx, sequence) + + if err == nil { + s.Add(time.Since(queryStart)) + + done.Add(1) + } else { + failed.Add(1) + } + }() + } + _ = pool.Acquire(context.WithoutCancel(ctx), numCPU) + + return done.Load(), failed.Load(), s.Result() +} diff --git a/cmd/try6_mod/main_test.go b/cmd/try6_mod/main_test.go new file mode 100644 index 0000000..ae04732 --- /dev/null +++ b/cmd/try6_mod/main_test.go @@ -0,0 +1,42 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main_test + +import ( + "context" + "testing" + + . "fillmore-labs.com/blog/goroutines/cmd/try6_mod" + "go.uber.org/goleak" +) + +func TestRun6(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + defer goleak.VerifyNone(t) + + ctx, cancel := context.WithTimeout(context.Background(), Timeout) + defer cancel() + s, f, m := Run6(ctx, Count) + + t.Logf("*** Finished %d runs (%d failed) - avg %v, stddev %v\n", s, f, m.Avg, m.Dev) +} + +func BenchmarkRun6(b *testing.B) { + _, _, _ = Run6(context.Background(), b.N) +} diff --git a/cmd/try7/main.go b/cmd/try7/main.go new file mode 100644 index 0000000..85dde59 --- /dev/null +++ b/cmd/try7/main.go @@ -0,0 +1,75 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "runtime" + "time" + + "fillmore-labs.com/blog/goroutines/pkg/fibonacci" + "fillmore-labs.com/blog/goroutines/pkg/mean" +) + +const ( + Count = 1_000 + sequence = 27 +) + +func main() { + start := time.Now() + + m := Run7(Count) + + fmt.Printf("*** Finished %d runs in %v - avg %v, stddev %v\n", Count, time.Since(start), m.Avg, m.Dev) +} + +func Run7(c int) mean.Mean { + numCPU := runtime.GOMAXPROCS(0) + + loopCount := c / numCPU + remainder := c - (loopCount * numCPU) + + var g int + aggregates := make(chan mean.Aggregate) + for i := range numCPU { + count := loopCount + if i == 0 { + count += remainder + } + + g++ + go func() { + var a mean.Aggregate + + for range count { + queryStart := time.Now() + _ = fibonacci.Slow(sequence) + a.Update(float64(time.Since(queryStart))) + } + + aggregates <- a + }() + } + + var s mean.Aggregate + for range g { + s = s.Combine(<-aggregates) + } + + return s.Finalize() +} diff --git a/cmd/try7/main_test.go b/cmd/try7/main_test.go new file mode 100644 index 0000000..da73fce --- /dev/null +++ b/cmd/try7/main_test.go @@ -0,0 +1,39 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package main_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/cmd/try7" + "go.uber.org/goleak" +) + +func TestRun7(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + defer goleak.VerifyNone(t) + + m := Run7(Count) + + t.Logf("*** Finished %d runs - avg %v, stddev %v\n", Count, m.Avg, m.Dev) +} + +func BenchmarkRun7(b *testing.B) { + _ = Run7(b.N) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b17dacf --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module fillmore-labs.com/blog/goroutines + +go 1.22 + +toolchain go1.22.1 + +require ( + go.uber.org/goleak v1.3.0 + golang.org/x/sync v0.6.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..07038d0 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/fibonacci/parallel1.go b/pkg/fibonacci/parallel1.go new file mode 100644 index 0000000..cfead80 --- /dev/null +++ b/pkg/fibonacci/parallel1.go @@ -0,0 +1,36 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package fibonacci + +// Parallel1 calculates the nth Fibonacci number concurrently by spawning goroutines to calculate the previous two +// numbers. +// +// This allows calculating larger Fibonacci numbers much faster by leveraging multiple CPU cores. +// The goroutines run independently without blocking each other. It returns once both goroutines have completed. +func Parallel1(n int) int { + if n < 2 { + return n + } + + fc1 := make(chan int) + go func() { fc1 <- Parallel1(n - 1) }() + + fc2 := make(chan int) + go func() { fc2 <- Parallel1(n - 2) }() + + return <-fc1 + <-fc2 +} diff --git a/pkg/fibonacci/parallel1_test.go b/pkg/fibonacci/parallel1_test.go new file mode 100644 index 0000000..f4b265b --- /dev/null +++ b/pkg/fibonacci/parallel1_test.go @@ -0,0 +1,47 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package fibonacci_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/pkg/fibonacci" +) + +func TestParallel1(t *testing.T) { + t.Parallel() + + type args struct { + i int + } + tests := []struct { + name string + args args + want int + }{ + {name: "27", args: args{i: 27}, want: 196_418}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := Parallel1(tt.args.i); got != tt.want { + t.Errorf("Parallel1() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/fibonacci/parallel2.go b/pkg/fibonacci/parallel2.go new file mode 100644 index 0000000..d84f175 --- /dev/null +++ b/pkg/fibonacci/parallel2.go @@ -0,0 +1,34 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package fibonacci + +// Parallel2 calculates the nth Fibonacci number by spawning a goroutine to concurrently calculate the previous number +// (n-1) while calculating the second preceding number (n-2) sequentially in the main goroutine. +// +// This avoids launching an extra goroutine compared to [Parallel1], improving performance. +func Parallel2(n int) int { + if n < 2 { + return n + } + + fc1 := make(chan int) + go func() { fc1 <- Parallel2(n - 1) }() + + fn2 := Parallel2(n - 2) + + return <-fc1 + fn2 +} diff --git a/pkg/fibonacci/parallel2_test.go b/pkg/fibonacci/parallel2_test.go new file mode 100644 index 0000000..ad6e47e --- /dev/null +++ b/pkg/fibonacci/parallel2_test.go @@ -0,0 +1,47 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package fibonacci_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/pkg/fibonacci" +) + +func TestParallel2(t *testing.T) { + t.Parallel() + + type args struct { + i int + } + tests := []struct { + name string + args args + want int + }{ + {name: "27", args: args{i: 27}, want: 196_418}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := Parallel2(tt.args.i); got != tt.want { + t.Errorf("Parallel2() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/fibonacci/slow.go b/pkg/fibonacci/slow.go new file mode 100644 index 0000000..01b32bd --- /dev/null +++ b/pkg/fibonacci/slow.go @@ -0,0 +1,35 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package fibonacci + +// Slow recursively calculates the nth Fibonacci number. +// +// It implements the mathematical definition of the Fibonacci sequence, +// where each number is the sum of the two numbers before it. +// +// However, because it is calculating the values recursively without caching or goroutines, +// it will be very slow for large numbers due to redundant function calls. +func Slow(n int) int { + if n < 2 { + return n + } + + fn1 := Slow(n - 1) + fn2 := Slow(n - 2) + + return fn1 + fn2 +} diff --git a/pkg/fibonacci/slow_test.go b/pkg/fibonacci/slow_test.go new file mode 100644 index 0000000..aa4d64f --- /dev/null +++ b/pkg/fibonacci/slow_test.go @@ -0,0 +1,47 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package fibonacci_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/pkg/fibonacci" +) + +func TestSlow(t *testing.T) { + t.Parallel() + + type args struct { + i int + } + tests := []struct { + name string + args args + want int + }{ + {name: "27", args: args{i: 27}, want: 196_418}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if got := Slow(tt.args.i); got != tt.want { + t.Errorf("Slow() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/fibonacci/slowctx.go b/pkg/fibonacci/slowctx.go new file mode 100644 index 0000000..22a0843 --- /dev/null +++ b/pkg/fibonacci/slowctx.go @@ -0,0 +1,50 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package fibonacci + +import ( + "context" + "fmt" +) + +// SlowCtx calculates the nth fibonacci number. +// +// Each recursive call can be canceled by the context through its Done channel. +func SlowCtx(ctx context.Context, n int) (int, error) { + select { + case <-ctx.Done(): + return 0, fmt.Errorf("fibonacci canceled: %w", context.Cause(ctx)) + + default: + } + + if n < 2 { + return n, nil + } + + fn1, err1 := SlowCtx(ctx, n-1) + if err1 != nil { + return 0, err1 + } + + fn2, err2 := SlowCtx(ctx, n-2) + if err2 != nil { + return 0, err2 + } + + return fn1 + fn2, nil +} diff --git a/pkg/fibonacci/slowctx_test.go b/pkg/fibonacci/slowctx_test.go new file mode 100644 index 0000000..4a03c62 --- /dev/null +++ b/pkg/fibonacci/slowctx_test.go @@ -0,0 +1,64 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package fibonacci_test + +import ( + "context" + "testing" + + . "fillmore-labs.com/blog/goroutines/pkg/fibonacci" +) + +func TestSlowCtx(t *testing.T) { + t.Parallel() + + contextCanceled := func() context.Context { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + return ctx + } + + type args struct { + ctx context.Context + i int + } + tests := []struct { + name string + args args + want int + wantErr bool + }{ + {name: "27", args: args{ctx: context.Background(), i: 27}, want: 196_418, wantErr: false}, + {name: "canceled", args: args{ctx: contextCanceled(), i: 27}, want: 0, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got, err := SlowCtx(tt.args.ctx, tt.args.i) + if (err != nil) != tt.wantErr { + t.Errorf("SlowCtx() error = %v, wantErr %v", err, tt.wantErr) + + return + } + if got != tt.want { + t.Errorf("SlowCtx() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/gc/README.md b/pkg/gc/README.md new file mode 100644 index 0000000..8426ca2 --- /dev/null +++ b/pkg/gc/README.md @@ -0,0 +1,18 @@ +# ConcurrentMark + +[ConcurrentMark](https://pkg.go.dev/github.com/containerd/containerd@v1.7.14/gc#ConcurrentMark) is adapted from +[the containerd source](https://github.com/containerd/containerd/blob/v1.7.14/gc/gc.go#L108). + + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pkg/gc/concurrentmark.go b/pkg/gc/concurrentmark.go new file mode 100644 index 0000000..bff00bf --- /dev/null +++ b/pkg/gc/concurrentmark.go @@ -0,0 +1,63 @@ +// Copyright The containerd Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package gc + +import ( + "sync" +) + +func ConcurrentMark(root <-chan Node, refs func(Node, func(Node))) map[Node]struct{} { + var ( + grays = make(chan Node) + seen = map[Node]struct{}{} // or not "white", basically "seen" + wg sync.WaitGroup + ) + + go func() { + for gray := range grays { + if _, ok := seen[gray]; ok { + wg.Done() + + continue + } + seen[gray] = struct{}{} // post-mark this as non-white + + go func() { + defer wg.Done() + + send := func(n Node) { + wg.Add(1) + grays <- n + } + + refs(gray, send) + }() + } + }() + + for r := range root { + wg.Add(1) + grays <- r + } + + // Wait for outstanding grays to be processed + wg.Wait() + + close(grays) + + return seen +} diff --git a/pkg/gc/concurrentmark_test.go b/pkg/gc/concurrentmark_test.go new file mode 100644 index 0000000..fb4ceee --- /dev/null +++ b/pkg/gc/concurrentmark_test.go @@ -0,0 +1,29 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package gc_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/pkg/gc" +) + +func BenchmarkConcurrentMark(b *testing.B) { + for range b.N { + _ = ConcurrentMark(MakeRootC(), Lookupc) + } +} diff --git a/pkg/gc/gc.go b/pkg/gc/gc.go new file mode 100644 index 0000000..cf2e7bb --- /dev/null +++ b/pkg/gc/gc.go @@ -0,0 +1,39 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package gc + +type Node string + +func Lookupc(ref Node, fn func(Node)) { + if len(ref) > 8 { + return + } + + for _, n := range "ABCD" { + fn(ref + Node(n)) + } +} + +func MakeRootC() <-chan Node { + rootC := make(chan Node) + go func() { + rootC <- "" + close(rootC) + }() + + return rootC +} diff --git a/pkg/gc/mark.go b/pkg/gc/mark.go new file mode 100644 index 0000000..23eddf7 --- /dev/null +++ b/pkg/gc/mark.go @@ -0,0 +1,48 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package gc + +func Mark(root <-chan Node, refs func(Node, func(Node))) map[Node]struct{} { + var grays []Node + for r := range root { + grays = append(grays, r) + } + + seen := map[Node]struct{}{} // or not "white", basically "seen" + for len(grays) > 0 { + var next []Node + for _, gray := range grays { + if _, ok := seen[gray]; ok { + continue + } + seen[gray] = struct{}{} // post-mark this as non-white + + next = append(next, gray) + } + grays = nil + + send := func(n Node) { + grays = append(grays, n) + } + + for _, ref := range next { + refs(ref, send) + } + } + + return seen +} diff --git a/pkg/gc/mark_test.go b/pkg/gc/mark_test.go new file mode 100644 index 0000000..5534737 --- /dev/null +++ b/pkg/gc/mark_test.go @@ -0,0 +1,29 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package gc_test + +import ( + "testing" + + . "fillmore-labs.com/blog/goroutines/pkg/gc" +) + +func BenchmarkMark(b *testing.B) { + for range b.N { + _ = Mark(MakeRootC(), Lookupc) + } +} diff --git a/pkg/mean/aggregate.go b/pkg/mean/aggregate.go new file mode 100644 index 0000000..0ced5aa --- /dev/null +++ b/pkg/mean/aggregate.go @@ -0,0 +1,64 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package mean + +import ( + "math" + "time" +) + +// Mean represents the average and standard deviation of a set of durations. +type Mean struct { + Avg time.Duration + Dev time.Duration +} + +// Aggregate accumulates the mean and standard deviation of a set of durations. +type Aggregate struct { + count int + mean, m2 float64 +} + +// Update adds a new value to the aggregate. +func (a *Aggregate) Update(value float64) { + a.count++ + delta := value - a.mean + a.mean += delta / float64(a.count) + delta2 := value - a.mean + a.m2 += delta * delta2 +} + +// Finalize returns the final mean and standard deviation of the values added to the aggregate. +// It is called after all values have been added. +func (a *Aggregate) Finalize() Mean { + var stdDev float64 + if a.count > 0 { + stdDev = math.Sqrt(a.m2 / float64(a.count)) + } + + return Mean{Avg: time.Duration(a.mean), Dev: time.Duration(stdDev)} +} + +// Combine combines two aggregates. +func (a *Aggregate) Combine(b Aggregate) Aggregate { + count := a.count + b.count + mean := (float64(a.count)*a.mean + float64(b.count)*b.mean) / float64(count) + delta := b.mean - a.mean + m2 := a.m2 + b.m2 + delta*delta*float64(a.count)*float64(b.count)/(float64(count)) + + return Aggregate{count: count, mean: mean, m2: m2} +} diff --git a/pkg/mean/mean.go b/pkg/mean/mean.go new file mode 100644 index 0000000..27da984 --- /dev/null +++ b/pkg/mean/mean.go @@ -0,0 +1,62 @@ +// Copyright 2024 Oliver Eikemeier. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +package mean + +import ( + "time" +) + +// Summarizer collects durations and returns the calculated [Mean]. +type Summarizer struct { + in chan<- float64 + out <-chan Mean +} + +// New returns a new [Summarizer] and begins collecting durations. +// +// The returned [Summarizer] must be finalized by calling [Summarizer.Result]. +func New() Summarizer { + in := make(chan float64) + out := make(chan Mean) + go variance(in, out) + + return Summarizer{in, out} +} + +// Add the given duration to the result. +func (m Summarizer) Add(d time.Duration) { + m.in <- float64(d.Nanoseconds()) +} + +// Result returns the calculated [Mean]. +func (m Summarizer) Result() Mean { + close(m.in) + + return <-m.out +} + +// variance computes variance using Welford's algorithm. +func variance(in <-chan float64, out chan<- Mean) { + var a Aggregate + + for d := range in { + a.Update(d) + } + + out <- a.Finalize() + close(out) +}