{"id":50544258,"url":"https://github.com/saesh/mandelbrot","last_synced_at":"2026-06-03T22:31:26.368Z","repository":{"id":57559024,"uuid":"156524295","full_name":"saesh/mandelbrot","owner":"saesh","description":"A Go library to render the Mandelbrot set concurrently","archived":false,"fork":false,"pushed_at":"2018-12-21T15:19:29.000Z","size":1448,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-04-19T10:08:09.183Z","etag":null,"topics":["concurrency","fractal","go","mandelbrot"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saesh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-07T09:46:05.000Z","updated_at":"2024-09-11T20:42:36.000Z","dependencies_parsed_at":"2022-08-28T11:41:43.878Z","dependency_job_id":null,"html_url":"https://github.com/saesh/mandelbrot","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/saesh/mandelbrot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saesh%2Fmandelbrot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saesh%2Fmandelbrot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saesh%2Fmandelbrot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saesh%2Fmandelbrot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saesh","download_url":"https://codeload.github.com/saesh/mandelbrot/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saesh%2Fmandelbrot/sbom","scorecard":{"id":794747,"data":{"date":"2025-08-11","repo":{"name":"github.com/saesh/mandelbrot","commit":"48d1de20dee1592aa1850afe9b519c979a7036fc"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.2,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":1,"reason":"Found 1/8 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 3 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T08:37:03.636Z","repository_id":57559024,"created_at":"2025-08-23T08:37:03.637Z","updated_at":"2025-08-23T08:37:03.637Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33883102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concurrency","fractal","go","mandelbrot"],"created_at":"2026-06-03T22:31:25.620Z","updated_at":"2026-06-03T22:31:26.359Z","avatar_url":"https://github.com/saesh.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Mandelbrot\n\nThis Go library generates the Mandelbrot set and served as my playground to learn about concurrency in Go. Generating Mandelbrot set images sounded like a fun idea and a good fit because it requires a lot of calculations for a large amount of data. And you get some pretty images, Yay!\nI wanted to find out how go routines and channels work and experimented with some concurrency approaches I came up with.\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"/assets/image01.jpeg\" width=\"220\"\u003e\n    \u003cimg src=\"/assets/image02.jpeg\" width=\"220\"\u003e\n    \u003cimg src=\"/assets/image03.jpeg\" width=\"220\"\u003e\n    \u003cimg src=\"/assets/image04.jpeg\" width=\"220\"\u003e\n\u003c/p\u003e\n\n## How to use it?\n\n`mandelbrot` assumes you have Go 1.11+ installed as it uses Go modules for its dependencies.\n\n```text\n$ git clone https://github.com/saesh/mandelbrot \u0026\u0026 cd mandelbrot\n$ go run examples/mandelbrot-jpeg/main.go\n```\n\nIf you are using Go in version \u003c1.11 you can install the depencencies manually:\n\n```text\ngo get github.com/lucasb-eyer/go-colorful\n```\n\n---\n\nThe repository contains the library for generating Mandelbrot set image data in `pkg` and some example programs in `examples`.\n\nThe `Mandelbrot` type exposes the image data in the property `ImageData`, or it can be encoded as a JPEG file with the `WriteJpeg` method.\n\nTo create an image import the library and create a `Mandelbrot` object with parameters for the generation of the set:\n\n```go\npackage main\n\nimport (\n\t\"github.com/saesh/mandelbrot/pkg/colors\"\n\tg \"github.com/saesh/mandelbrot/pkg/generator\"\n)\n\nfunc main() {\n    mb := \u0026g.Mandelbrot{}\n\n    mb.Width = 1000\n    mb.Height = 1000\n    mb.MaxIterations = 600\n    mb.Colors = colors.Hue\n\n    mb.X = 0\n    mb.Y = 0\n    mb.R = 4\n\n    mb.Render()\n    mb.WriteJpeg(\"mandelbrot.jpeg\", 90)\n}\n```\n\nThe `Render` method blocks until all pixels are generated. The quality of the JPEG file can be set with the second parameter to `WriteJpeg`.\n\n## Concurrency\n\nTo understand go routines and channels I took several approaches to write this library in a concurrent fashion:\n\n### Sequential rendering\n\n`Mandelbrot.RenderSequentially`: Obviously sequential rendering is not concurrent but it was the first implementation of the algorithm and used a baseline. During this approach I found out, that `math.Pow` is incredibly\nslow.\n\n### Unlimited go routines\n\n`Mandelbrot.RenderWithUnlimitedRoutines`: Next step was to spawn a go routine per Coordinate (pixel) and see what happens. The result were even longer render times than the sequential rendering. This is due to the coordination of millions of go routines in the go runtime. Although CPU usage was ~80% accross all cores the render times were awefully slow. Also memory usage went up as each go routine needs a certain amount of memory (~4KB). Having millions of them waiting lets the memory usage grow fast.\n\n### Limit the maximum number of go routines\n\n`Mandelbrot.RenderWithMaxRoutines`: As unlimited go routines were not really fast, the next approach was to limit the number of go routines. I limited them to 100. Again, each routine got one Coordinate to render. The render times were faster! But not by that much, still dissappointing.\n\n### Buffered channel for N CPU go routines\n\n`Mandelbrot.RenderWithBufferedChannel`: Next, the data was split up in batches. The number of batches is equal to the number of CPUs. And for each batch one go routine was spawned. The Coordinate channel's buffer size is that of the length of each batch. So on a 4 core system rendering a 4000 pixel image 4 go routines would spawn, each processing 1000 pixels. This was really fast and let all cores run at 100% with almost no system usage.\n\nI plotted the render times of each approach:\n\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"/assets/rendering-times-chart.png\"\u003e\n\u003c/p\u003e\n\nThe data was gathered on a 4 core Intel Core i5-3570 at 3.6Ghz, with Go 1.11.2 on a Linux system. The maximum number of iterations for the algorithm was 300.\n\n## Credits\n\n   - This article laid out the foundation for the Mandelbrot set calculations: https://blog.jfo.click/the-mandelwat-set/\n   - Coloring the pixels based on iteration and complex numbers is hard, these answers on Stackoverflow helped immensely:\n      - https://stackoverflow.com/a/16505538 \n      - https://stackoverflow.com/a/1243788\n      - https://stackoverflow.com/a/18678856\n   - I didn't want to generate gradient colors myself, so I used `go-colorful` which includes a gradient generator in their docs: https://github.com/lucasb-eyer/go-colorful/blob/master/doc/gradientgen/gradientgen.go\n\n## Further reading\n\n   - https://blog.golang.org/pipelines\n   - https://medium.com/@thejasbabu/concurrency-patterns-golang-5c5e1bcd0833\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaesh%2Fmandelbrot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaesh%2Fmandelbrot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaesh%2Fmandelbrot/lists"}