{"id":17296588,"url":"https://github.com/chen3feng/atomiccounter","last_synced_at":"2025-04-14T12:04:22.216Z","repository":{"id":61622987,"uuid":"526670653","full_name":"chen3feng/atomiccounter","owner":"chen3feng","description":"A High Performance Atomic Counter for Write-More-Read-Less Scenario in Go","archived":false,"fork":false,"pushed_at":"2022-09-05T18:33:54.000Z","size":67,"stargazers_count":20,"open_issues_count":1,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-14T12:04:17.448Z","etag":null,"topics":["atomic","go","longadder"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chen3feng.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":"2022-08-19T16:04:35.000Z","updated_at":"2025-03-25T14:25:21.000Z","dependencies_parsed_at":"2022-10-19T18:45:31.654Z","dependency_job_id":null,"html_url":"https://github.com/chen3feng/atomiccounter","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chen3feng%2Fatomiccounter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chen3feng%2Fatomiccounter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chen3feng%2Fatomiccounter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chen3feng%2Fatomiccounter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chen3feng","download_url":"https://codeload.github.com/chen3feng/atomiccounter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248877985,"owners_count":21176243,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["atomic","go","longadder"],"created_at":"2024-10-15T11:13:14.504Z","updated_at":"2025-04-14T12:04:22.192Z","avatar_url":"https://github.com/chen3feng.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# atomiccounter\n\nEnglish | [简体中文](README_zh.md)\n\n[![License Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-red.svg)](COPYING)\n[![Golang](https://img.shields.io/badge/Language-go1.18+-blue.svg)](https://go.dev/)\n![Build Status](https://github.com/chen3feng/atomiccounter/actions/workflows/go.yml/badge.svg)\n[![Coverage Status](https://coveralls.io/repos/github/chen3feng/atomiccounter/badge.svg?branch=master)](https://coveralls.io/github/chen3feng/atomiccounter?branch=master)\n[![GoReport](https://goreportcard.com/badge/github.com/securego/gosec)](https://goreportcard.com/report/github.com/chen3feng/atomiccounter)\n[![Go Reference](https://pkg.go.dev/badge/github.com/chen3feng/atomiccounter.svg)](https://pkg.go.dev/github.com/chen3feng/atomiccounter)\n\nA High Performance Atomic Counter for Concurrent Write-More-Read-Less Scenario in Go.\n\nSimilar to [LongAdder](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAdder.html) in Java, or\n[ThreadCachedInt](https://github.com/facebook/folly/blob/main/folly/docs/ThreadCachedInt.md) in [folly](https://github.com/facebook/folly),\nIn scenarios of high concurrent writes but few reads, it can provide dozens of times the write performance than `sync/atomic`.\n\n## Benchmark\n\nper 100 calls.\n\nUnder MacOS with M1 Pro:\n\n```console\ngoos: darwin\ngoarch: arm64\npkg: github.com/chen3feng/atomiccounter\nBenchmarkNonAtomicAdd-10        47337121                22.14 ns/op\nBenchmarkAtomicAdd-10             180942                 6861 ns/op\nBenchmarkCounter-10             14871549                81.02 ns/op\n```\n\nUnder Linux:\n\n```console\ngoos: linux\ngoarch: amd64\npkg: github.com/chen3feng/atomiccounter\ncpu: Intel(R) Xeon(R) Gold 6133 CPU @ 2.50GHz\nBenchmarkNonAtomicAdd-16    \t 9508723\t       135.3 ns/op\nBenchmarkAtomicAdd-16       \t  582798\t        2070 ns/op\nBenchmarkCounter-16         \t 4748263\t       263.1 ns/op\n```\n\nFrom top to bottom are writing time-consuming of non-atomic (and thus unsafe), atomic, and `atomiccounter`.\nIt can be seen that in the case of high concurrent writes, `atomiccounter` is only a few times more slower\nthan non-atomic writes, but much faster than atomic writes.\n\nBut it is much slower reads:\n\n```console\ngoos: darwin\ngoarch: arm64\npkg: github.com/chen3feng/atomiccounter\nBenchmarkNonAtomicRead-10       1000000000               0.3112 ns/op\nBenchmarkAtomicRead-10          1000000000               0.5336 ns/op\nBenchmarkCounterRead-10         54609476                  21.20 ns/op\n```\n\nIn addition, each `atomiccounter.Int64` object needs to consume 8K memory, so please only use it in a small number of\nscenarios with a large number of concurrent writes but few reads, such as counting the number of requests.\n\n## Compare with Similar Libraries\n\nI found 3 similar Libraries in GitHub (the later 2 seems same):\n\n- https://github.com/puzpuzpuz/xsync\n- https://github.com/linxGnu/go-adder\n- https://github.com/line/garr\n\nAnd got the following benchmark result under the Apple M1 Pro chip.\n\n```console\nBenchmarkAdd_NonAtomic-10               49337793                22.02 ns/op\nBenchmarkAdd_Atomic-10                    206678                 6854 ns/op\nBenchmarkAdd_AtomicCounter-10           14658782                82.22 ns/op\nBenchmarkAdd_XsyncCounter-10             9599529                144.6 ns/op\nBenchmarkAdd_GoAdder-10                   825858                 1339 ns/op\nBenchmarkAdd_GarrAdder-10                 915090                 1305 ns/op\n\nBenchmarkRead_NonAtomic-10             263460258                4.087 ns/op\nBenchmarkRead_Atomic-10                172530186                6.945 ns/op\nBenchmarkRead_AtomicCounter-10           2793618                425.2 ns/op\nBenchmarkRead_XSyncCounter-10            2396407                489.6 ns/op\nBenchmarkRead_GoAdder-10                32101244                36.02 ns/op\nBenchmarkRead_GarrAdder-10              29420326                35.40 ns/op\n```\n\nObviously, `atomiccounter` is the fastest for concurrent writing.\n\nSee [atomiccounter_bench](https://github.com/chen3feng/atomiccounter_bench) for source code.\n\n## Implementation\n\nData race is one of the biggest performance killers in multi-core programs. For counters with a large number of writes,\nif ordinary atomic is used, the performance will be severely affected.\n\nIn scenarios with few reads, a common solution is to spread the writes across different variables and accumulate them when they are read.\nSuch as Java's LongAdder and folly ThreadCachedInt, and per-cpu in the Linux kernel are all used this this method.\nAlthough the implementation details are different, the idea is similar.\n\nAt present, there is no well-known implementation for this kind of purpose in go, so I implemented this library.\n\nTo reduce memory footprint, multiple `Int64` objects may share same memory chunk.\n\n### Memory Layout\n\nAn int64 array of multiple sizes of CPU [cache line size](https://en.wikipedia.org/wiki/CPU_cache#Cache_entries) becomes a cell.\nA group of cells is called a chunk.\n\nThe size of the cell is an integer multiple of the cache line size of the CPU, and the first and last fields are paded\nwith blanks of the size of the cache line size, thus avoiding [false sharing](https://www.google.com/search?q=false+sharing).\n\nThe `chunk.lastIndex` member is used to record the last unused index for allocating the `Int64` object.\n\nEach `Int64` object contains 2 fields: the chunk pointer and the index in the cell, so multiple `Int64` objects can share the same chunk,\nbut access elements with different indices in each cell.\n\n### Allocate an `Int64` object\n\nThe address of the last created chunk is recorded in the global variable `lastChunk`. When an `Int64` object is created,\nits `lastIndex` is increased. If it reachs the number of int64 in the cell,\nit means that this chunk has been totally allocated and a new chunk needs to be created.\n\n### Access an `Int64` object\n\nPlease first understand Go's [GMP](https://www.google.com/search?q=golang+GMP) scheduling model.\n\nThe best performance is to get the current subscript of `M` in Go and directly access the corresponding `cell`,\nso that there will be no conflict between different `M`s, and even avoid using atomic operations.\n\nBut I haven't found a way to get the `M`'s subscript.\n\nTherefore, this implementation uses the hash of the address of `M` as the subscript to access the cell,\nand the measured effect is also quite good.\n\nAs long as the number of cells in each chunk is larger than the common number of CPU cores,\nthe impact of hash collisions can be reduced, so that different M will have a high probability\nof accessing different cells.\n\nWhen increasing the value of an `Int64` object, the hash of current `M`'s' address is used as the\nsubscript to obtain the corresponding cell in the chunk.\nThen use the `Int64.index` member as a subscript to access the int64 array in this cell.\n\nWhen reading, traverse the value indexed by `Int64.index` in all cell arrays and accumulated the value.\n\n\u003c!-- gomarkdoc:embed:start --\u003e\n\n\u003c!-- Code generated by gomarkdoc. DO NOT EDIT --\u003e\n\n# atomiccounter\n\n```go\nimport \"github.com/chen3feng/atomiccounter\"\n```\n\nPackage atomiccounter provides an atomic counter for high throughput concurrent writing and rare reading scenario.\n\n\u003cdetails\u003e\u003csummary\u003eExample\u003c/summary\u003e\n\u003cp\u003e\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/chen3feng/atomiccounter\"\n\t\"sync\"\n)\n\nfunc main() {\n\tcounter := atomiccounter.MakeInt64()\n\tvar wg sync.WaitGroup\n\tfor i := 0; i \u003c 100; i++ {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tcounter.Inc()\n\t\t\twg.Done()\n\t\t}()\n\n\t}\n\twg.Wait()\n\tfmt.Println(counter.Read())\n\tcounter.Set(0)\n\tfmt.Println(counter.Read())\n\tcounter.Add(10)\n\tfmt.Println(counter.Read())\n}\n```\n\n#### Output\n\n```\n100\n0\n10\n```\n\n\u003c/p\u003e\n\u003c/details\u003e\n\n## Index\n\n- [type Int64](\u003c#type-int64\u003e)\n  - [func MakeInt64() Int64](\u003c#func-makeint64\u003e)\n  - [func (c *Int64) Add(n int64)](\u003c#func-int64-add\u003e)\n  - [func (c *Int64) Inc()](\u003c#func-int64-inc\u003e)\n  - [func (c *Int64) Read() int64](\u003c#func-int64-read\u003e)\n  - [func (c *Int64) Set(n int64)](\u003c#func-int64-set\u003e)\n  - [func (c *Int64) Swap(n int64) int64](\u003c#func-int64-swap\u003e)\n\n\n## type [Int64](\u003chttps://github.com/chen3feng/atomiccounter/blob/master/int64.go#L19-L22\u003e)\n\nInt64 is an int64 atomic counter.\n\n```go\ntype Int64 struct {\n    // contains filtered or unexported fields\n}\n```\n\n### func [MakeInt64](\u003chttps://github.com/chen3feng/atomiccounter/blob/master/int64.go#L57\u003e)\n\n```go\nfunc MakeInt64() Int64\n```\n\nMakeInt64 creates a new Int64 object. Int64 objects must be created by this function, simply initialized doesn't work.\n\n### func \\(\\*Int64\\) [Add](\u003chttps://github.com/chen3feng/atomiccounter/blob/master/int64.go#L72\u003e)\n\n```go\nfunc (c *Int64) Add(n int64)\n```\n\nAdd adds n to the counter.\n\n### func \\(\\*Int64\\) [Inc](\u003chttps://github.com/chen3feng/atomiccounter/blob/master/int64.go#L78\u003e)\n\n```go\nfunc (c *Int64) Inc()\n```\n\nInc adds 1 to the counter.\n\n### func \\(\\*Int64\\) [Read](\u003chttps://github.com/chen3feng/atomiccounter/blob/master/int64.go#L91\u003e)\n\n```go\nfunc (c *Int64) Read() int64\n```\n\nRead return the current value. it is a little slow so it should not be called frequently. Th result is not guaranteed to be accurate in race conditions.\n\n### func \\(\\*Int64\\) [Set](\u003chttps://github.com/chen3feng/atomiccounter/blob/master/int64.go#L83\u003e)\n\n```go\nfunc (c *Int64) Set(n int64)\n```\n\nSet set the value of the counter to n.\n\n### func \\(\\*Int64\\) [Swap](\u003chttps://github.com/chen3feng/atomiccounter/blob/master/int64.go#L101\u003e)\n\n```go\nfunc (c *Int64) Swap(n int64) int64\n```\n\nSwap returns the current value and swap it with n.\n\n\n\nGenerated by [gomarkdoc](\u003chttps://github.com/princjef/gomarkdoc\u003e)\n\n\n\u003c!-- gomarkdoc:embed:end --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchen3feng%2Fatomiccounter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchen3feng%2Fatomiccounter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchen3feng%2Fatomiccounter/lists"}