├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── feature_reqeust.yml │ └── question.yml └── workflows │ ├── codeql-analysis.yml │ └── push-check.yml ├── .gitignore ├── .licenserc.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CREDITS ├── LICENSE ├── README.md ├── cache └── asynccache │ ├── README.md │ ├── asynccache.go │ ├── asynccache_test.go │ └── atomic_error.go ├── check_branch_name.sh ├── cloud ├── circuitbreaker │ ├── README.MD │ ├── README_ZH.MD │ ├── breaker.go │ ├── breaker_test.go │ ├── circuitbreaker.go │ ├── counter.go │ ├── counter_test.go │ ├── interface.go │ ├── metricer.go │ ├── metricer_test.go │ ├── panel.go │ ├── panel_test.go │ ├── per_p_metricer.go │ ├── per_p_metricer_test.go │ ├── test_helper.go │ └── tripfunc.go └── metainfo │ ├── README-CN.md │ ├── README.md │ ├── backward.go │ ├── backward_test.go │ ├── helper_test.go │ ├── http.go │ ├── http_test.go │ ├── info.go │ ├── info_test.go │ ├── kv.go │ ├── kvstore.go │ ├── kvstore_test.go │ ├── pool.go │ ├── pool_test.go │ ├── utils.go │ └── utils_test.go ├── collection ├── hashset │ ├── README.md │ ├── hashset.go │ ├── hashset_bench_test.go │ ├── hashset_test.go │ ├── types.go │ └── types_gen.go ├── lscq │ ├── asm.go │ ├── asm.s │ ├── asm_arm64.go │ ├── asm_arm64.s │ ├── bench_test.go │ ├── lscq.go │ ├── lscq_test.go │ ├── readme.md │ ├── types.go │ ├── types_gen.go │ └── util.go ├── skipmap │ ├── asm.s │ ├── bench.sh │ ├── bench_test.go │ ├── flag.go │ ├── oparray.go │ ├── oparry_test.go │ ├── readme.md │ ├── skipmap.go │ ├── skipmap_bench_test.go │ ├── skipmap_str_test.go │ ├── skipmap_test.go │ ├── types.go │ ├── types_gen.go │ └── util.go ├── skipset │ ├── asm.s │ ├── bench.sh │ ├── flag.go │ ├── flag_test.go │ ├── oparry.go │ ├── oparry_test.go │ ├── readme.md │ ├── skipset.go │ ├── skipset_bench_test.go │ ├── skipset_test.go │ ├── types.go │ ├── types_gen.go │ └── util.go └── zset │ ├── oparry.go │ ├── opt.go │ ├── readme.md │ ├── skiplist.go │ ├── zset.go │ ├── zset_bench_test.go │ └── zset_test.go ├── go.mod ├── go.sum ├── internal ├── benchmark │ ├── linkedq │ │ └── linkedq.go │ └── msq │ │ └── msq.go ├── hack │ └── hack.go ├── runtimex │ ├── asm.s │ ├── ppin.go │ ├── ppin_test.go │ ├── readunaligned.go │ ├── readunaligned_bigendian.go │ ├── runtime_go_1.22.go │ ├── runtime_pre_go_1.22.go │ └── runtime_test.go └── wyhash │ ├── digest.go │ ├── digest_test.go │ ├── wyhash.go │ └── wyhash_test.go ├── lang ├── channel │ ├── channel.go │ ├── channel_example_test.go │ └── channel_test.go ├── dirtmake │ ├── bytes.go │ └── bytes_test.go ├── fastrand │ ├── fastrand.go │ ├── fastrand_test.go │ └── readme.md ├── mcache │ ├── README.md │ ├── mcache.go │ ├── mcache_test.go │ ├── utils.go │ └── utils_test.go ├── span │ ├── span.go │ └── span_test.go ├── stringx │ ├── README.md │ ├── doc.go │ ├── exmaple_test.go │ ├── is.go │ ├── is_test.go │ ├── stringx.go │ └── stringx_test.go └── syncx │ ├── README.md │ ├── asm.s │ ├── linkname.go │ ├── pool.go │ ├── pool_race.go │ ├── pool_test.go │ ├── poolqueue.go │ ├── rwmutex.go │ └── rwmutex_test.go └── util ├── gctuner ├── README.md ├── finalizer.go ├── finalizer_test.go ├── mem.go ├── mem_test.go ├── tuner.go └── tuner_test.go ├── gopool ├── README.md ├── config.go ├── gopool.go ├── pool.go ├── pool_test.go └── worker.go ├── logger ├── README.md ├── default.go └── logger.go └── xxhash3 ├── README.md ├── accum.go ├── accum_amd64.go ├── accum_scalar.go ├── avx2_amd64.s ├── consts.go ├── correctness_test.go ├── hash.go ├── hash128.go ├── internal ├── avo │ ├── avx.go │ ├── build.sh │ ├── gen.go │ └── sse.go └── xxh3_raw │ └── xxh3_raw.go ├── performance_test.go ├── sse2_amd64.s └── util.go /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # global code owners 2 | * @PureWhiteWu @zhangyunhao116 @joway 3 | 4 | # cache code owners 5 | /cache/asynccache @PureWhiteWu @zhangyunhao116 6 | 7 | # cloud code owners 8 | /cloud/circuitbreaker @PureWhiteWu @zhangyunhao116 9 | /cloud/metainfo @xiaost @YangruiEmma @jayantxie 10 | 11 | # collection code owners 12 | /collection/hashset @lyeeeeee @PureWhiteWu 13 | /collection/skipmap @zhangyunhao116 @PureWhiteWu 14 | /collection/skipset @zhangyunhao116 @PureWhiteWu 15 | /collection/zset @SilverRainZ @zhangyunhao116 16 | 17 | # lang code owners 18 | /lang/fastrand @zhangyunhao116 @PureWhiteWu 19 | /lang/mcache @PureWhiteWu @zhangyunhao116 20 | /lang/syncx @PureWhiteWu @zhangyunhao116 21 | /lang/stringx @kaka19ace 22 | /lang/channel @joway 23 | 24 | # util code owners 25 | /util/gopool @PureWhiteWu @zhangyunhao116 26 | /util/logger @PureWhiteWu @zhangyunhao116 27 | /util/xxhash3 @lyeeeeee 28 | /util/gctuner @joway 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | 10 | - type: input 11 | attributes: 12 | label: Operating System 13 | description: What operating system are you using? 14 | placeholder: "Example: Debian GNU/Linux 9 (stretch)" 15 | validations: 16 | required: true 17 | - type: input 18 | attributes: 19 | label: Go Version 20 | description: What version of golang are you using? 21 | placeholder: "Example: go1.16.5 linux/amd64" 22 | validations: 23 | required: true 24 | - type: input 25 | attributes: 26 | label: Package Version 27 | description: What version of bytedance/gopkg are you using? 28 | placeholder: "Example: 20210913/main/develop" 29 | validations: 30 | required: true 31 | 32 | - type: textarea 33 | attributes: 34 | label: Affected Packages 35 | description: Which packages are affected by this issue? 36 | placeholder: | 37 | One package per line, for example: 38 | 39 | - lang/fastrand 40 | - collection/skipset 41 | - ... 42 | validations: 43 | required: true 44 | - type: textarea 45 | attributes: 46 | label: Expected Behavior 47 | description: What did you expect to happen? 48 | validations: 49 | required: true 50 | - type: textarea 51 | attributes: 52 | label: Actual Behavior 53 | description: What actually happened? 54 | validations: 55 | required: true 56 | - type: textarea 57 | attributes: 58 | label: Reproduction Steps 59 | description: How do you trigger this bug? Please walk us through it step by step. 60 | placeholder: | 61 | 1. 62 | 2. 63 | 3. 64 | ... 65 | validations: 66 | required: true 67 | 68 | - type: textarea 69 | attributes: 70 | label: Other Information 71 | description: Do you still need to add any information? 72 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Security Bug Report 4 | url: https://github.com/bytedance/gopkg/blob/develop/CONTRIBUTING.md#3-security-bugs 5 | about: | 6 | Please do not report the safe disclosure of bugs to public issues. 7 | Contact us by email (gopkg@bytedance.com) 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_reqeust.yml: -------------------------------------------------------------------------------- 1 | # Reference: https://github.com/jekyll/jekyll/blob/master/.github/ISSUE_TEMPLATE/feature_request.md 2 | 3 | name: Feature Request 4 | description: File a feature request 5 | labels: ["enhancement"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for taking the time to fill out this feature request! 11 | 12 | - type: textarea 13 | attributes: 14 | label: Summary 15 | description: Please give a one-paragraph explanation of the feature. 16 | validations: 17 | required: true 18 | 19 | - type: textarea 20 | attributes: 21 | label: Motivation 22 | description: Why do you want to see this feature? 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | attributes: 28 | label: Explanation 29 | description: | 30 | Please explain the proposal as if it was already included in the project and you 31 | were teaching it to another programmer. That generally means: 32 | 33 | - Introducing new named concepts. 34 | - Explaining the feature largely in terms of examples. 35 | - If applicable, provide sample error messages, deprecation warnings, or 36 | migration guidance. 37 | 38 | *If this is a small feature, you may omit this section.* 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | name: Question 2 | description: Ask a question 3 | labels: ["question"] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Question 8 | description: What do you want to know? 9 | validations: 10 | required: true 11 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ develop, main ] 17 | paths-ignore: 18 | - '**.md' 19 | pull_request: 20 | # The branches below must be a subset of the branches above 21 | branches: [ develop ] 22 | paths-ignore: 23 | - '**.md' 24 | schedule: 25 | - cron: '35 21 * * 4' 26 | 27 | jobs: 28 | analyze: 29 | name: Analyze 30 | runs-on: ubuntu-latest 31 | permissions: 32 | actions: read 33 | contents: read 34 | security-events: write 35 | 36 | strategy: 37 | fail-fast: false 38 | matrix: 39 | language: [ 'go' ] 40 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 41 | # Learn more: 42 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 43 | 44 | steps: 45 | - name: Checkout repository 46 | uses: actions/checkout@v4 47 | 48 | - name: Set up Go 49 | uses: actions/setup-go@v5 50 | with: 51 | go-version: 1.18 52 | 53 | - uses: actions/cache@v4 54 | with: 55 | path: ~/go/pkg/mod 56 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 57 | restore-keys: | 58 | ${{ runner.os }}-go- 59 | 60 | # Initializes the CodeQL tools for scanning. 61 | - name: Initialize CodeQL 62 | uses: github/codeql-action/init@v1 63 | with: 64 | languages: ${{ matrix.language }} 65 | # If you wish to specify custom queries, you can do so here or in a config file. 66 | # By default, queries listed here will override any specified in a config file. 67 | # Prefix the list here with "+" to use these queries and those in the config file. 68 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 69 | 70 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 71 | # If this step fails, then you should remove it and run the build manually (see below) 72 | - name: Autobuild 73 | uses: github/codeql-action/autobuild@v1 74 | 75 | # ℹ️ Command-line programs to run using the OS shell. 76 | # 📚 https://git.io/JvXDl 77 | 78 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 79 | # and modify them (or add more) to build your code if your project 80 | # uses a compiled language 81 | 82 | #- run: | 83 | # make bootstrap 84 | # make release 85 | 86 | - name: Perform CodeQL Analysis 87 | uses: github/codeql-action/analyze@v1 88 | -------------------------------------------------------------------------------- /.github/workflows/push-check.yml: -------------------------------------------------------------------------------- 1 | name: Push Check 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | pull_request: 8 | paths-ignore: 9 | - '**.md' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: 1.18 21 | 22 | - name: Lint 23 | run: | 24 | test -z "$(gofmt -s -l .)" 25 | go vet -stdmethods=false $(go list ./...) 26 | 27 | - name: Unit Test 28 | run: go test -race ./... 29 | 30 | license-header-check: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - name: Check License Header 36 | uses: apache/skywalking-eyes/header@v0.4.0 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | # IDE or Editor's config 18 | .idea/ 19 | 20 | testdata 21 | -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | header: 2 | license: 3 | spdx-id: Apache-2.0 4 | copyright-owner: ByteDance Inc. 5 | 6 | paths: 7 | - '**/*.go' 8 | - '**/*.s' 9 | 10 | comment: on-failure -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Your First Pull Request 4 | We use github for our codebase. You can start by reading [How To Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests). 5 | 6 | ## Without Semantic Versioning 7 | We keep the stable code in branch `main` like `golang.org/x`. Development base on branch `develop`. And we promise the **Forward Compatibility** by adding new package directory with suffix `v2/v3` when code has break changes. 8 | 9 | ## Branch Organization 10 | We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) as our branch organization, as known as [FDD](https://en.wikipedia.org/wiki/Feature-driven_development) 11 | 12 | 13 | ## Bugs 14 | ### 1. How to Find Known Issues 15 | We are using [Github Issues](https://github.com/bytedance/gopkg/issues) for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist. 16 | 17 | ### 2. Reporting New Issues 18 | Providing a reduced test code is a recommended way for reporting issues. Then can placed in: 19 | - Just in issues 20 | - [Golang Playground](https://play.golang.org/) 21 | 22 | ### 3. Security Bugs 23 | Please do not report the safe disclosure of bugs to public issues. Contact us by [Support Email](mailto:gopkg@bytedance.com) 24 | 25 | ## How to Get in Touch 26 | - [Email](mailto:gopkg@bytedance.com) 27 | 28 | ## Submit a Pull Request 29 | Before you submit your Pull Request (PR) consider the following guidelines: 30 | 1. Search [GitHub](https://github.com/bytedance/gopkg/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate existing efforts. 31 | 2. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add. Discussing the design upfront helps to ensure that we're ready to accept your work. 32 | 3. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the bytedance/gopkg repo. 33 | 4. In your forked repository, make your changes in a new git branch: 34 | ``` 35 | git checkout -b bugfix/security_bug develop 36 | ``` 37 | 5. Create your patch, including appropriate test cases. 38 | 6. Follow our [Style Guides](#code-style-guides). 39 | 7. Commit your changes using a descriptive commit message that follows [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit). 40 | Adherence to these conventions is necessary because release notes are automatically generated from these messages. 41 | 8. Push your branch to GitHub: 42 | ``` 43 | git push origin bugfix/security_bug 44 | ``` 45 | 9. In GitHub, send a pull request to `gopkg:develop` 46 | 47 | Note: you must use one of `optimize/feature/bugfix/doc/ci/test/refactor` following a slash(`/`) as the branch prefix. 48 | 49 | Your pr title and commit message should follow https://www.conventionalcommits.org/. 50 | 51 | ## Contribution Prerequisites 52 | - Our development environment keeps up with [Go Official](https://golang.org/project/). 53 | - You need fully checking with lint tools before submit your pull request. [gofmt](https://golang.org/pkg/cmd/gofmt/) and [golangci-lint](https://github.com/golangci/golangci-lint) 54 | - You are familiar with [Github](https://github.com) 55 | - Maybe you need familiar with [Actions](https://github.com/features/actions)(our default workflow tool). 56 | 57 | ## Code Style Guides 58 | See [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments). 59 | 60 | Good resources: 61 | - [Effective Go](https://golang.org/doc/effective_go) 62 | - [Pingcap General advice](https://pingcap.github.io/style-guide/general.html) 63 | - [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md) 64 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | github.com/stretchr/testify 2 | golang.org/x/sync 3 | golang.org/x/sys 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gopkg 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/bytedance/gopkg.svg)](https://pkg.go.dev/github.com/bytedance/gopkg) 4 | 5 | `gopkg` is a universal utility collection for Go, it complements offerings such as Boost, Better std, Cloud tools. 6 | 7 | ## Table of Contents 8 | 9 | - [Introduction](#Introduction) 10 | - [Catalogs](#Catalogs) 11 | - [Releases](#Releases) 12 | - [How To Use](#How-To-Use) 13 | - [License](#License) 14 | 15 | ## Introduction 16 | 17 | `gopkg` is a universal utility collection for Go, it complements offerings such as Boost, Better std, Cloud tools. It is migrated from the internal code base at ByteDance and has been extensively adopted in production. 18 | 19 | We depend on the same code(this repo) in our production environment. 20 | 21 | ## Catalogs 22 | 23 | * [cache](https://github.com/bytedance/gopkg/tree/main/cache): Caching Mechanism 24 | * [cloud](https://github.com/bytedance/gopkg/tree/main/cloud): Cloud Computing Design Patterns 25 | * [collection](https://github.com/bytedance/gopkg/tree/main/collection): Data Structures 26 | * [lang](https://github.com/bytedance/gopkg/tree/main/lang): Enhanced Standard Libraries 27 | * [util](https://github.com/bytedance/gopkg/tree/main/util): Utilities Useful across Domains 28 | 29 | ## Releases 30 | 31 | `gopkg` recommends users to "live-at-head" (update to the latest commit from the main branch as often as possible). 32 | We develop at `develop` branch and will only merge to `main` when `develop` is stable. 33 | 34 | ## How To Use 35 | 36 | You can use `go get -u github.com/bytedance/gopkg@main` to get or update `gopkg`. 37 | 38 | ## License 39 | 40 | `gopkg` is licensed under the terms of the Apache license 2.0. See [LICENSE](LICENSE) for more information. 41 | -------------------------------------------------------------------------------- /cache/asynccache/README.md: -------------------------------------------------------------------------------- 1 | # asynccache 2 | 3 | ## Introduction 4 | 5 | `asynccache` fetches and updates the latest data periodically and supports expire a key if unused for a period. 6 | 7 | The functions it provides is listed below: 8 | ```go 9 | type AsyncCache interface { 10 | // SetDefault sets the default value of given key if it is new to the cache. 11 | // It is useful for cache warming up. 12 | // Param val should not be nil. 13 | SetDefault(key string, val interface{}) (exist bool) 14 | 15 | // Get tries to fetch a value corresponding to the given key from the cache. 16 | // If error occurs during the first time fetching, it will be cached until the 17 | // sequential fetching triggered by the refresh goroutine succeed. 18 | Get(key string) (val interface{}, err error) 19 | 20 | // GetOrSet tries to fetch a value corresponding to the given key from the cache. 21 | // If the key is not yet cached or error occurs, the default value will be set. 22 | GetOrSet(key string, defaultVal interface{}) (val interface{}) 23 | 24 | // Dump dumps all cache entries. 25 | // This will not cause expire to refresh. 26 | Dump() map[string]interface{} 27 | 28 | // DeleteIf deletes cached entries that match the `shouldDelete` predicate. 29 | DeleteIf(shouldDelete func(key string) bool) 30 | 31 | // Close closes the async cache. 32 | // This should be called when the cache is no longer needed, or may lead to resource leak. 33 | Close() 34 | } 35 | ``` 36 | 37 | ## Example 38 | 39 | ```go 40 | var key, ret = "key", "ret" 41 | opt := Options{ 42 | RefreshDuration: time.Second, 43 | IsSame: func(key string, oldData, newData interface{}) bool { 44 | return false 45 | }, 46 | Fetcher: func(key string) (interface{}, error) { 47 | return ret, nil 48 | }, 49 | } 50 | c := NewAsyncCache(opt) 51 | 52 | v, err := c.Get(key) 53 | assert.NoError(err) 54 | assert.Equal(v.(string), ret) 55 | 56 | time.Sleep(time.Second / 2) 57 | ret = "change" 58 | v, err = c.Get(key) 59 | assert.NoError(err) 60 | assert.NotEqual(v.(string), ret) 61 | 62 | time.Sleep(time.Second) 63 | v, err = c.Get(key) 64 | assert.NoError(err) 65 | assert.Equal(v.(string), ret) 66 | ``` 67 | -------------------------------------------------------------------------------- /cache/asynccache/atomic_error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Copyright (c) 2016 Uber Technologies, Inc. 16 | // 17 | // Permission is hereby granted, free of charge, to any person obtaining a copy 18 | // of this software and associated documentation files (the "Software"), to deal 19 | // in the Software without restriction, including without limitation the rights 20 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | // copies of the Software, and to permit persons to whom the Software is 22 | // furnished to do so, subject to the following conditions: 23 | // 24 | // The above copyright notice and this permission notice shall be included in 25 | // all copies or substantial portions of the Software. 26 | // 27 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 33 | // THE SOFTWARE. 34 | 35 | package asynccache 36 | 37 | import "sync/atomic" 38 | 39 | // Error is an atomic type-safe wrapper around Value for errors 40 | type Error struct{ v atomic.Value } 41 | 42 | // errorHolder is non-nil holder for error object. 43 | // atomic.Value panics on saving nil object, so err object needs to be 44 | // wrapped with valid object first. 45 | type errorHolder struct{ err error } 46 | 47 | // Load atomically loads the wrapped error 48 | func (e *Error) Load() error { 49 | v := e.v.Load() 50 | if v == nil { 51 | return nil 52 | } 53 | 54 | eh := v.(errorHolder) 55 | return eh.err 56 | } 57 | 58 | // Store atomically stores error. 59 | // NOTE: a holder object is allocated on each Store call. 60 | func (e *Error) Store(err error) { 61 | e.v.Store(errorHolder{err: err}) 62 | } 63 | -------------------------------------------------------------------------------- /check_branch_name.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | basic="(master|develop|main)" 4 | lints="((build|ci|chore|docs|feat|(hot)?fix|perf|refactor|revert|style|test)/.+)" 5 | other="((release/v[0-9]+\.[0-9]+\.[0-9]+(-[a-z0-9.]+(\+[a-z0-9.]+)?)?)|revert-[a-z0-9]+)" 6 | pattern="^(${basic}|${lints}|${other})$" 7 | 8 | current=$(git status | head -n1 | sed 's/On branch //') 9 | name=${1:-$current} 10 | if [[ ! $name =~ $pattern ]]; then 11 | echo "branch name '$name' is invalid" 12 | exit 1 13 | else 14 | echo "branch name '$name' is valid" 15 | fi 16 | -------------------------------------------------------------------------------- /cloud/circuitbreaker/README_ZH.MD: -------------------------------------------------------------------------------- 1 | # Circuitbreaker 2 | 3 | ## 熔断器简单介绍 4 | ### 熔断器作用 5 | 在进行RPC调用时, 下游服务难免会出错; 6 | 7 | 当下游出现问题时, 如果上游继续对其进行调用, 既妨碍了下游的恢复, 也浪费了上游的资源; 8 | 9 | 为了解决这个问题, 你可以设置一些动态开关, 当下游出错时, 手动的关闭对下游的调用; 10 | 11 | 然而更好的办法是使用熔断器, 自动化的解决这个问题. 12 | 13 | 这里是一篇更详细的[熔断器介绍](https://msdn.microsoft.com/zh-cn/library/dn589784.aspx). 14 | 15 | 比较出名的熔断器当属hystrix了, 这里是他的[设计文档](https://github.com/Netflix/Hystrix/wiki). 16 | 17 | ### 熔断思路 18 | **熔断器的思路很简单: 根据RPC的成功失败情况, 限制对下游的访问;** 19 | 20 | 通常熔断器分为三个时期: CLOSED, OPEN, HALFOPEN; 21 | 22 | RPC正常时, 为CLOSED; 23 | 24 | 当RPC错误增多时, 熔断器会被触发, 进入OPEN; 25 | 26 | OPEN后经过一定的冷却时间, 熔断器变为HALFOPEN; 27 | 28 | HALFOPEN时会对下游进行一些有策略的访问, 然后根据结果决定是变为CLOSED, 还是OPEN; 29 | 30 | 总得来说三个状态的转换大致如下图: 31 | 32 |
 33 |  [CLOSED] -->- tripped ----> [OPEN]<-------+
 34 |     ^                          |           ^
 35 |     |                          v           |
 36 |     +                          |      detect fail
 37 |     |                          |           |
 38 |     |                    cooling timeout   |
 39 |     ^                          |           ^
 40 |     |                          v           |
 41 |     +--- detect succeed --<-[HALFOPEN]-->--+
 42 | 
43 | 44 | ## 该包的使用 45 | 46 | ### 基本使用 47 | 该包将RPC的各个情况分为三类: Succeed, Fail, Timeout, 并维护了一定时间窗口内三者的计数; 48 | 49 | 在每次RPC前, 你应该调用IsAllowed()来决定是否发起RPC; 50 | 51 | 并在调用完成后根据结果, 调用Succeed(), Fail(), Timeout()来反馈; 52 | 53 | 同时该包还进行了并发数控制, 你在每次RPC完成后, 还必须调用Done(); 54 | 55 | 下面是一段例子: 56 |
 57 | var p *Panel
 58 | 
 59 | func init() {
 60 |     var err error
 61 |     p, err = NewPanel(nil, Options{
 62 |     	CoolingTimeout: time.Minute,
 63 |     	DetectTimeout:  time.Minute,
 64 |     	ShouldTrip:     ThresholdTripFunc(100),
 65 |     })
 66 |     if err != nil {
 67 |     	panic(err)
 68 |     }
 69 | }
 70 | 
 71 | func DoRPC() error {
 72 |     key := "remote::rpc::method"
 73 |     if p.IsAllowed(key) == false {
 74 |         return Err("Not allowed by circuitbreaker")
 75 |     }
 76 | 
 77 |     err := doRPC()
 78 |     if err == nil {
 79 |         p.Succeed(key)
 80 |     } else if IsFailErr(err) {
 81 |         p.Fail(key)
 82 |     } else if IsTimeout(err) {
 83 |         p.Timeout(key)
 84 |     }
 85 |     return err
 86 | }
 87 | 
 88 | func main() {
 89 |     ...
 90 |     for ... {
 91 |         DoRPC()
 92 |     }
 93 |     p.Close()
 94 | }
 95 | 
96 | 97 | ### 熔断触发策略 98 | 该包提供了三个基本的熔断触发策略: 99 | + 连续错误数达到阈值(ConsecutiveTripFunc) 100 | + 错误数达到阈值(ThresholdTripFunc) 101 | + 错误率达到阈值(RateTripFunc) 102 | 103 | 当然, 你可以通过实现TripFunc函数来写自己的熔断触发策略; 104 | 105 | Circuitbreaker会在每次Fail或者Timeout时, 去调用TripFunc, 来决定是否触发熔断; 106 | 107 | ### 熔断冷却策略 108 | 进入OPEN状态后, 熔断器会冷却一段时间, 默认是10秒, 当然该参数可配置(CoolingTimeout); 109 | 110 | 在这段时期内, 所有的IsAllowed()请求将会被返回false; 111 | 112 | 冷却完毕后进入HALFOPEN; 113 | 114 | ### 半打开时策略 115 | 在HALFOPEN时, 熔断器每隔"一段时间"便会放过一个请求, 当连续成功"若干数目"的请求后, 熔断器将变为CLOSED; 如果其中有任意一个失败, 则将变为OPEN; 116 | 117 | 该过程是一个逐渐试探下游, 并打开的过程; 118 | 119 | 上述的"一段时间"(DetectTimeout)和"若干数目"(DEFAULT_HALFOPEN_SUCCESSES)都是可以配置的; 120 | 121 | ### 并发控制 122 | 该熔断还进行了并发控制, 参数为MaxConcurrency; 123 | 124 | 当并发数达到上限时, IsAllowed将会返回false; 125 | 126 | ### 统计 127 | ##### 默认参数 128 | 熔断器会统计一段时间窗口内的成功, 失败和超时, 默认窗口大小是10S; 129 | 130 | 时间窗口可以通过两个参数设置, 不过通常情况下你可以不用关心. 131 | 132 | ##### 统计方法 133 | 统计方法是将该段时间窗口分为若干个桶, 每个桶记录一定固定时长内的数据; 134 | 135 | 比如统计10秒内的数据, 于是可以将10秒的时间段分散到100个桶, 每个桶统计100ms时间段内的数据; 136 | 137 | Options中的BucketTime和BucketNums, 就分别对应了每个桶维护的时间段, 和桶的个数; 138 | 139 | 如将BucketTime设置为100ms, 将BucketNums设置为100, 则对应了10秒的时间窗口; 140 | 141 | ##### 抖动 142 | 随着时间的移动, 窗口内最老的那个桶会过期, 当最后那个桶过期时, 则会出现了抖动; 143 | 144 | 举个例子: 145 | + 你将10秒分为了10个桶, 0号桶对应了[0S, 1S)的时间, 1号桶对应[1S, 2S), ..., 9号桶对应[9S, 10S); 146 | + 在10.1S时, 执行一次Succ, 则circuitbreaker内会发生下述的操作; 147 | + (1)检测到0号桶已经过期, 将其丢弃; (2)创建新的10号桶, 对应[10S, 11S); (3)将该次Succ放入10号桶内; 148 | + 在10.2S时, 你执行Successes()查询窗口内成功数, 则你得到的实际统计值是[1S, 10.2S)的数据, 而不是[0.2S, 10.2S); 149 | 150 | 如果使用分桶计数的办法, 这样的抖动是无法避免的, 比较折中的一个办法是将桶的个数增多, 可以降低抖动的影响; 151 | 152 | 如划分2000个桶, 则抖动对整体的数据的影响最多也就1/2000; 153 | 154 | 在该包中, 默认的桶个数也是100, 桶时间为100ms, 总体窗口为10S; 155 | 156 | 当时曾想过多种技术办法来避免这种问题, 但是都会引入更多其他的问题, 如果你有好的思路, 请issue或者MR. -------------------------------------------------------------------------------- /cloud/circuitbreaker/counter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package circuitbreaker 16 | 17 | import ( 18 | "runtime" 19 | "sync/atomic" 20 | 21 | "github.com/bytedance/gopkg/internal/runtimex" 22 | ) 23 | 24 | type Counter interface { 25 | Add(i int64) 26 | Get() int64 27 | Zero() 28 | } 29 | 30 | type atomicCounter struct { 31 | x int64 32 | } 33 | 34 | func (c *atomicCounter) Add(i int64) { 35 | atomic.AddInt64(&c.x, i) 36 | } 37 | 38 | func (c *atomicCounter) Get() int64 { 39 | return atomic.LoadInt64(&c.x) 40 | } 41 | 42 | func (c *atomicCounter) Zero() { 43 | atomic.StoreInt64(&c.x, 0) 44 | } 45 | 46 | const ( 47 | cacheLineSize = 64 48 | ) 49 | 50 | var ( 51 | countersLen int 52 | ) 53 | 54 | func init() { 55 | countersLen = runtime.GOMAXPROCS(0) 56 | } 57 | 58 | type counterShard struct { 59 | x int64 60 | _ [cacheLineSize - 8]byte 61 | } 62 | 63 | type perPCounter []counterShard 64 | 65 | func newPerPCounter() perPCounter { 66 | return make([]counterShard, countersLen) 67 | } 68 | 69 | func (c perPCounter) Add(i int64) { 70 | tid := runtimex.Pid() 71 | atomic.AddInt64(&c[tid%countersLen].x, i) 72 | } 73 | 74 | // Get is not precise, but it's ok in this scenario. 75 | func (c perPCounter) Get() int64 { 76 | var n int64 77 | for i := range c { 78 | n += atomic.LoadInt64(&c[i].x) 79 | } 80 | return n 81 | } 82 | 83 | func (c perPCounter) Zero() { 84 | for i := range c { 85 | atomic.StoreInt64(&c[i].x, 0) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /cloud/circuitbreaker/counter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package circuitbreaker 16 | 17 | import ( 18 | "sync" 19 | "testing" 20 | ) 21 | 22 | func BenchmarkAtomicCounter_Add(b *testing.B) { 23 | c := atomicCounter{} 24 | b.SetParallelism(1000) 25 | b.RunParallel(func(p *testing.PB) { 26 | for p.Next() { 27 | c.Add(1) 28 | } 29 | }) 30 | } 31 | 32 | func BenchmarkPerPCounter_Add(b *testing.B) { 33 | c := newPerPCounter() 34 | b.SetParallelism(1000) 35 | b.RunParallel(func(p *testing.PB) { 36 | for p.Next() { 37 | c.Add(1) 38 | } 39 | }) 40 | } 41 | 42 | func TestPerPCounter(t *testing.T) { 43 | numPerG := 1000 44 | numG := 1000 45 | c := newPerPCounter() 46 | c1 := atomicCounter{} 47 | var wg sync.WaitGroup 48 | wg.Add(numG) 49 | for i := 0; i < numG; i++ { 50 | go func() { 51 | defer wg.Done() 52 | for i := 0; i < numPerG; i++ { 53 | c.Add(1) 54 | c1.Add(1) 55 | } 56 | }() 57 | } 58 | wg.Wait() 59 | total := c.Get() 60 | total1 := c1.Get() 61 | if total != c1.Get() { 62 | t.Errorf("expected %d, get %d", total1, total) 63 | } 64 | c.Zero() 65 | c1.Zero() 66 | if c.Get() != 0 || c1.Get() != 0 { 67 | t.Errorf("zero failed") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /cloud/circuitbreaker/interface.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package circuitbreaker 16 | 17 | import "time" 18 | 19 | // Panel is what users should use. 20 | type Panel interface { 21 | Succeed(key string) 22 | Fail(key string) 23 | FailWithTrip(key string, f TripFunc) 24 | Timeout(key string) 25 | TimeoutWithTrip(key string, f TripFunc) 26 | IsAllowed(key string) bool 27 | RemoveBreaker(key string) 28 | DumpBreakers() map[string]Breaker 29 | // Close should be called if Panel is not used anymore. Or may lead to resource leak. 30 | // If Panel is used after Close is called, behavior is undefined. 31 | Close() 32 | GetMetricer(key string) Metricer 33 | } 34 | 35 | // Breaker is the base of a circuit breaker. 36 | type Breaker interface { 37 | Succeed() 38 | Fail() 39 | FailWithTrip(TripFunc) 40 | Timeout() 41 | TimeoutWithTrip(TripFunc) 42 | IsAllowed() bool 43 | State() State 44 | Metricer() Metricer 45 | Reset() 46 | } 47 | 48 | // Metricer metrics errors, timeouts and successes 49 | type Metricer interface { 50 | Failures() int64 // return the number of failures 51 | Successes() int64 // return the number of successes 52 | Timeouts() int64 // return the number of timeouts 53 | ConseErrors() int64 // return the consecutive errors recently 54 | ConseTime() time.Duration // return the consecutive error time 55 | ErrorRate() float64 // rate = (timeouts + failures) / (timeouts + failures + successes) 56 | Samples() int64 // (timeouts + failures + successes) 57 | Counts() (successes, failures, timeouts int64) 58 | } 59 | 60 | // mutable Metricer 61 | type metricer interface { 62 | Metricer 63 | 64 | Fail() // records a failure 65 | Succeed() // records a success 66 | Timeout() // records a timeout 67 | 68 | Reset() 69 | tick() 70 | } 71 | -------------------------------------------------------------------------------- /cloud/circuitbreaker/test_helper.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package circuitbreaker 16 | 17 | import "reflect" 18 | 19 | // testingTB is a subset of common methods between *testing.T and *testing.B. 20 | type testingTB interface { 21 | Fatal(args ...interface{}) 22 | Fatalf(format string, args ...interface{}) 23 | Helper() 24 | } 25 | 26 | // assert . 27 | func assert(t testingTB, cond bool) { 28 | t.Helper() 29 | if !cond { 30 | t.Fatal("assertion failed") 31 | } 32 | } 33 | 34 | // Assertf . 35 | func Assertf(t testingTB, cond bool, format string, val ...interface{}) { 36 | t.Helper() 37 | if !cond { 38 | t.Fatalf(format, val...) 39 | } 40 | } 41 | 42 | // deepEqual . 43 | func deepEqual(t testingTB, a, b interface{}) { 44 | t.Helper() 45 | if !reflect.DeepEqual(a, b) { 46 | t.Fatalf("assertion failed: %v != %v", a, b) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cloud/circuitbreaker/tripfunc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package circuitbreaker 16 | 17 | import "time" 18 | 19 | // TripFunc is a function called by a breaker when error appear and 20 | // determines whether the breaker should trip. 21 | type TripFunc func(Metricer) bool 22 | 23 | // TripFuncWithKey returns a TripFunc according to the key. 24 | type TripFuncWithKey func(string) TripFunc 25 | 26 | // ThresholdTripFunc . 27 | func ThresholdTripFunc(threshold int64) TripFunc { 28 | return func(m Metricer) bool { 29 | return m.Failures()+m.Timeouts() >= threshold 30 | } 31 | } 32 | 33 | // ConsecutiveTripFunc . 34 | func ConsecutiveTripFunc(threshold int64) TripFunc { 35 | return func(m Metricer) bool { 36 | return m.ConseErrors() >= threshold 37 | } 38 | } 39 | 40 | // RateTripFunc . 41 | func RateTripFunc(rate float64, minSamples int64) TripFunc { 42 | return func(m Metricer) bool { 43 | samples := m.Samples() 44 | return samples >= minSamples && m.ErrorRate() >= rate 45 | } 46 | } 47 | 48 | // ConsecutiveTripFuncV2 uses the following three strategies based on the parameters passed in. 49 | // 1. when the number of samples >= minSamples and the error rate >= rate 50 | // 2. when the number of samples >= durationSamples and the length of consecutive errors >= duration 51 | // 3. When the number of consecutive errors >= conseErrors 52 | // The fuse is opened when any of the above three strategies holds. 53 | func ConsecutiveTripFuncV2(rate float64, minSamples int64, duration time.Duration, durationSamples, conseErrors int64) TripFunc { 54 | return func(m Metricer) bool { 55 | samples := m.Samples() 56 | // based on stat 57 | if samples >= minSamples && m.ErrorRate() >= rate { 58 | return true 59 | } 60 | // based on continuous time 61 | if duration > 0 && m.ConseErrors() >= durationSamples && m.ConseTime() >= duration { 62 | return true 63 | } 64 | // base on consecutive errors 65 | if conseErrors > 0 && m.ConseErrors() >= conseErrors { 66 | return true 67 | } 68 | return false 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /cloud/metainfo/README-CN.md: -------------------------------------------------------------------------------- 1 | 2 | metainfo 3 | ======== 4 | 5 | 该库提供了一种在 go 语言的 `context.Context` 中保存用于跨服务传递的元信息的统一接口。 6 | 7 | **元信息**被设计为字符串的键值对,并且键是**大小写敏感的**。 8 | 9 | 根据数据传递的场景,元信息被分为两种类别,*transient* 和 *persistent* —— 前者只会传递一跳,从客户端传递到下游服务端,然后消失;后者需要在整个服务调用链上一直传递,直到被丢弃。 10 | 11 | 由于传递过程使用了 go 语言的 `context.Context`,因此,为了避免服务端的 `Context` 实例直接传递给用于调用下游服务的客户端时造成从更上游服务传递来的 transient 数据被继续透传,需要引入一个中间态:*transient-upstream*,以和当前服务自己设置的 transient 数据作区分。该类别仅用于实现 transient 的语义,在接口上,transient 和 transient-upstream 并没有区别。 12 | 13 | 该库被设计成针对 `context.Context` 进行操作的接口集合,而具体的元数据在网络上传输的形式和方式,应由支持该库的框架来实现。通常,终端用户不应该关注其具体传输形式,而应该仅依赖该库提供的抽象接口。 14 | 15 | 框架支持指南 16 | ------------ 17 | 18 | 如果要在某个框架里引入 metainfo 并对框架的用户提供支持,需要满足如下的条件: 19 | 20 | 1. 框架使用的传输协议应该支持元信息的传递(例如 HTTP header、thrift 的 header transport 等)。 21 | 2. 当框架作为服务端接收到元信息后,需要将元信息添加到 `context.Context` 对象里。随后,在进入用户的代码逻辑(和其他可能要使用元信息的代码)之前,需要用 `metainfo.TransferForward` 转化客户端传递过来的 transient 信息为 transient-upstream。 22 | 3. 在框架作为客户端发起对服务端的请求,需要将元信息传递出去之前,需要调用一次 `metainfo.TransferForward` 将 `context.Context` 里的 transient-upstream 信息丢弃掉,使得 transient 信息符合“只传递一跳”的语义。 23 | 24 | API 参考 25 | ------- 26 | 27 | **注意** 28 | 29 | 1. 出于兼容性和普适性,元信息的形式为字符串的 key value 对。 30 | 2. 空串作为 key 或者 value 都是无效的。 31 | 3. 由于 context 的特性,程序对 metainfo 的增删改只会对拥有相同的 context 或者其子 context 的代码可见。 32 | 33 | **常量** 34 | 35 | metainfo 包提供了几个常量字符串前缀,用于无 context(例如网络传输)的场景下标记元信息的类型。 36 | 37 | 典型的业务代码通常不需要用到这些前缀。支持 metainfo 的框架也可以自行选择在传输时用于区分元信息类别的方式。 38 | 39 | - `PrefixPersistent` 40 | - `PrefixTransient` 41 | - `PrefixTransientUpstream` 42 | 43 | **方法** 44 | 45 | - `TransferForward(ctx context.Context) context.Context` 46 | - 向前传递,用于将上游传来的 transient 数据转化为 transient-upstream 数据,并过滤掉原有的 transient-upstream 数据。 47 | - `GetValue(ctx context.Context, k string) (string, bool)` 48 | - 从 context 里获取指定 key 的 transient 数据(包括 transient-upstream 数据)。 49 | - `GetAllValues(ctx context.Context) map[string]string` 50 | - 从 context 里获取所有 transient 数据(包括 transient-upstream 数据)。 51 | - `RangeValues(ctx context.Context, f func(k, v string) bool)` 52 | - 从 context 里基于 f 过滤获取 transient 数据(包括 transient-upstream 数据)。 53 | - `WithValue(ctx context.Context, k string, v string) context.Context` 54 | - 向 context 里添加一个 transient 数据。 55 | - `DelValue(ctx context.Context, k string) context.Context` 56 | - 从 context 里删除指定的 transient 数据。 57 | - `GetPersistentValue(ctx context.Context, k string) (string, bool)` 58 | - 从 context 里获取指定 key 的 persistent 数据。 59 | - `GetAllPersistentValues(ctx context.Context) map[string]string` 60 | - 从 context 里获取所有 persistent 数据。 61 | - `RangePersistentValues(ctx context.Context, f func(k, v string) bool)` 62 | - 从 context 里基于 f 过滤获取 persistent 数据。 63 | - `WithPersistentValue(ctx context.Context, k string, v string) context.Context` 64 | - 向 context 里添加一个 persistent 数据。 65 | - `DelPersistentValue(ctx context.Context, k string) context.Context` 66 | - 从 context 里删除指定的 persistent 数据。 67 | 68 | -------------------------------------------------------------------------------- /cloud/metainfo/README.md: -------------------------------------------------------------------------------- 1 | 2 | metainfo 3 | ======== 4 | 5 | This library provides a set of methods to store and manipulate meta information in golang's `context.Context` for inter-service data transmission. 6 | 7 | **Meta information** is designed as key-value pairs of strings and keys are case-sensitive. 8 | 9 | Meta information is divided into two kinds: **transient** and **persistent** -- the former is passed from a client to a server and disappears from the network while the latter should be passed to the other servers (if the current server plays a client role, too) and go on until some server discards it. 10 | 11 | Because we uses golang's `context.Context` to carry meta information, we need to distinguish the *transient* information comes from a client and those set by the current service since they will be stored in the same context object. So we introduce a third kind of meta information **transient-upstream** to denote the transient information received from a client. The **transient-upstream** kind is designed to achieve the semantic of *transient* meta information and is indistinguishable from the transient kind with the APIs from a user perspective. 12 | 13 | This library is designed as a set of methods operating on `context.Context`. The way data is transmitted between services should be defined and implemented by frameworks that support this library. Usually, end users should not be concerned about this but rely on the APIs only. 14 | 15 | Framework Support Guide 16 | ----------------------- 17 | 18 | To introduce **metainfo** into a certain framework, to following conditions should be satisfied: 19 | 20 | 1. Protocol used by the framework for data transmission should support meta information (HTTP headers, header transport of the apache thrift framework, and so on). 21 | 2. When the framework receives meta information as a server role, it needs to add those information to the `context.Context` (by using APIs in this library). Then, before going into the codes of end user (or other codes that requires meta information), the framework should use `metainfo.TransferForward` to convert transient information from the client into transient-upstream information. 22 | 3. When the framework establishes a request towards a server as a client role, before sending out the meta information, it should call `metainfo.TransferForward` to discard transient-upstream information in the `context.Context` to make them "alive only one hop". 23 | 24 | API Reference 25 | ------------- 26 | 27 | **Notice** 28 | 29 | 1. Meta information is designed as key-value pairs as strings. 30 | 2. Empty string is invalid when used as a key or value. 31 | 3. Due to the characteristics of `context.Context`, any modification on a context object is only visible to the codes that holds the context or its sub contexts. 32 | 33 | **Constants** 34 | 35 | Package metainfo provides serveral string prefixes to denote the kinds of meta information where context is not available (such as when transmiting data through network). 36 | 37 | Typical codes for business logic should never use prefixes. And frameworks that support metainfo may choose other approaches to achieve the same goal. 38 | 39 | - `PrefixPersistent` 40 | - `PrefixTransient` 41 | - `PrefixTransientUpstream` 42 | 43 | -------------------------------------------------------------------------------- /cloud/metainfo/helper_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo_test 16 | 17 | type tb interface { 18 | Fatal(args ...interface{}) 19 | Fatalf(format string, args ...interface{}) 20 | Helper() 21 | } 22 | 23 | func assert(t tb, cond bool, val ...interface{}) { 24 | t.Helper() 25 | if !cond { 26 | if len(val) > 0 { 27 | val = append([]interface{}{"assertion failed:"}, val...) 28 | t.Fatal(val...) 29 | } else { 30 | t.Fatal("assertion failed") 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cloud/metainfo/kvstore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import "sync" 18 | 19 | type kvstore map[string]string 20 | 21 | var kvpool sync.Pool 22 | 23 | func newKVStore(size ...int) kvstore { 24 | kvs := kvpool.Get() 25 | if kvs == nil { 26 | if len(size) > 0 { 27 | return make(kvstore, size[0]) 28 | } 29 | return make(kvstore) 30 | } 31 | return kvs.(kvstore) 32 | } 33 | 34 | func (store kvstore) size() int { 35 | return len(store) 36 | } 37 | 38 | func (store kvstore) recycle() { 39 | /* 40 | for k := range m { 41 | delete(m, k) 42 | } 43 | ==> 44 | LEAQ type.map[string]int(SB), AX 45 | MOVQ AX, (SP) 46 | MOVQ "".m(SB), AX 47 | MOVQ AX, 8(SP) 48 | PCDATA $1, $0 49 | CALL runtime.mapclear(SB) 50 | */ 51 | for key := range store { 52 | delete(store, key) 53 | } 54 | kvpool.Put(store) 55 | } 56 | -------------------------------------------------------------------------------- /cloud/metainfo/kvstore_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | ) 21 | 22 | func TestKVStore(t *testing.T) { 23 | store := newKVStore() 24 | store["a"] = "a" 25 | store["a"] = "b" 26 | if store["a"] != "b" { 27 | t.Fatal() 28 | } 29 | store.recycle() 30 | if store["a"] == "b" { 31 | t.Fatal() 32 | } 33 | store = newKVStore() 34 | if store["a"] == "b" { 35 | t.Fatal() 36 | } 37 | } 38 | 39 | func BenchmarkMap(b *testing.B) { 40 | for keys := 1; keys <= 1000; keys *= 10 { 41 | b.Run(fmt.Sprintf("keys=%d", keys), func(b *testing.B) { 42 | b.ReportAllocs() 43 | for i := 0; i < b.N; i++ { 44 | m := make(map[string]string) 45 | for idx := 0; idx < 1000; idx++ { 46 | m[fmt.Sprintf("key-%d", idx)] = string('a' + byte(idx%26)) 47 | } 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func BenchmarkKVStore(b *testing.B) { 54 | for keys := 1; keys <= 1000; keys *= 10 { 55 | b.Run(fmt.Sprintf("keys=%d", keys), func(b *testing.B) { 56 | b.ReportAllocs() 57 | for i := 0; i < b.N; i++ { 58 | m := newKVStore() 59 | for idx := 0; idx < 1000; idx++ { 60 | m[fmt.Sprintf("key-%d", idx)] = string('a' + byte(idx%26)) 61 | } 62 | m.recycle() 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /cloud/metainfo/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import "sync" 18 | 19 | var tmpnodePool = sync.Pool{} 20 | 21 | type tmpnode struct { 22 | persistent []kv 23 | transient []kv 24 | } 25 | 26 | const ( 27 | // tmpnodeDefaultBufferSize is the default cap size of slice used by tmpnode 28 | tmpnodeDefaultBufferSize = 32 29 | 30 | // tmpnodeCopyThresholdSize is the threshold of copying data from `tmpnode` to `node` 31 | // 32 | // if len(persistent) or len(transient) > the value, the new `node` will borrow refs from `tmpnode` 33 | // see `Node` method for details. 34 | tmpnodeCopyThresholdSize = 0.75 * tmpnodeDefaultBufferSize 35 | ) 36 | 37 | // Node copies data from persistent or transient, and returns a new `*node` for context 38 | func (n *tmpnode) Node() *node { 39 | ret := &node{} 40 | 41 | // in case we only have a few kvs, copy cost is low 42 | if sz := n.Size(); sz <= 2*tmpnodeCopyThresholdSize { 43 | kvs := make([]kv, n.Size()) // alloc one, and used by persistent & transient 44 | sz = copy(kvs, n.persistent) 45 | ret.persistent = kvs[:sz:sz] 46 | kvs = kvs[sz:] 47 | sz = copy(kvs, n.transient) 48 | ret.transient = kvs 49 | return ret 50 | } 51 | 52 | // if size of the slice has items more than `tmpnodeCopyThresholdSize` 53 | // considering reusing the slice instead of copying data. 54 | if sz := len(n.persistent); sz > tmpnodeCopyThresholdSize { 55 | ret.persistent = n.persistent 56 | n.persistent = nil 57 | } else if sz > 0 { 58 | ret.persistent = append(make([]kv, 0, sz), n.persistent...) 59 | } 60 | 61 | if sz := len(n.transient); sz > tmpnodeCopyThresholdSize { 62 | ret.transient = n.transient 63 | n.transient = nil 64 | } else if sz > 0 { 65 | ret.transient = append(make([]kv, 0, sz), n.transient...) 66 | } 67 | return ret 68 | } 69 | 70 | func (n *tmpnode) Size() int { 71 | return len(n.persistent) + len(n.transient) 72 | } 73 | 74 | func (n *tmpnode) Reset() { 75 | if n.persistent == nil && n.transient == nil { 76 | // in case both nil, 77 | // alloc one slice and used by persistent & transient 78 | // one less allocation 79 | kvs := make([]kv, 2*tmpnodeDefaultBufferSize) 80 | n.persistent = kvs[:tmpnodeDefaultBufferSize][:0:tmpnodeDefaultBufferSize] 81 | n.transient = kvs[tmpnodeDefaultBufferSize:][:0:tmpnodeDefaultBufferSize] 82 | return 83 | } 84 | 85 | if n.persistent == nil { // set to nil in Node() if it's large 86 | n.persistent = make([]kv, 0, tmpnodeDefaultBufferSize) 87 | } else { 88 | n.persistent = n.persistent[:0] 89 | } 90 | if n.transient == nil { // set to nil in Node() if it's large 91 | n.transient = make([]kv, 0, tmpnodeDefaultBufferSize) 92 | } else { 93 | n.transient = n.transient[:0] 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /cloud/metainfo/pool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package metainfo 16 | 17 | import "testing" 18 | 19 | func assert(t *testing.T, cond bool, val ...interface{}) { 20 | t.Helper() 21 | if !cond { 22 | if len(val) > 0 { 23 | t.Fatal(val...) 24 | } else { 25 | t.Fatal("assertion failed") 26 | } 27 | } 28 | } 29 | 30 | func TestTmpNode(t *testing.T) { 31 | n := tmpnode{} 32 | 33 | // Test Reset, case both nil 34 | n.persistent, n.transient = nil, nil 35 | n.Reset() 36 | assert(t, len(n.persistent) == 0) 37 | assert(t, cap(n.persistent) == tmpnodeDefaultBufferSize) 38 | assert(t, len(n.transient) == 0) 39 | assert(t, cap(n.transient) == tmpnodeDefaultBufferSize) 40 | 41 | // Test Reset, n.persistent = nil 42 | n.persistent = nil 43 | n.transient = make([]kv, 7, 7) 44 | n.Reset() 45 | assert(t, len(n.persistent) == 0) 46 | assert(t, cap(n.persistent) == tmpnodeDefaultBufferSize) 47 | assert(t, len(n.transient) == 0) 48 | assert(t, cap(n.transient) == 7) 49 | 50 | // Test Reset, n.transient = nil 51 | n.persistent = make([]kv, 7, 7) 52 | n.transient = nil 53 | n.Reset() 54 | assert(t, len(n.persistent) == 0) 55 | assert(t, cap(n.persistent) == 7) 56 | assert(t, len(n.transient) == 0) 57 | assert(t, cap(n.transient) == tmpnodeDefaultBufferSize) 58 | 59 | // Test Node(), <= 2*tmpnodeCopyThresholdSize 60 | n.persistent = []kv{{"k1", "v1"}} 61 | n.transient = []kv{{"k2", "v2"}} 62 | newn := n.Node() 63 | assert(t, len(n.persistent) == 1) 64 | assert(t, cap(n.persistent) == 1) 65 | assert(t, len(n.transient) == 1) 66 | assert(t, cap(n.transient) == 1) 67 | assert(t, n.persistent[0] == newn.persistent[0]) 68 | assert(t, n.transient[0] == newn.transient[0]) 69 | 70 | // Test Node(), > tmpnodeCopyThresholdSize 71 | sz := int(2 * tmpnodeCopyThresholdSize) 72 | n.persistent = make([]kv, sz, sz) 73 | n.persistent[0] = kv{"k3", "v3"} 74 | n.transient = []kv{{"k4", "v4"}} 75 | newn = n.Node() 76 | assert(t, n.persistent == nil) 77 | assert(t, len(n.transient) == 1) 78 | assert(t, len(newn.persistent) == sz) 79 | assert(t, newn.persistent[0] == kv{"k3", "v3"}) 80 | assert(t, len(newn.transient) == 1) 81 | assert(t, n.transient[0] == newn.transient[0]) 82 | 83 | n.persistent = []kv{{"k3", "v3"}} 84 | n.transient = make([]kv, sz, sz) 85 | n.transient[0] = kv{"k4", "v4"} 86 | newn = n.Node() 87 | assert(t, len(n.persistent) == 1) 88 | assert(t, n.transient == nil) 89 | assert(t, len(newn.persistent) == 1) 90 | assert(t, newn.persistent[0] == n.persistent[0]) 91 | assert(t, len(newn.transient) == sz) 92 | assert(t, newn.transient[0] == kv{"k4", "v4"}) 93 | 94 | } 95 | -------------------------------------------------------------------------------- /collection/hashset/README.md: -------------------------------------------------------------------------------- 1 | # hashset 2 | 3 | ## Introduction 4 | In this repository, we implemented one foundational data structure: Set based on Map in golang. We have: 5 | `Add(value int64)`: Adds the specified element to this set. 6 | `Contains(value int64) bool`: Returns true if this set contains the specified element. 7 | `Remove(value int64)`: Removes the specified element from this set. 8 | `Range(f func(value int64) bool)`: Function f executes by taking element in the set as parameter sequentially until f returns false 9 | `Len() int`: Returns the number of elements of this set. 10 | 11 | We made two experiments in order to measure the overall performance of the new hashset: 12 | 1. the chosen value's type: empty struct vs. bool 13 | 2. the impact of checking the existence of the key before add/remove an item 14 | 15 | ## Features 16 | - The API of hashset is totally compatible with skipset [link](https://github.com/zhangyunhao116/skipset/) 17 | - Usually, developers implement the set in golang by setting the value of pair to `bool` or `int`. However, We proved that using empty struct is more space efficiency and slightly time efficiency. 18 | 19 | 20 | ## When to use hashset 21 | Hashset **doesnt** guarantee concurrent safe. If you do need a concurrent safe set, go for skipset [link] -> https://github.com/bytedance/gopkg/tree/develop/collection/skipset 22 | 23 | ## Quickstart 24 | ```go 25 | package main 26 | 27 | import ( 28 | "fmt" 29 | "github.com/bytedance/gopkg/collection/hashset" 30 | ) 31 | 32 | func main() { 33 | l := hashset.NewInt() 34 | 35 | for _, v := range []int{10, 12, 15} { 36 | if l.Add(v) { 37 | fmt.Println("hashset add", v) 38 | } 39 | } 40 | 41 | if l.Contains(10) { 42 | fmt.Println("hashset contains 10") 43 | } 44 | 45 | l.Range(func(value int) bool { 46 | fmt.Println("hashset range found ", value) 47 | return true 48 | }) 49 | 50 | l.Remove(15) 51 | fmt.Printf("hashset contains %d items\r\n", l.Len()) 52 | } 53 | ``` 54 | 55 | ## Benchmark 56 | go version: go1.15.10 linux/amd64 57 | CPU: Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz (4C8T) 58 | OS: Debian 4.14.81.bm.15 59 | MEMORY: 16G 60 | 61 | ``` 62 | $ go test -run=None -bench=. -benchtime=1000000x -benchmem -count=10 -cpu=4 > 1000000x20x4.txt 63 | $ benchstat 1000000x20x4.txt 64 | name time/op 65 | ValueAsBool-4 301ns ± 7% 66 | ValueAsEmptyStruct-4 300ns ± 7% 67 | AddAfterContains-4 334ns ± 5% 68 | AddWithoutContains-4 303ns ± 9% 69 | RemoveAfterContains_Missing-4 177ns ± 4% 70 | RemoveWithoutContains_Missing-4 176ns ± 7% 71 | RemoveAfterContains_Hitting-4 205ns ± 2% 72 | RemoveWithoutContains_Hitting-4 135ns ±16% 73 | 74 | name alloc/op 75 | ValueAsBool-4 54.0B ± 0% 76 | ValueAsEmptyStruct-4 49.0B ± 0% 77 | AddAfterContains-4 49.0B ± 0% 78 | AddWithoutContains-4 49.0B ± 0% 79 | RemoveAfterContains_Missing-4 0.00B 80 | RemoveWithoutContains_Missing-4 0.00B 81 | RemoveAfterContains_Hitting-4 0.00B 82 | RemoveWithoutContains_Hitting-4 0.00B 83 | ``` 84 | -------------------------------------------------------------------------------- /collection/hashset/hashset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hashset 16 | 17 | type Int64Set map[int64]struct{} 18 | 19 | // NewInt64 returns an empty int64 set 20 | func NewInt64() Int64Set { 21 | return make(map[int64]struct{}) 22 | } 23 | 24 | // NewInt64WithSize returns an empty int64 set initialized with specific size 25 | func NewInt64WithSize(size int) Int64Set { 26 | return make(map[int64]struct{}, size) 27 | } 28 | 29 | // Add adds the specified element to this set 30 | // Always returns true due to the build-in map doesn't indicate caller whether the given element already exists 31 | // Reserves the return type for future extension 32 | func (s Int64Set) Add(value int64) bool { 33 | s[value] = struct{}{} 34 | return true 35 | } 36 | 37 | // Contains returns true if this set contains the specified element 38 | func (s Int64Set) Contains(value int64) bool { 39 | if _, ok := s[value]; ok { 40 | return true 41 | } 42 | return false 43 | } 44 | 45 | // Remove removes the specified element from this set 46 | // Always returns true due to the build-in map doesn't indicate caller whether the given element already exists 47 | // Reserves the return type for future extension 48 | func (s Int64Set) Remove(value int64) bool { 49 | delete(s, value) 50 | return true 51 | } 52 | 53 | // Range calls f sequentially for each value present in the hashset. 54 | // If f returns false, range stops the iteration. 55 | func (s Int64Set) Range(f func(value int64) bool) { 56 | for k := range s { 57 | if !f(k) { 58 | break 59 | } 60 | } 61 | } 62 | 63 | // Len returns the number of elements of this set 64 | func (s Int64Set) Len() int { 65 | return len(s) 66 | } 67 | -------------------------------------------------------------------------------- /collection/hashset/hashset_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hashset 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | ) 21 | 22 | func Example() { 23 | l := NewInt() 24 | 25 | for _, v := range []int{10, 12, 15} { 26 | l.Add(v) 27 | } 28 | 29 | if l.Contains(10) { 30 | fmt.Println("hashset contains 10") 31 | } 32 | 33 | l.Range(func(value int) bool { 34 | fmt.Println("hashset range found ", value) 35 | return true 36 | }) 37 | 38 | l.Remove(15) 39 | fmt.Printf("hashset contains %d items\r\n", l.Len()) 40 | } 41 | 42 | func TestIntSet(t *testing.T) { 43 | // Correctness. 44 | l := NewInt64() 45 | if l.Len() != 0 { 46 | t.Fatal("invalid length") 47 | } 48 | if l.Contains(0) { 49 | t.Fatal("invalid contains") 50 | } 51 | 52 | if l.Add(0); l.Len() != 1 { 53 | t.Fatal("invalid add") 54 | } 55 | if !l.Contains(0) { 56 | t.Fatal("invalid contains") 57 | } 58 | if l.Remove(0); l.Len() != 0 { 59 | t.Fatal("invalid remove") 60 | } 61 | 62 | if l.Add(20); l.Len() != 1 { 63 | t.Fatal("invalid add") 64 | } 65 | if l.Add(22); l.Len() != 2 { 66 | t.Fatal("invalid add") 67 | } 68 | if l.Add(21); l.Len() != 3 { 69 | t.Fatal("invalid add") 70 | } 71 | if l.Add(21); l.Len() != 3 { 72 | t.Fatal(l.Len(), " invalid add") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /collection/hashset/types_gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build ignore 16 | // +build ignore 17 | 18 | package main 19 | 20 | import ( 21 | "bytes" 22 | "go/format" 23 | "io/ioutil" 24 | "os" 25 | "strings" 26 | ) 27 | 28 | var lengthFunction = `func (s Int64Set) Len() int { 29 | return len(s) 30 | }` 31 | 32 | func main() { 33 | f, err := os.Open("hashset.go") 34 | if err != nil { 35 | panic(err) 36 | } 37 | filedata, err := ioutil.ReadAll(f) 38 | if err != nil { 39 | panic(err) 40 | } 41 | w := new(bytes.Buffer) 42 | w.WriteString(`// Copyright 2021 ByteDance Inc. 43 | // 44 | // Licensed under the Apache License, Version 2.0 (the "License"); 45 | // you may not use this file except in compliance with the License. 46 | // You may obtain a copy of the License at 47 | // 48 | // http://www.apache.org/licenses/LICENSE-2.0 49 | // 50 | // Unless required by applicable law or agreed to in writing, software 51 | // distributed under the License is distributed on an "AS IS" BASIS, 52 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 53 | // See the License for the specific language governing permissions and 54 | // limitations under the License. 55 | 56 | // Code generated by go run types_gen.go; DO NOT EDIT.` + "\r\n\n") 57 | start_pos := strings.Index(string(filedata), "package hashset") 58 | w.WriteString(string(filedata)[start_pos : start_pos+len("package hashset")]) 59 | ts := []string{"Float32", "Float64", "Int32", "Int16", "Int", "Uint64", "Uint32", "Uint16", "Uint"} // all types need to be converted 60 | 61 | for _, upper := range ts { 62 | lower := strings.ToLower(upper) 63 | data := string(filedata) 64 | // Remove header. 65 | data = data[start_pos+len("package hashset"):] 66 | // Remove the special case. 67 | data = strings.Replace(data, lengthFunction, "", -1) 68 | // Common cases. 69 | data = strings.Replace(data, "int64", lower, -1) 70 | data = strings.Replace(data, "Int64", upper, -1) 71 | if inSlice(lowerSlice(ts), lower) { 72 | data = strings.Replace(data, "length "+lower, "length int64", 1) 73 | } 74 | // Add the special case. 75 | data = data + strings.Replace(lengthFunction, "Int64Set", upper+"Set", 1) 76 | w.WriteString(data) 77 | w.WriteString("\r\n") 78 | } 79 | 80 | out, err := format.Source(w.Bytes()) 81 | if err != nil { 82 | panic(err) 83 | } 84 | if err := ioutil.WriteFile("types.go", out, 0660); err != nil { 85 | panic(err) 86 | } 87 | } 88 | 89 | func lowerSlice(s []string) []string { 90 | n := make([]string, len(s)) 91 | for i, v := range s { 92 | n[i] = strings.ToLower(v) 93 | } 94 | return n 95 | } 96 | 97 | func inSlice(s []string, val string) bool { 98 | for _, v := range s { 99 | if v == val { 100 | return true 101 | } 102 | } 103 | return false 104 | } 105 | -------------------------------------------------------------------------------- /collection/lscq/asm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build amd64 && !gccgo && !appengine 16 | // +build amd64,!gccgo,!appengine 17 | 18 | package lscq 19 | 20 | import ( 21 | "unsafe" 22 | ) 23 | 24 | type uint128 [2]uint64 25 | 26 | func compareAndSwapUint128(addr *uint128, old1, old2, new1, new2 uint64) (swapped bool) 27 | 28 | func loadUint128(addr *uint128) (val uint128) 29 | 30 | func loadSCQNodePointer(addr unsafe.Pointer) (val scqNodePointer) 31 | 32 | func loadSCQNodeUint64(addr unsafe.Pointer) (val scqNodeUint64) 33 | 34 | func atomicTestAndSetFirstBit(addr *uint64) (val uint64) 35 | 36 | func atomicTestAndSetSecondBit(addr *uint64) (val uint64) 37 | 38 | func resetNode(addr unsafe.Pointer) 39 | 40 | //go:nosplit 41 | func compareAndSwapSCQNodePointer(addr *scqNodePointer, old, new scqNodePointer) (swapped bool) { 42 | // Ref: src/runtime/atomic_pointer.go:sync_atomic_CompareAndSwapPointer 43 | if runtimeEnableWriteBarrier() { 44 | runtimeatomicwb(&addr.data, new.data) 45 | } 46 | return compareAndSwapUint128((*uint128)(runtimenoescape(unsafe.Pointer(addr))), old.flags, uint64(uintptr(old.data)), new.flags, uint64(uintptr(new.data))) 47 | } 48 | 49 | func compareAndSwapSCQNodeUint64(addr *scqNodeUint64, old, new scqNodeUint64) (swapped bool) { 50 | return compareAndSwapUint128((*uint128)(unsafe.Pointer(addr)), old.flags, old.data, new.flags, new.data) 51 | } 52 | 53 | func runtimeEnableWriteBarrier() bool 54 | 55 | //go:linkname runtimeatomicwb runtime.atomicwb 56 | //go:noescape 57 | func runtimeatomicwb(ptr *unsafe.Pointer, new unsafe.Pointer) 58 | 59 | //go:linkname runtimenoescape runtime.noescape 60 | func runtimenoescape(p unsafe.Pointer) unsafe.Pointer 61 | 62 | //go:nosplit 63 | func atomicWriteBarrier(ptr *unsafe.Pointer) { 64 | // For SCQ dequeue only. (fastpath) 65 | if runtimeEnableWriteBarrier() { 66 | runtimeatomicwb(ptr, nil) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /collection/lscq/asm.s: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build amd64,!gccgo,!appengine 16 | 17 | #include "textflag.h" 18 | 19 | TEXT ·compareAndSwapUint128(SB),NOSPLIT,$0 20 | MOVQ addr+0(FP), R8 21 | MOVQ old1+8(FP), AX 22 | MOVQ old2+16(FP), DX 23 | MOVQ new1+24(FP), BX 24 | MOVQ new2+32(FP), CX 25 | LOCK 26 | CMPXCHG16B (R8) 27 | SETEQ swapped+40(FP) 28 | RET 29 | 30 | TEXT ·loadUint128(SB),NOSPLIT,$0 31 | MOVQ addr+0(FP), R8 32 | XORQ AX, AX 33 | XORQ DX, DX 34 | XORQ BX, BX 35 | XORQ CX, CX 36 | LOCK 37 | CMPXCHG16B (R8) 38 | MOVQ AX, val_0+8(FP) 39 | MOVQ DX, val_1+16(FP) 40 | RET 41 | 42 | TEXT ·loadSCQNodeUint64(SB),NOSPLIT,$0 43 | JMP ·loadUint128(SB) 44 | 45 | TEXT ·loadSCQNodePointer(SB),NOSPLIT,$0 46 | JMP ·loadUint128(SB) 47 | 48 | TEXT ·atomicTestAndSetFirstBit(SB),NOSPLIT,$0 49 | MOVQ addr+0(FP), DX 50 | LOCK 51 | BTSQ $63,(DX) 52 | MOVQ AX, val+8(FP) 53 | RET 54 | 55 | TEXT ·atomicTestAndSetSecondBit(SB),NOSPLIT,$0 56 | MOVQ addr+0(FP), DX 57 | LOCK 58 | BTSQ $62,(DX) 59 | MOVQ AX, val+8(FP) 60 | RET 61 | 62 | TEXT ·resetNode(SB),NOSPLIT,$0 63 | MOVQ addr+0(FP), DX 64 | MOVQ $0, 8(DX) 65 | LOCK 66 | BTSQ $62, (DX) 67 | RET 68 | 69 | TEXT ·runtimeEnableWriteBarrier(SB),NOSPLIT,$0 70 | MOVL runtime·writeBarrier(SB), AX 71 | MOVB AX, ret+0(FP) 72 | RET 73 | -------------------------------------------------------------------------------- /collection/lscq/asm_arm64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build arm64 && !gccgo && !appengine 16 | // +build arm64,!gccgo,!appengine 17 | 18 | package lscq 19 | 20 | import ( 21 | "runtime" 22 | "unsafe" 23 | 24 | "golang.org/x/sys/cpu" 25 | ) 26 | 27 | var arm64HasAtomics = detectArm64HasAtomics() 28 | 29 | type uint128 [2]uint64 30 | 31 | func compareAndSwapUint128(addr *uint128, old1, old2, new1, new2 uint64) (swapped bool) 32 | 33 | func loadUint128(addr *uint128) (val uint128) 34 | 35 | func loadSCQNodePointer(addr unsafe.Pointer) (val scqNodePointer) 36 | 37 | func loadSCQNodeUint64(addr unsafe.Pointer) (val scqNodeUint64) 38 | 39 | func atomicTestAndSetFirstBit(addr *uint64) (val uint64) 40 | 41 | func atomicTestAndSetSecondBit(addr *uint64) (val uint64) 42 | 43 | func resetNode(addr unsafe.Pointer) 44 | 45 | //go:nosplit 46 | func compareAndSwapSCQNodePointer(addr *scqNodePointer, old, new scqNodePointer) (swapped bool) { 47 | // Ref: src/runtime/atomic_pointer.go:sync_atomic_CompareAndSwapPointer 48 | if runtimeEnableWriteBarrier() { 49 | runtimeatomicwb(&addr.data, new.data) 50 | } 51 | return compareAndSwapUint128((*uint128)(runtimenoescape(unsafe.Pointer(addr))), old.flags, uint64(uintptr(old.data)), new.flags, uint64(uintptr(new.data))) 52 | } 53 | 54 | func compareAndSwapSCQNodeUint64(addr *scqNodeUint64, old, new scqNodeUint64) (swapped bool) { 55 | return compareAndSwapUint128((*uint128)(unsafe.Pointer(addr)), old.flags, old.data, new.flags, new.data) 56 | } 57 | 58 | func runtimeEnableWriteBarrier() bool 59 | 60 | //go:linkname runtimeatomicwb runtime.atomicwb 61 | //go:noescape 62 | func runtimeatomicwb(ptr *unsafe.Pointer, new unsafe.Pointer) 63 | 64 | //go:linkname runtimenoescape runtime.noescape 65 | func runtimenoescape(p unsafe.Pointer) unsafe.Pointer 66 | 67 | //go:nosplit 68 | func atomicWriteBarrier(ptr *unsafe.Pointer) { 69 | // For SCQ dequeue only. (fastpath) 70 | if runtimeEnableWriteBarrier() { 71 | runtimeatomicwb(ptr, nil) 72 | } 73 | } 74 | 75 | //go:linkname sysctlEnabled internal/cpu.sysctlEnabled 76 | func sysctlEnabled(name []byte) bool 77 | 78 | func detectArm64HasAtomics() bool { 79 | switch runtime.GOOS { 80 | case "darwin": 81 | return sysctlEnabled([]byte("hw.optional.armv8_1_atomics\x00")) 82 | default: 83 | return cpu.ARM64.HasATOMICS 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /collection/lscq/asm_arm64.s: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include "textflag.h" 16 | #include "funcdata.h" 17 | 18 | TEXT ·compareAndSwapUint128(SB), NOSPLIT, $0-41 19 | MOVD addr+0(FP), R0 20 | MOVD old1+8(FP), R2 21 | MOVD old2+16(FP), R3 22 | MOVD new1+24(FP), R4 23 | MOVD new2+32(FP), R5 24 | MOVBU ·arm64HasAtomics+0(SB), R1 25 | CBZ R1, load_store_loop 26 | MOVD R2, R6 27 | MOVD R3, R7 28 | CASPD (R2, R3), (R0), (R4, R5) 29 | CMP R2, R6 30 | BNE ok 31 | CMP R3, R7 32 | CSET EQ, R0 33 | MOVB R0, ret+40(FP) 34 | RET 35 | load_store_loop: 36 | LDAXP (R0), (R6, R7) 37 | CMP R2, R6 38 | BNE ok 39 | CMP R3, R7 40 | BNE ok 41 | STLXP (R4, R5), (R0), R6 42 | CBNZ R6, load_store_loop 43 | ok: 44 | CSET EQ, R0 45 | MOVB R0, ret+40(FP) 46 | RET 47 | 48 | TEXT ·loadUint128(SB),NOSPLIT,$0-24 49 | MOVD ptr+0(FP), R0 50 | LDAXP (R0), (R0, R1) 51 | MOVD R0, ret+8(FP) 52 | MOVD R1, ret+16(FP) 53 | RET 54 | 55 | TEXT ·loadSCQNodeUint64(SB),NOSPLIT,$0 56 | MOVD ptr+0(FP), R0 57 | LDAXP (R0), (R0, R1) 58 | MOVD R0, ret+8(FP) 59 | MOVD R1, ret+16(FP) 60 | RET 61 | 62 | TEXT ·loadSCQNodePointer(SB),NOSPLIT,$0 63 | MOVD ptr+0(FP), R0 64 | LDAXP (R0), (R0, R1) 65 | MOVD R0, ret+8(FP) 66 | MOVD R1, ret+16(FP) 67 | RET 68 | 69 | TEXT ·atomicTestAndSetFirstBit(SB),NOSPLIT,$0 70 | MOVD addr+0(FP), R0 71 | load_store_loop: 72 | LDAXR (R0), R1 73 | ORR $(1<<63), R1, R1 74 | STLXR R1, (R0), R2 75 | CBNZ R2, load_store_loop 76 | MOVD R1, val+8(FP) 77 | RET 78 | 79 | 80 | TEXT ·atomicTestAndSetSecondBit(SB),NOSPLIT,$0 81 | MOVD addr+0(FP), R0 82 | load_store_loop: 83 | LDAXR (R0), R1 84 | ORR $(1<<62), R1, R1 85 | STLXR R1, (R0), R2 86 | CBNZ R2, load_store_loop 87 | MOVD R1, val+8(FP) 88 | RET 89 | 90 | TEXT ·resetNode(SB),NOSPLIT,$0 91 | MOVD addr+0(FP), R0 92 | MOVD $0, 8(R0) 93 | load_store_loop: 94 | LDAXR (R0), R1 95 | ORR $(1<<62), R1, R1 96 | STLXR R1, (R0), R2 97 | CBNZ R2, load_store_loop 98 | RET 99 | 100 | TEXT ·runtimeEnableWriteBarrier(SB),NOSPLIT,$0 101 | MOVW runtime·writeBarrier(SB), R0 102 | MOVB R0, ret+0(FP) 103 | RET 104 | -------------------------------------------------------------------------------- /collection/lscq/types_gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build ignore 16 | // +build ignore 17 | 18 | package main 19 | 20 | import ( 21 | "bytes" 22 | "go/format" 23 | "io/ioutil" 24 | "os" 25 | "strings" 26 | ) 27 | 28 | var license = `// Copyright 2021 ByteDance Inc. 29 | // 30 | // Licensed under the Apache License, Version 2.0 (the "License"); 31 | // you may not use this file except in compliance with the License. 32 | // You may obtain a copy of the License at 33 | // 34 | // http://www.apache.org/licenses/LICENSE-2.0 35 | // 36 | // Unless required by applicable law or agreed to in writing, software 37 | // distributed under the License is distributed on an "AS IS" BASIS, 38 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 39 | // See the License for the specific language governing permissions and 40 | // limitations under the License. 41 | ` 42 | 43 | func main() { 44 | f, err := os.Open("lscq.go") 45 | if err != nil { 46 | panic(err) 47 | } 48 | filedata, err := ioutil.ReadAll(f) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | w := new(bytes.Buffer) 54 | w.WriteString(license + `// Code generated by go run types_gen.go; DO NOT EDIT.` + "\r\n") 55 | w.WriteString(string(filedata)[strings.Index(string(filedata), "package lscq") : strings.Index(string(filedata), ")\n")+1]) 56 | // ts := []string{"Float32", "Float64", "Int64", "Int32", "Int16", "Int", "Uint64", "Uint32", "Uint16", "Uint"} // all types need to be converted 57 | ts := []string{"Uint64"} // all types need to be converted 58 | for _, upper := range ts { 59 | lower := strings.ToLower(upper) 60 | data := string(filedata) 61 | // Remove header(imported packages). 62 | data = data[strings.Index(data, ")\n")+1:] 63 | // Common cases. 64 | data = strings.Replace(data, "atomic.StorePointer((*unsafe.Pointer)(ent.data), nil)", "", -1) 65 | data = strings.Replace(data, "NewPointer", "New"+upper, -1) 66 | data = strings.Replace(data, "data unsafe.Pointer", "data "+lower, -1) 67 | data = strings.Replace(data, "data unsafe.Pointer", "data "+lower, -1) 68 | data = strings.Replace(data, "pointerSCQ", lower+"SCQ", -1) 69 | data = strings.Replace(data, "PointerSCQ", upper+"SCQ", -1) 70 | data = strings.Replace(data, "pointerQueue", lower+"Queue", -1) 71 | data = strings.Replace(data, "PointerQueue", upper+"Queue", -1) 72 | data = strings.Replace(data, "scqNodePointer", "scqNode"+upper, -1) 73 | data = strings.Replace(data, "compareAndSwapSCQNodePointer", "compareAndSwapSCQNode"+upper, -1) 74 | data = strings.Replace(data, "loadSCQNodePointer", "loadSCQNode"+upper, -1) 75 | data = strings.Replace(data, "pointerSCQPool", lower+"SCQPool", -1) 76 | data = strings.Replace(data, "atomicWriteBarrier(&entAddr.data)", "", -1) 77 | w.WriteString(data) 78 | w.WriteString("\r\n") 79 | } 80 | 81 | out, err := format.Source(w.Bytes()) 82 | if err != nil { 83 | panic(err) 84 | } 85 | if err := ioutil.WriteFile("types.go", out, 0660); err != nil { 86 | panic(err) 87 | } 88 | } 89 | 90 | func lowerSlice(s []string) []string { 91 | n := make([]string, len(s)) 92 | for i, v := range s { 93 | n[i] = strings.ToLower(v) 94 | } 95 | return n 96 | } 97 | 98 | func inSlice(s []string, val string) bool { 99 | for _, v := range s { 100 | if v == val { 101 | return true 102 | } 103 | } 104 | return false 105 | } 106 | -------------------------------------------------------------------------------- /collection/lscq/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lscq 16 | 17 | import ( 18 | "unsafe" 19 | 20 | "golang.org/x/sys/cpu" 21 | ) 22 | 23 | const ( 24 | scqsize = 1 << 16 25 | cacheLineSize = unsafe.Sizeof(cpu.CacheLinePad{}) 26 | ) 27 | 28 | func uint64Get63(value uint64) uint64 { 29 | return value & ((1 << 63) - 1) 30 | } 31 | 32 | func uint64Get1(value uint64) bool { 33 | return (value & (1 << 63)) == (1 << 63) 34 | } 35 | 36 | func uint64GetAll(value uint64) (bool, uint64) { 37 | return (value & (1 << 63)) == (1 << 63), value & ((1 << 63) - 1) 38 | } 39 | 40 | func loadSCQFlags(flags uint64) (isSafe bool, isEmpty bool, cycle uint64) { 41 | isSafe = (flags & (1 << 63)) == (1 << 63) 42 | isEmpty = (flags & (1 << 62)) == (1 << 62) 43 | cycle = flags & ((1 << 62) - 1) 44 | return isSafe, isEmpty, cycle 45 | } 46 | 47 | func newSCQFlags(isSafe bool, isEmpty bool, cycle uint64) uint64 { 48 | v := cycle & ((1 << 62) - 1) 49 | if isSafe { 50 | v += 1 << 63 51 | } 52 | if isEmpty { 53 | v += 1 << 62 54 | } 55 | return v 56 | } 57 | 58 | func cacheRemap16Byte(index uint64) uint64 { 59 | const cacheLineSize = cacheLineSize / 2 60 | rawIndex := index & uint64(scqsize-1) 61 | cacheLineNum := (rawIndex) % (scqsize / uint64(cacheLineSize)) 62 | cacheLineIdx := rawIndex / (scqsize / uint64(cacheLineSize)) 63 | return cacheLineNum*uint64(cacheLineSize) + cacheLineIdx 64 | } 65 | -------------------------------------------------------------------------------- /collection/skipmap/asm.s: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The runtime package uses //go:linkname to push a few functions into this 16 | // package but we still need a .s file so the Go tool does not pass -complete 17 | // to the go tool compile so the latter does not complain about Go functions 18 | // with no bodies. 19 | // See https://github.com/golang/go/issues/23311 20 | -------------------------------------------------------------------------------- /collection/skipmap/bench.sh: -------------------------------------------------------------------------------- 1 | go test -run=NOTEST -bench=. -count=10 -timeout=60m -benchtime=100000x -benchmem > x.txt && benchstat x.txt -------------------------------------------------------------------------------- /collection/skipmap/bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/bytedance/gopkg/lang/fastrand" 21 | ) 22 | 23 | func BenchmarkLoadOrStoreExist(b *testing.B) { 24 | m := NewInt() 25 | m.Store(1, 1) 26 | b.ResetTimer() 27 | b.RunParallel(func(p *testing.PB) { 28 | for p.Next() { 29 | m.LoadOrStore(1, 1) 30 | } 31 | }) 32 | } 33 | 34 | func BenchmarkLoadOrStoreLazyExist(b *testing.B) { 35 | m := NewInt() 36 | m.Store(1, 1) 37 | b.ResetTimer() 38 | b.RunParallel(func(p *testing.PB) { 39 | for p.Next() { 40 | m.LoadOrStoreLazy(1, func() interface{} { return 1 }) 41 | } 42 | }) 43 | } 44 | 45 | func BenchmarkLoadOrStoreExistSingle(b *testing.B) { 46 | m := NewInt() 47 | m.Store(1, 1) 48 | b.ResetTimer() 49 | for i := 0; i < b.N; i++ { 50 | m.LoadOrStore(1, 1) 51 | } 52 | } 53 | 54 | func BenchmarkLoadOrStoreLazyExistSingle(b *testing.B) { 55 | m := NewInt() 56 | m.Store(1, 1) 57 | b.ResetTimer() 58 | for i := 0; i < b.N; i++ { 59 | m.LoadOrStoreLazy(1, func() interface{} { return 1 }) 60 | } 61 | } 62 | 63 | func BenchmarkLoadOrStoreRandom(b *testing.B) { 64 | m := NewInt() 65 | b.ResetTimer() 66 | b.RunParallel(func(p *testing.PB) { 67 | for p.Next() { 68 | m.LoadOrStore(fastrand.Int(), 1) 69 | } 70 | }) 71 | } 72 | 73 | func BenchmarkLoadOrStoreLazyRandom(b *testing.B) { 74 | m := NewInt() 75 | b.ResetTimer() 76 | b.RunParallel(func(p *testing.PB) { 77 | for p.Next() { 78 | m.LoadOrStoreLazy(fastrand.Int(), func() interface{} { return 1 }) 79 | } 80 | }) 81 | } 82 | 83 | func BenchmarkLoadOrStoreRandomSingle(b *testing.B) { 84 | m := NewInt() 85 | b.ResetTimer() 86 | for i := 0; i < b.N; i++ { 87 | m.LoadOrStore(fastrand.Int(), 1) 88 | } 89 | } 90 | 91 | func BenchmarkLoadOrStoreLazyRandomSingle(b *testing.B) { 92 | m := NewInt() 93 | b.ResetTimer() 94 | for i := 0; i < b.N; i++ { 95 | m.LoadOrStoreLazy(fastrand.Int(), func() interface{} { return 1 }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /collection/skipmap/flag.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import "sync/atomic" 18 | 19 | const ( 20 | fullyLinked = 1 << iota 21 | marked 22 | ) 23 | 24 | // concurrent-safe bitflag. 25 | type bitflag struct { 26 | data uint32 27 | } 28 | 29 | func (f *bitflag) SetTrue(flags uint32) { 30 | for { 31 | old := atomic.LoadUint32(&f.data) 32 | if old&flags != flags { 33 | // Flag is 0, need set it to 1. 34 | n := old | flags 35 | if atomic.CompareAndSwapUint32(&f.data, old, n) { 36 | return 37 | } 38 | continue 39 | } 40 | return 41 | } 42 | } 43 | 44 | func (f *bitflag) SetFalse(flags uint32) { 45 | for { 46 | old := atomic.LoadUint32(&f.data) 47 | check := old & flags 48 | if check != 0 { 49 | // Flag is 1, need set it to 0. 50 | n := old ^ check 51 | if atomic.CompareAndSwapUint32(&f.data, old, n) { 52 | return 53 | } 54 | continue 55 | } 56 | return 57 | } 58 | } 59 | 60 | func (f *bitflag) Get(flag uint32) bool { 61 | return (atomic.LoadUint32(&f.data) & flag) != 0 62 | } 63 | 64 | func (f *bitflag) MGet(check, expect uint32) bool { 65 | return (atomic.LoadUint32(&f.data) & check) == expect 66 | } 67 | -------------------------------------------------------------------------------- /collection/skipmap/oparray.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import ( 18 | "sync/atomic" 19 | "unsafe" 20 | ) 21 | 22 | const ( 23 | op1 = 4 24 | op2 = maxLevel - op1 25 | ) 26 | 27 | type optionalArray struct { 28 | base [op1]unsafe.Pointer 29 | extra *([op2]unsafe.Pointer) 30 | } 31 | 32 | func (a *optionalArray) load(i int) unsafe.Pointer { 33 | if i < op1 { 34 | return a.base[i] 35 | } 36 | return a.extra[i-op1] 37 | } 38 | 39 | func (a *optionalArray) store(i int, p unsafe.Pointer) { 40 | if i < op1 { 41 | a.base[i] = p 42 | return 43 | } 44 | a.extra[i-op1] = p 45 | } 46 | 47 | func (a *optionalArray) atomicLoad(i int) unsafe.Pointer { 48 | if i < op1 { 49 | return atomic.LoadPointer(&a.base[i]) 50 | } 51 | return atomic.LoadPointer(&a.extra[i-op1]) 52 | } 53 | 54 | func (a *optionalArray) atomicStore(i int, p unsafe.Pointer) { 55 | if i < op1 { 56 | atomic.StorePointer(&a.base[i], p) 57 | return 58 | } 59 | atomic.StorePointer(&a.extra[i-op1], p) 60 | } 61 | -------------------------------------------------------------------------------- /collection/skipmap/oparry_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import ( 18 | "testing" 19 | "unsafe" 20 | 21 | "github.com/bytedance/gopkg/lang/fastrand" 22 | ) 23 | 24 | type dummy struct { 25 | data optionalArray 26 | } 27 | 28 | func TestOpArray(t *testing.T) { 29 | n := new(dummy) 30 | n.data.extra = new([op2]unsafe.Pointer) 31 | 32 | var array [maxLevel]unsafe.Pointer 33 | for i := 0; i < maxLevel; i++ { 34 | value := unsafe.Pointer(&dummy{}) 35 | array[i] = value 36 | n.data.store(i, value) 37 | } 38 | 39 | for i := 0; i < maxLevel; i++ { 40 | if array[i] != n.data.load(i) || array[i] != n.data.atomicLoad(i) { 41 | t.Fatal(i, array[i], n.data.load(i)) 42 | } 43 | } 44 | 45 | for i := 0; i < 1000; i++ { 46 | r := int(fastrand.Uint32n(maxLevel)) 47 | value := unsafe.Pointer(&dummy{}) 48 | if i%100 == 0 { 49 | value = nil 50 | } 51 | array[r] = value 52 | if fastrand.Uint32n(2) == 0 { 53 | n.data.store(r, value) 54 | } else { 55 | n.data.atomicStore(r, value) 56 | } 57 | } 58 | 59 | for i := 0; i < maxLevel; i++ { 60 | if array[i] != n.data.load(i) || array[i] != n.data.atomicLoad(i) { 61 | t.Fatal(i, array[i], n.data.load(i)) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /collection/skipmap/skipmap_str_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import ( 18 | "reflect" 19 | "strconv" 20 | "sync" 21 | "sync/atomic" 22 | "testing" 23 | ) 24 | 25 | func TestStringMap(t *testing.T) { 26 | m := NewString() 27 | 28 | // Correctness. 29 | m.Store("123", "123") 30 | v, ok := m.Load("123") 31 | if !ok || v != "123" || m.Len() != 1 { 32 | t.Fatal("invalid") 33 | } 34 | 35 | m.Store("123", "456") 36 | v, ok = m.Load("123") 37 | if !ok || v != "456" || m.Len() != 1 { 38 | t.Fatal("invalid") 39 | } 40 | 41 | m.Store("123", 456) 42 | v, ok = m.Load("123") 43 | if !ok || v != 456 || m.Len() != 1 { 44 | t.Fatal("invalid") 45 | } 46 | 47 | m.Delete("123") 48 | _, ok = m.Load("123") 49 | if ok || m.Len() != 0 { 50 | t.Fatal("invalid") 51 | } 52 | 53 | _, ok = m.LoadOrStore("123", 456) 54 | if ok || m.Len() != 1 { 55 | t.Fatal("invalid") 56 | } 57 | 58 | v, ok = m.Load("123") 59 | if !ok || v != 456 || m.Len() != 1 { 60 | t.Fatal("invalid") 61 | } 62 | 63 | v, ok = m.LoadAndDelete("123") 64 | if !ok || v != 456 || m.Len() != 0 { 65 | t.Fatal("invalid") 66 | } 67 | 68 | _, ok = m.LoadOrStore("123", 456) 69 | if ok || m.Len() != 1 { 70 | t.Fatal("invalid") 71 | } 72 | 73 | m.LoadOrStore("456", 123) 74 | if ok || m.Len() != 2 { 75 | t.Fatal("invalid") 76 | } 77 | 78 | m.Range(func(key string, value interface{}) bool { 79 | if key == "123" { 80 | m.Store("123", 123) 81 | } else if key == "456" { 82 | m.LoadAndDelete("456") 83 | } 84 | return true 85 | }) 86 | 87 | v, ok = m.Load("123") 88 | if !ok || v != 123 || m.Len() != 1 { 89 | t.Fatal("invalid") 90 | } 91 | 92 | // Concurrent. 93 | var wg sync.WaitGroup 94 | for i := 0; i < 1000; i++ { 95 | i := i 96 | wg.Add(1) 97 | go func() { 98 | n := strconv.Itoa(i) 99 | m.Store(n, int(i+1000)) 100 | wg.Done() 101 | }() 102 | } 103 | wg.Wait() 104 | var count2 int64 105 | m.Range(func(key string, value interface{}) bool { 106 | atomic.AddInt64(&count2, 1) 107 | return true 108 | }) 109 | m.Delete("600") 110 | var count int64 111 | m.Range(func(key string, value interface{}) bool { 112 | atomic.AddInt64(&count, 1) 113 | return true 114 | }) 115 | 116 | val, ok := m.Load("500") 117 | if !ok || reflect.TypeOf(val).Kind().String() != "int" || val.(int) != 1500 { 118 | t.Fatal("fail") 119 | } 120 | 121 | _, ok = m.Load("600") 122 | if ok { 123 | t.Fatal("fail") 124 | } 125 | 126 | if m.Len() != 999 || int(count) != m.Len() { 127 | t.Fatal("fail", m.Len(), count, count2) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /collection/skipmap/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipmap 16 | 17 | import ( 18 | _ "unsafe" // for linkname 19 | 20 | "github.com/bytedance/gopkg/internal/wyhash" 21 | "github.com/bytedance/gopkg/lang/fastrand" 22 | ) 23 | 24 | const ( 25 | maxLevel = 16 26 | p = 0.25 27 | defaultHighestLevel = 3 28 | ) 29 | 30 | func hash(s string) uint64 { 31 | return wyhash.Sum64String(s) 32 | } 33 | 34 | //go:linkname cmpstring runtime.cmpstring 35 | func cmpstring(a, b string) int 36 | 37 | func randomLevel() int { 38 | level := 1 39 | for fastrand.Uint32n(1/p) == 0 { 40 | level++ 41 | } 42 | if level > maxLevel { 43 | return maxLevel 44 | } 45 | return level 46 | } 47 | -------------------------------------------------------------------------------- /collection/skipset/asm.s: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The runtime package uses //go:linkname to push a few functions into this 16 | // package but we still need a .s file so the Go tool does not pass -complete 17 | // to the go tool compile so the latter does not complain about Go functions 18 | // with no bodies. 19 | // See https://github.com/golang/go/issues/23311 -------------------------------------------------------------------------------- /collection/skipset/bench.sh: -------------------------------------------------------------------------------- 1 | go test -run=NOTEST -bench=. -benchtime=100000x -benchmem -count=10 -timeout=60m > x.txt && benchstat x.txt -------------------------------------------------------------------------------- /collection/skipset/flag.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import "sync/atomic" 18 | 19 | const ( 20 | fullyLinked = 1 << iota 21 | marked 22 | ) 23 | 24 | type bitflag struct { 25 | data uint32 26 | } 27 | 28 | func (f *bitflag) SetTrue(flags uint32) { 29 | for { 30 | old := atomic.LoadUint32(&f.data) 31 | if old&flags != flags { 32 | // Flag is 0, need set it to 1. 33 | n := old | flags 34 | if atomic.CompareAndSwapUint32(&f.data, old, n) { 35 | return 36 | } 37 | continue 38 | } 39 | return 40 | } 41 | } 42 | 43 | func (f *bitflag) SetFalse(flags uint32) { 44 | for { 45 | old := atomic.LoadUint32(&f.data) 46 | check := old & flags 47 | if check != 0 { 48 | // Flag is 1, need set it to 0. 49 | n := old ^ check 50 | if atomic.CompareAndSwapUint32(&f.data, old, n) { 51 | return 52 | } 53 | continue 54 | } 55 | return 56 | } 57 | } 58 | 59 | func (f *bitflag) Get(flag uint32) bool { 60 | return (atomic.LoadUint32(&f.data) & flag) != 0 61 | } 62 | 63 | func (f *bitflag) MGet(check, expect uint32) bool { 64 | return (atomic.LoadUint32(&f.data) & check) == expect 65 | } 66 | -------------------------------------------------------------------------------- /collection/skipset/flag_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestFlag(t *testing.T) { 22 | // Correctness. 23 | const ( 24 | f0 = 1 << iota 25 | f1 26 | f2 27 | f3 28 | f4 29 | f5 30 | f6 31 | f7 32 | ) 33 | x := &bitflag{} 34 | 35 | x.SetTrue(f1 | f3) 36 | if x.Get(f0) || !x.Get(f1) || x.Get(f2) || !x.Get(f3) || !x.MGet(f0|f1|f2|f3, f1|f3) { 37 | t.Fatal("invalid") 38 | } 39 | x.SetTrue(f1) 40 | x.SetTrue(f1 | f3) 41 | if x.data != f1+f3 { 42 | t.Fatal("invalid") 43 | } 44 | 45 | x.SetFalse(f1 | f2) 46 | if x.Get(f0) || x.Get(f1) || x.Get(f2) || !x.Get(f3) || !x.MGet(f0|f1|f2|f3, f3) { 47 | t.Fatal("invalid") 48 | } 49 | x.SetFalse(f1 | f2) 50 | if x.data != f3 { 51 | t.Fatal("invalid") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /collection/skipset/oparry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import ( 18 | "sync/atomic" 19 | "unsafe" 20 | ) 21 | 22 | const ( 23 | op1 = 4 24 | op2 = maxLevel - op1 25 | ) 26 | 27 | type optionalArray struct { 28 | base [op1]unsafe.Pointer 29 | extra *([op2]unsafe.Pointer) 30 | } 31 | 32 | func (a *optionalArray) load(i int) unsafe.Pointer { 33 | if i < op1 { 34 | return a.base[i] 35 | } 36 | return a.extra[i-op1] 37 | } 38 | 39 | func (a *optionalArray) store(i int, p unsafe.Pointer) { 40 | if i < op1 { 41 | a.base[i] = p 42 | return 43 | } 44 | a.extra[i-op1] = p 45 | } 46 | 47 | func (a *optionalArray) atomicLoad(i int) unsafe.Pointer { 48 | if i < op1 { 49 | return atomic.LoadPointer(&a.base[i]) 50 | } 51 | return atomic.LoadPointer(&a.extra[i-op1]) 52 | } 53 | 54 | func (a *optionalArray) atomicStore(i int, p unsafe.Pointer) { 55 | if i < op1 { 56 | atomic.StorePointer(&a.base[i], p) 57 | return 58 | } 59 | atomic.StorePointer(&a.extra[i-op1], p) 60 | } 61 | -------------------------------------------------------------------------------- /collection/skipset/oparry_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import ( 18 | "testing" 19 | "unsafe" 20 | 21 | "github.com/bytedance/gopkg/lang/fastrand" 22 | ) 23 | 24 | type dummy struct { 25 | data optionalArray 26 | } 27 | 28 | func TestOpArray(t *testing.T) { 29 | n := new(dummy) 30 | n.data.extra = new([op2]unsafe.Pointer) 31 | 32 | var array [maxLevel]unsafe.Pointer 33 | for i := 0; i < maxLevel; i++ { 34 | value := unsafe.Pointer(&dummy{}) 35 | array[i] = value 36 | n.data.store(i, value) 37 | } 38 | 39 | for i := 0; i < maxLevel; i++ { 40 | if array[i] != n.data.load(i) || array[i] != n.data.atomicLoad(i) { 41 | t.Fatal(i, array[i], n.data.load(i)) 42 | } 43 | } 44 | 45 | for i := 0; i < 1000; i++ { 46 | r := int(fastrand.Uint32n(maxLevel)) 47 | value := unsafe.Pointer(&dummy{}) 48 | if i%100 == 0 { 49 | value = nil 50 | } 51 | array[r] = value 52 | if fastrand.Uint32n(2) == 0 { 53 | n.data.store(r, value) 54 | } else { 55 | n.data.atomicStore(r, value) 56 | } 57 | } 58 | 59 | for i := 0; i < maxLevel; i++ { 60 | if array[i] != n.data.load(i) || array[i] != n.data.atomicLoad(i) { 61 | t.Fatal(i, array[i], n.data.load(i)) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /collection/skipset/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package skipset 16 | 17 | import ( 18 | _ "unsafe" // for linkname 19 | 20 | "github.com/bytedance/gopkg/internal/wyhash" 21 | "github.com/bytedance/gopkg/lang/fastrand" 22 | ) 23 | 24 | const ( 25 | maxLevel = 16 26 | p = 0.25 27 | defaultHighestLevel = 3 28 | ) 29 | 30 | func hash(s string) uint64 { 31 | return wyhash.Sum64String(s) 32 | } 33 | 34 | //go:linkname cmpstring runtime.cmpstring 35 | func cmpstring(a, b string) int 36 | 37 | func randomLevel() int { 38 | level := 1 39 | for fastrand.Uint32n(1/p) == 0 { 40 | level++ 41 | } 42 | if level > maxLevel { 43 | return maxLevel 44 | } 45 | return level 46 | } 47 | -------------------------------------------------------------------------------- /collection/zset/oparry.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zset 16 | 17 | import ( 18 | "unsafe" 19 | ) 20 | 21 | const ( 22 | op1 = 4 23 | op2 = maxLevel - op1 // TODO: not sure that whether 4 is the best number for op1([28]Pointer for op2). 24 | ) 25 | 26 | type listLevel struct { 27 | next unsafe.Pointer // the forward pointer 28 | span int // span is count of level 0 element to next element in current level 29 | } 30 | type optionalArray struct { 31 | base [op1]listLevel 32 | extra *([op2]listLevel) 33 | } 34 | 35 | func (a *optionalArray) init(level int) { 36 | if level > op1 { 37 | a.extra = new([op2]listLevel) 38 | } 39 | } 40 | 41 | func (a *optionalArray) loadNext(i int) unsafe.Pointer { 42 | if i < op1 { 43 | return a.base[i].next 44 | } 45 | return a.extra[i-op1].next 46 | } 47 | 48 | func (a *optionalArray) storeNext(i int, p unsafe.Pointer) { 49 | if i < op1 { 50 | a.base[i].next = p 51 | return 52 | } 53 | a.extra[i-op1].next = p 54 | } 55 | 56 | func (a *optionalArray) loadSpan(i int) int { 57 | if i < op1 { 58 | return a.base[i].span 59 | } 60 | return a.extra[i-op1].span 61 | } 62 | 63 | func (a *optionalArray) storeSpan(i int, s int) { 64 | if i < op1 { 65 | a.base[i].span = s 66 | return 67 | } 68 | a.extra[i-op1].span = s 69 | } 70 | -------------------------------------------------------------------------------- /collection/zset/opt.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zset 16 | 17 | // RangeOpt describes the whether the min/max is exclusive in score range. 18 | type RangeOpt struct { 19 | ExcludeMin bool 20 | ExcludeMax bool 21 | } 22 | -------------------------------------------------------------------------------- /collection/zset/readme.md: -------------------------------------------------------------------------------- 1 | # zset 2 | 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/bytedance/gopkg/collection/zset.svg)](https://pkg.go.dev/github.com/bytedance/gopkg/collection/zset) 4 | 5 | ## Introduction 6 | 7 | zset provides a concurrent-safety sorted set, can be used as a local replacement of [Redis' zset](https://redis.com/ebook/part-2-core-concepts/chapter-3-commands-in-redis/3-5-sorted-sets/). 8 | 9 | The main difference to other sets is, every value of set is associated with a score, that is used to take the sorted set ordered, from the smallest to the greatest score. 10 | 11 | The zset has `O(log(N))` time complexity when doing Add(ZADD) and Remove(ZREM) operations and `O(1)` time complexity when doing Contains operations. 12 | 13 | ## Features 14 | 15 | - Concurrent safe API 16 | - Values are sorted with score 17 | - Implementation equivalent to redis 18 | - Fast skiplist level randomization 19 | 20 | ## Comparison 21 | 22 | | Redis command | Go function | 23 | |-----------------------|---------------------| 24 | | ZADD | Add | 25 | | ZINCRBY | IncrBy | 26 | | ZREM | Remove | 27 | | ZREMRANGEBYSCORE | RemoveRangeByScore | 28 | | ZREMRANGEBYRANK | RemoveRangeByRank | 29 | | ZUNION | Union | 30 | | ZINTER | Inter | 31 | | ZINTERCARD | *TODO* | 32 | | ZDIFF | *TODO* | 33 | | ZRANGE | Range | 34 | | ZRANGEBYSCORE | IncrBy | 35 | | ZREVRANGEBYSCORE | RevRangeByScore | 36 | | ZCOUNT | Count | 37 | | ZREVRANGE | RevRange | 38 | | ZCARD | Len | 39 | | ZSCORE | Score | 40 | | ZRANK | Rank | 41 | | ZREVRANK | RevRank | 42 | | ZPOPMIN | *TODO* | 43 | | ZPOPMAX | *TODO* | 44 | | ZRANDMEMBER | *TODO* | 45 | 46 | List of redis commands are generated from the following command: 47 | 48 | ```bash 49 | cat redis/src/server.c | grep -o '"z.*",z.*Command' | grep -o '".*"' | cut -d '"' -f2 50 | ``` 51 | 52 | You may find that not all redis commands have corresponding go implementations, 53 | the reason is as follows: 54 | 55 | ### Unsupported Commands 56 | 57 | Redis' zset can operates elements in lexicographic order, which is not commonly 58 | used function, so zset does not support commands like ZREMRANGEBYLEX, ZLEXCOUNT 59 | and so on. 60 | 61 | | Redis command | 62 | |-----------------------| 63 | | ZREMRANGEBYLEX | 64 | | ZRANGEBYLEX | 65 | | ZREVRANGEBYLEX | 66 | | ZLEXCOUNT | 67 | 68 | In redis, user accesses zset via a string key. We do not need such string key 69 | because we have variable. so the following commands are not implemented: 70 | 71 | | Redis command | 72 | |-----------------------| 73 | | ZUNIONSTORE | 74 | | ZINTERSTORE | 75 | | ZDIFFSTORE | 76 | | ZRANGESTORE | 77 | | ZMSCORE | 78 | | ZSCAN | 79 | 80 | ## QuickStart 81 | 82 | ```go 83 | package main 84 | 85 | import ( 86 | "fmt" 87 | 88 | "github.com/bytedance/gopkg/collection/zset" 89 | ) 90 | 91 | func main() { 92 | z := zset.NewFloat64() 93 | 94 | values := []string{"Alice", "Bob", "Zhang"} 95 | scores := []float64{90, 89, 59.9} 96 | for i := range values { 97 | z.Add(scores[i], values[i]) 98 | } 99 | 100 | s, ok := z.Score("Alice") 101 | if ok { 102 | fmt.Println("Alice's score is", s) 103 | } 104 | 105 | n := z.Count(0, 60) 106 | fmt.Println("There are", n, "people below 60 points") 107 | 108 | for _, n := range z.Range(0, -1) { 109 | fmt.Println("zset range found", n.Value, n.Score) 110 | } 111 | } 112 | ``` 113 | -------------------------------------------------------------------------------- /collection/zset/zset_bench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package zset 16 | 17 | import ( 18 | "math" 19 | "strconv" 20 | "testing" 21 | 22 | "github.com/bytedance/gopkg/lang/fastrand" 23 | ) 24 | 25 | const initsize = 1 << 10 26 | const randN = math.MaxUint32 27 | 28 | func BenchmarkContains100Hits(b *testing.B) { 29 | benchmarkContainsNHits(b, 100) 30 | } 31 | 32 | func BenchmarkContains50Hits(b *testing.B) { 33 | benchmarkContainsNHits(b, 50) 34 | } 35 | 36 | func BenchmarkContainsNoHits(b *testing.B) { 37 | benchmarkContainsNHits(b, 0) 38 | } 39 | 40 | func benchmarkContainsNHits(b *testing.B, n int) { 41 | b.Run("sortedset", func(b *testing.B) { 42 | z := NewFloat64() 43 | var vals []string 44 | for i := 0; i < initsize; i++ { 45 | val := strconv.Itoa(i) 46 | vals = append(vals, val) 47 | if fastrand.Intn(100)+1 <= n { 48 | z.Add(fastrand.Float64(), val) 49 | } 50 | } 51 | b.ResetTimer() 52 | b.RunParallel(func(pb *testing.PB) { 53 | for pb.Next() { 54 | _ = z.Contains(vals[fastrand.Intn(initsize)]) 55 | } 56 | }) 57 | }) 58 | } 59 | 60 | func BenchmarkAdd(b *testing.B) { 61 | benchmarkNAddNIncrNRemoveNContains(b, 100, 0, 0, 0) 62 | } 63 | 64 | func Benchmark1Add99Contains(b *testing.B) { 65 | benchmarkNAddNIncrNRemoveNContains(b, 1, 0, 0, 99) 66 | } 67 | 68 | func Benchmark10Add90Contains(b *testing.B) { 69 | benchmarkNAddNIncrNRemoveNContains(b, 10, 0, 0, 90) 70 | } 71 | 72 | func Benchmark50Add50Contains(b *testing.B) { 73 | benchmarkNAddNIncrNRemoveNContains(b, 50, 0, 0, 50) 74 | } 75 | 76 | func Benchmark1Add3Incr6Remove90Contains(b *testing.B) { 77 | benchmarkNAddNIncrNRemoveNContains(b, 1, 3, 6, 90) 78 | } 79 | 80 | func benchmarkNAddNIncrNRemoveNContains(b *testing.B, nAdd, nIncr, nRemove, nContains int) { 81 | // var anAdd, anIncr, anRemove, anContains int 82 | 83 | b.Run("sortedset", func(b *testing.B) { 84 | z := NewFloat64() 85 | var vals []string 86 | var scores []float64 87 | var ops []int 88 | for i := 0; i < initsize; i++ { 89 | vals = append(vals, strconv.Itoa(fastrand.Intn(randN))) 90 | scores = append(scores, fastrand.Float64()) 91 | ops = append(ops, fastrand.Intn(100)) 92 | } 93 | b.ResetTimer() 94 | b.RunParallel(func(pb *testing.PB) { 95 | for pb.Next() { 96 | r := fastrand.Intn(initsize) 97 | val := vals[r] 98 | if u := ops[r] + 1; u <= nAdd { 99 | // anAdd++ 100 | z.Add(scores[r], val) 101 | } else if u-nAdd <= nIncr { 102 | // anIncr++ 103 | z.IncrBy(scores[r], val) 104 | } else if u-nAdd-nIncr <= nRemove { 105 | // anRemove++ 106 | z.Remove(val) 107 | } else if u-nAdd-nIncr-nRemove <= nContains { 108 | // anContains++ 109 | z.Contains(val) 110 | } 111 | } 112 | }) 113 | // b.Logf("N: %d, Add: %f, Incr: %f, Remove: %f, Contains: %f", b.N, float64(anAdd)/float64(b.N), float64(anIncr)/float64(b.N), float64(anRemove)/float64(b.N), float64(anContains)/float64(b.N)) 114 | }) 115 | } 116 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bytedance/gopkg 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/stretchr/testify v1.9.0 7 | golang.org/x/net v0.24.0 8 | golang.org/x/sync v0.8.0 9 | golang.org/x/sys v0.19.0 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | golang.org/x/text v0.14.0 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 8 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 9 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 10 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 11 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 12 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 13 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 14 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 18 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 19 | -------------------------------------------------------------------------------- /internal/benchmark/linkedq/linkedq.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package linkedq 16 | 17 | import "sync" 18 | 19 | type LinkedQueue struct { 20 | head *linkedqueueNode 21 | tail *linkedqueueNode 22 | mu sync.Mutex 23 | } 24 | 25 | type linkedqueueNode struct { 26 | value uint64 27 | next *linkedqueueNode 28 | } 29 | 30 | func New() *LinkedQueue { 31 | node := new(linkedqueueNode) 32 | return &LinkedQueue{head: node, tail: node} 33 | } 34 | 35 | func (q *LinkedQueue) Enqueue(value uint64) bool { 36 | q.mu.Lock() 37 | q.tail.next = &linkedqueueNode{value: value} 38 | q.tail = q.tail.next 39 | q.mu.Unlock() 40 | return true 41 | } 42 | 43 | func (q *LinkedQueue) Dequeue() (uint64, bool) { 44 | q.mu.Lock() 45 | if q.head.next == nil { 46 | q.mu.Unlock() 47 | return 0, false 48 | } else { 49 | value := q.head.next.value 50 | q.head = q.head.next 51 | q.mu.Unlock() 52 | return value, true 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /internal/benchmark/msq/msq.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package msq 16 | 17 | import ( 18 | "sync" 19 | "sync/atomic" 20 | "unsafe" 21 | ) 22 | 23 | var msqv1pool *sync.Pool = &sync.Pool{New: func() interface{} { return new(msqv1node) }} 24 | 25 | type MSQueue struct { 26 | head unsafe.Pointer // *msqv1node 27 | tail unsafe.Pointer // *msqv1node 28 | } 29 | 30 | type msqv1node struct { 31 | value uint64 32 | next unsafe.Pointer // *msqv1node 33 | } 34 | 35 | func New() *MSQueue { 36 | node := unsafe.Pointer(new(msqv1node)) 37 | return &MSQueue{head: node, tail: node} 38 | } 39 | 40 | func loadMSQPointer(p *unsafe.Pointer) *msqv1node { 41 | return (*msqv1node)(atomic.LoadPointer(p)) 42 | } 43 | 44 | func (q *MSQueue) Enqueue(value uint64) bool { 45 | node := &msqv1node{value: value} 46 | for { 47 | tail := atomic.LoadPointer(&q.tail) 48 | tailstruct := (*msqv1node)(tail) 49 | next := atomic.LoadPointer(&tailstruct.next) 50 | if tail == atomic.LoadPointer(&q.tail) { 51 | if next == nil { 52 | // tail.next is empty, inset new node 53 | if atomic.CompareAndSwapPointer(&tailstruct.next, next, unsafe.Pointer(node)) { 54 | atomic.CompareAndSwapPointer(&q.tail, tail, unsafe.Pointer(node)) 55 | break 56 | } 57 | } else { 58 | atomic.CompareAndSwapPointer(&q.tail, tail, next) 59 | } 60 | } 61 | } 62 | return true 63 | } 64 | 65 | func (q *MSQueue) Dequeue() (value uint64, ok bool) { 66 | for { 67 | head := atomic.LoadPointer(&q.head) 68 | tail := atomic.LoadPointer(&q.tail) 69 | headstruct := (*msqv1node)(head) 70 | next := atomic.LoadPointer(&headstruct.next) 71 | if head == atomic.LoadPointer(&q.head) { 72 | if head == tail { 73 | if next == nil { 74 | return 0, false 75 | } 76 | atomic.CompareAndSwapPointer(&q.tail, tail, next) 77 | } else { 78 | value = ((*msqv1node)(next)).value 79 | if atomic.CompareAndSwapPointer(&q.head, head, next) { 80 | return value, true 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /internal/hack/hack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package hack 16 | 17 | import ( 18 | "reflect" 19 | "unsafe" 20 | ) 21 | 22 | // StringToBytes converts a string to a byte slice. 23 | // 24 | // This is a shallow copy, means that the returned byte slice reuse 25 | // the underlying array in string, so you can't change the returned 26 | // byte slice in any situations. 27 | func StringToBytes(s string) (b []byte) { 28 | bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) 29 | sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) 30 | bh.Data = sh.Data 31 | bh.Len = sh.Len 32 | bh.Cap = sh.Len 33 | return b 34 | } 35 | 36 | // BytesToString converts a byte slice to a string. 37 | // 38 | // This is a shallow copy, means that the returned string reuse the 39 | // underlying array in byte slice, it's your responsibility to keep 40 | // the input byte slice survive until you don't access the string anymore. 41 | func BytesToString(b []byte) string { 42 | return *(*string)(unsafe.Pointer(&b)) 43 | } 44 | -------------------------------------------------------------------------------- /internal/runtimex/asm.s: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // The runtime package uses //go:linkname to push a few functions into this 16 | // package but we still need a .s file so the Go tool does not pass -complete 17 | // to the go tool compile so the latter does not complain about Go functions 18 | // with no bodies. 19 | // See https://github.com/golang/go/issues/23311 -------------------------------------------------------------------------------- /internal/runtimex/ppin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtimex 16 | 17 | import ( 18 | _ "unsafe" 19 | ) 20 | 21 | //go:noescape 22 | //go:linkname runtime_procPin runtime.procPin 23 | func runtime_procPin() int 24 | 25 | //go:noescape 26 | //go:linkname runtime_procUnpin runtime.procUnpin 27 | func runtime_procUnpin() 28 | 29 | // Pin pins current p, return pid. 30 | // DO NOT USE if you don't know what this is. 31 | func Pin() int { 32 | return runtime_procPin() 33 | } 34 | 35 | // Unpin unpins current p. 36 | func Unpin() { 37 | runtime_procUnpin() 38 | } 39 | 40 | // Pid returns the id of current p. 41 | func Pid() (id int) { 42 | id = runtime_procPin() 43 | runtime_procUnpin() 44 | return 45 | } 46 | -------------------------------------------------------------------------------- /internal/runtimex/ppin_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtimex 16 | 17 | import ( 18 | "runtime" 19 | "sync" 20 | "testing" 21 | ) 22 | 23 | func TestPin(t *testing.T) { 24 | n := Pin() 25 | Unpin() 26 | t.Log(n) 27 | var wg sync.WaitGroup 28 | for i := 0; i < runtime.GOMAXPROCS(0); i++ { 29 | wg.Add(1) 30 | go func() { 31 | n := Pin() 32 | Unpin() 33 | t.Log(n) 34 | wg.Done() 35 | }() 36 | } 37 | wg.Wait() 38 | } 39 | -------------------------------------------------------------------------------- /internal/runtimex/readunaligned.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build 386 || amd64 || arm || arm64 || ppc64le || mips64le || mipsle || riscv64 || wasm || loong64 16 | // +build 386 amd64 arm arm64 ppc64le mips64le mipsle riscv64 wasm loong64 17 | 18 | // 19 | // from golang-go/src/os/endian_little.go 20 | 21 | package runtimex 22 | 23 | import ( 24 | "unsafe" 25 | ) 26 | 27 | func ReadUnaligned64(p unsafe.Pointer) uint64 { 28 | // Equal to runtime.readUnaligned64, but this function can be inlined 29 | // compared to use runtime.readUnaligned64 via go:linkname. 30 | q := (*[8]byte)(p) 31 | return uint64(q[0]) | uint64(q[1])<<8 | uint64(q[2])<<16 | uint64(q[3])<<24 | uint64(q[4])<<32 | uint64(q[5])<<40 | uint64(q[6])<<48 | uint64(q[7])<<56 32 | } 33 | 34 | func ReadUnaligned32(p unsafe.Pointer) uint64 { 35 | q := (*[4]byte)(p) 36 | return uint64(uint32(q[0]) | uint32(q[1])<<8 | uint32(q[2])<<16 | uint32(q[3])<<24) 37 | } 38 | 39 | func ReadUnaligned16(p unsafe.Pointer) uint64 { 40 | q := (*[2]byte)(p) 41 | return uint64(uint32(q[0]) | uint32(q[1])<<8) 42 | } 43 | -------------------------------------------------------------------------------- /internal/runtimex/readunaligned_bigendian.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build ppc64 || s390x || mips || mips64 16 | // +build ppc64 s390x mips mips64 17 | 18 | // 19 | // from golang-go/src/os/endian_big.go 20 | 21 | package runtimex 22 | 23 | import ( 24 | "unsafe" 25 | ) 26 | 27 | func ReadUnaligned64(p unsafe.Pointer) uint64 { 28 | // Equal to runtime.readUnaligned64, but this function can be inlined 29 | // compared to use runtime.readUnaligned64 via go:linkname. 30 | q := (*[8]byte)(p) 31 | return uint64(q[7]) | uint64(q[6])<<8 | uint64(q[5])<<16 | uint64(q[4])<<24 | 32 | uint64(q[3])<<32 | uint64(q[2])<<40 | uint64(q[1])<<48 | uint64(q[0])<<56 33 | } 34 | 35 | func ReadUnaligned32(p unsafe.Pointer) uint64 { 36 | q := (*[4]byte)(p) 37 | return uint64(uint32(q[3]) | uint32(q[2])<<8 | uint32(q[1])<<16 | uint32(q[0])<<24) 38 | } 39 | 40 | func ReadUnaligned16(p unsafe.Pointer) uint64 { 41 | q := (*[2]byte)(p) 42 | return uint64(uint32(q[1]) | uint32(q[0])<<8) 43 | } 44 | -------------------------------------------------------------------------------- /internal/runtimex/runtime_go_1.22.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build go1.22 16 | // +build go1.22 17 | 18 | package runtimex 19 | 20 | import ( 21 | _ "unsafe" // for linkname 22 | ) 23 | 24 | //go:linkname Fastrand runtime.cheaprand 25 | func Fastrand() uint32 26 | -------------------------------------------------------------------------------- /internal/runtimex/runtime_pre_go_1.22.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !go1.22 16 | // +build !go1.22 17 | 18 | package runtimex 19 | 20 | import ( 21 | _ "unsafe" // for linkname 22 | ) 23 | 24 | //go:linkname Fastrand runtime.fastrand 25 | func Fastrand() uint32 26 | -------------------------------------------------------------------------------- /internal/runtimex/runtime_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package runtimex 16 | 17 | import "testing" 18 | 19 | func TestFastrand(t *testing.T) { 20 | var isRandom bool 21 | for i := 0; i < 3; i++ { 22 | x := Fastrand() 23 | y := Fastrand() 24 | if x != y { 25 | isRandom = true 26 | break 27 | } 28 | } 29 | if !isRandom { 30 | t.Fatal("Invalid fastrand results") 31 | } 32 | } 33 | 34 | func BenchmarkFastrand(b *testing.B) { 35 | for i := 0; i < b.N; i++ { 36 | Fastrand() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /internal/wyhash/digest_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package wyhash 16 | 17 | import ( 18 | "fmt" 19 | "math/rand" 20 | "runtime" 21 | "testing" 22 | 23 | _ "unsafe" // for linkname 24 | ) 25 | 26 | func TestDigest(t *testing.T) { 27 | d := NewDefault() 28 | for size := 0; size <= 1024; size++ { 29 | data := make([]byte, size) 30 | for i := range data { 31 | data[i] = byte(rand.Int31n(256)) 32 | } 33 | // Random write small data. 34 | var r int 35 | if size == 0 { 36 | r = 0 37 | } else { 38 | r = int(rand.Int31n(int32(len(data)))) 39 | } 40 | d.Write(data[:r]) 41 | d.Write(data[r:]) 42 | if d.Sum64() != Sum64(data) { 43 | t.Fatal(size, d.Sum64(), Sum64(data)) 44 | } 45 | d.Reset() 46 | } 47 | 48 | largedata := make([]byte, 10*1024) 49 | for i := range largedata { 50 | largedata[i] = byte(rand.Int31n(256)) 51 | } 52 | 53 | var a, b int 54 | digest := NewDefault() 55 | partsizelimit := 300 56 | for { 57 | if len(largedata)-a < 300 { 58 | b = len(largedata) - a 59 | } else { 60 | b = int(rand.Int31n(int32(partsizelimit))) 61 | } 62 | digest.Write(largedata[a : a+b]) 63 | if Sum64(largedata[:a+b]) != digest.Sum64() { 64 | t.Fatal(a, b) 65 | } 66 | a += b 67 | if a == len(largedata) { 68 | break 69 | } 70 | } 71 | } 72 | 73 | func BenchmarkDigest(b *testing.B) { 74 | sizes := []int{33, 64, 96, 128, 129, 240, 241, 75 | 512, 1024, 10 * 1024, 76 | } 77 | 78 | for _, size := range sizes { 79 | b.Run(fmt.Sprintf("%d", size), func(b *testing.B) { 80 | b.SetBytes(int64(size)) 81 | var acc uint64 82 | data := make([]byte, size) 83 | b.ReportAllocs() 84 | b.ResetTimer() 85 | 86 | for i := 0; i < b.N; i++ { 87 | d := NewDefault() 88 | d.Write(data) 89 | acc = d.Sum64() 90 | } 91 | runtime.KeepAlive(acc) 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /internal/wyhash/wyhash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package wyhash implements https://github.com/wangyi-fudan/wyhash 16 | package wyhash 17 | 18 | import ( 19 | "math/bits" 20 | "unsafe" 21 | 22 | "github.com/bytedance/gopkg/internal/hack" 23 | "github.com/bytedance/gopkg/internal/runtimex" 24 | ) 25 | 26 | const ( 27 | DefaultSeed = 0xa0761d6478bd642f // s0 28 | s1 = 0xe7037ed1a0b428db 29 | s2 = 0x8ebc6af09c88c6e3 30 | s3 = 0x589965cc75374cc3 31 | s4 = 0x1d8e4e27c47d124f 32 | ) 33 | 34 | func _wymix(a, b uint64) uint64 { 35 | hi, lo := bits.Mul64(a, b) 36 | return hi ^ lo 37 | } 38 | 39 | //go:nosplit 40 | func add(p unsafe.Pointer, x uintptr) unsafe.Pointer { 41 | return unsafe.Pointer(uintptr(p) + x) 42 | } 43 | 44 | func Sum64(data []byte) uint64 { 45 | return Sum64WithSeed(data, DefaultSeed) 46 | } 47 | 48 | func Sum64String(data string) uint64 { 49 | return Sum64StringWithSeed(data, DefaultSeed) 50 | } 51 | 52 | func Sum64WithSeed(data []byte, seed uint64) uint64 { 53 | return Sum64StringWithSeed(hack.BytesToString(data), seed) 54 | } 55 | 56 | func Sum64StringWithSeed(data string, seed uint64) uint64 { 57 | var ( 58 | a, b uint64 59 | ) 60 | 61 | length := len(data) 62 | i := uintptr(len(data)) 63 | paddr := *(*unsafe.Pointer)(unsafe.Pointer(&data)) 64 | 65 | if i > 64 { 66 | var see1 = seed 67 | for i > 64 { 68 | seed = _wymix(runtimex.ReadUnaligned64(paddr)^s1, runtimex.ReadUnaligned64(add(paddr, 8))^seed) ^ _wymix(runtimex.ReadUnaligned64(add(paddr, 16))^s2, runtimex.ReadUnaligned64(add(paddr, 24))^seed) 69 | see1 = _wymix(runtimex.ReadUnaligned64(add(paddr, 32))^s3, runtimex.ReadUnaligned64(add(paddr, 40))^see1) ^ _wymix(runtimex.ReadUnaligned64(add(paddr, 48))^s4, runtimex.ReadUnaligned64(add(paddr, 56))^see1) 70 | paddr = add(paddr, 64) 71 | i -= 64 72 | } 73 | seed ^= see1 74 | } 75 | 76 | for i > 16 { 77 | seed = _wymix(runtimex.ReadUnaligned64(paddr)^s1, runtimex.ReadUnaligned64(add(paddr, 8))^seed) 78 | paddr = add(paddr, 16) 79 | i -= 16 80 | } 81 | 82 | // i <= 16 83 | switch { 84 | case i == 0: 85 | return _wymix(s1, _wymix(s1, seed)) 86 | case i < 4: 87 | a = uint64(*(*byte)(paddr))<<16 | uint64(*(*byte)(add(paddr, uintptr(i>>1))))<<8 | uint64(*(*byte)(add(paddr, uintptr(i-1)))) 88 | // b = 0 89 | return _wymix(s1^uint64(length), _wymix(a^s1, seed)) 90 | case i == 4: 91 | a = runtimex.ReadUnaligned32(paddr) 92 | // b = 0 93 | return _wymix(s1^uint64(length), _wymix(a^s1, seed)) 94 | case i < 8: 95 | a = runtimex.ReadUnaligned32(paddr) 96 | b = runtimex.ReadUnaligned32(add(paddr, i-4)) 97 | return _wymix(s1^uint64(length), _wymix(a^s1, b^seed)) 98 | case i == 8: 99 | a = runtimex.ReadUnaligned64(paddr) 100 | // b = 0 101 | return _wymix(s1^uint64(length), _wymix(a^s1, seed)) 102 | default: // 8 < i <= 16 103 | a = runtimex.ReadUnaligned64(paddr) 104 | b = runtimex.ReadUnaligned64(add(paddr, i-8)) 105 | return _wymix(s1^uint64(length), _wymix(a^s1, b^seed)) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /internal/wyhash/wyhash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package wyhash 16 | 17 | import ( 18 | "fmt" 19 | "runtime" 20 | "testing" 21 | ) 22 | 23 | func BenchmarkWyhash(b *testing.B) { 24 | sizes := []int{17, 21, 24, 29, 32, 25 | 33, 64, 69, 96, 97, 128, 129, 240, 241, 26 | 512, 1024, 100 * 1024, 27 | } 28 | 29 | for size := 0; size <= 16; size++ { 30 | b.Run(fmt.Sprintf("%d", size), func(b *testing.B) { 31 | b.SetBytes(int64(size)) 32 | var ( 33 | x uint64 34 | data = string(make([]byte, size)) 35 | ) 36 | b.ReportAllocs() 37 | b.ResetTimer() 38 | 39 | for i := 0; i < b.N; i++ { 40 | x = Sum64String(data) 41 | } 42 | runtime.KeepAlive(x) 43 | }) 44 | } 45 | 46 | for _, size := range sizes { 47 | b.Run(fmt.Sprintf("%d", size), func(b *testing.B) { 48 | b.SetBytes(int64(size)) 49 | var x uint64 50 | data := string(make([]byte, size)) 51 | b.ReportAllocs() 52 | b.ResetTimer() 53 | 54 | for i := 0; i < b.N; i++ { 55 | x = Sum64String(data) 56 | } 57 | runtime.KeepAlive(x) 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /lang/channel/channel_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package channel 16 | 17 | import ( 18 | "runtime" 19 | "sync/atomic" 20 | "testing" 21 | "time" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | type request struct { 27 | Id int 28 | Latency time.Duration 29 | Done chan struct{} 30 | } 31 | 32 | type response struct { 33 | Id int 34 | } 35 | 36 | var taskPool Channel 37 | 38 | func Service1(req *request) { 39 | taskPool.Input(req) 40 | return 41 | } 42 | 43 | func Service2(req *request) (*response, error) { 44 | if req.Latency > 0 { 45 | time.Sleep(req.Latency) 46 | } 47 | return &response{Id: req.Id}, nil 48 | } 49 | 50 | func TestNetworkIsolationOrDownstreamBlock(t *testing.T) { 51 | taskPool = New( 52 | WithNonBlock(), 53 | WithTimeout(time.Millisecond*10), 54 | ) 55 | defer taskPool.Close() 56 | var responded int32 57 | go func() { 58 | // task worker 59 | for task := range taskPool.Output() { 60 | req := task.(*request) 61 | done := make(chan struct{}) 62 | go func() { 63 | _, _ = Service2(req) 64 | close(done) 65 | }() 66 | select { 67 | case <-time.After(time.Millisecond * 100): 68 | case <-done: 69 | atomic.AddInt32(&responded, 1) 70 | } 71 | } 72 | }() 73 | 74 | start := time.Now() 75 | for i := 1; i <= 100; i++ { 76 | req := &request{Id: i} 77 | if i > 50 && i <= 60 { // suddenly have network issue for 10 requests 78 | req.Latency = time.Hour 79 | } 80 | Service1(req) 81 | } 82 | cost := time.Now().Sub(start) 83 | assert.True(t, cost < time.Millisecond*10) // Service1 should not block 84 | time.Sleep(time.Millisecond * 1500) // wait all tasks finished 85 | assert.Equal(t, int32(50), atomic.LoadInt32(&responded)) // 50 success and 10 timeout and 40 discard 86 | } 87 | 88 | func TestCPUHeavy(t *testing.T) { 89 | runtime.GOMAXPROCS(1) 90 | var concurrency int32 91 | taskPool = New( 92 | WithNonBlock(), 93 | WithThrottle(nil, func(c Channel) bool { 94 | return atomic.LoadInt32(&concurrency) > 10 95 | }), 96 | ) 97 | defer taskPool.Close() 98 | var responded int32 99 | go func() { 100 | // task worker 101 | for task := range taskPool.Output() { 102 | req := task.(*request) 103 | t.Logf("NumGoroutine: %d", runtime.NumGoroutine()) 104 | go func() { 105 | curConcurrency := atomic.AddInt32(&concurrency, 1) 106 | defer atomic.AddInt32(&concurrency, -1) 107 | if curConcurrency > 10 { 108 | // concurrency too high, reuqest faild 109 | return 110 | } 111 | 112 | atomic.AddInt32(&responded, 1) 113 | if req.Id >= 11 && req.Id <= 20 { 114 | start := time.Now() 115 | for x := uint64(0); ; x++ { 116 | if x%1000 == 0 { 117 | if time.Now().Sub(start) >= 100*time.Millisecond { 118 | return 119 | } 120 | } 121 | } 122 | } 123 | }() 124 | } 125 | }() 126 | 127 | start := time.Now() 128 | for i := 1; i <= 100; i++ { 129 | req := &request{Id: i} 130 | Service1(req) 131 | } 132 | cost := time.Now().Sub(start) 133 | assert.True(t, cost < time.Millisecond*10) // Service1 should not block 134 | time.Sleep(time.Second * 2) // wait all tasks finished 135 | t.Logf("responded: %d", atomic.LoadInt32(&responded)) // most tasks success 136 | } 137 | -------------------------------------------------------------------------------- /lang/dirtmake/bytes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dirtmake 16 | 17 | import ( 18 | "unsafe" 19 | ) 20 | 21 | type slice struct { 22 | data unsafe.Pointer 23 | len int 24 | cap int 25 | } 26 | 27 | //go:linkname mallocgc runtime.mallocgc 28 | func mallocgc(size uintptr, typ unsafe.Pointer, needzero bool) unsafe.Pointer 29 | 30 | // Bytes allocates a byte slice but does not clean up the memory it references. 31 | // Throw a fatal error instead of panic if cap is greater than runtime.maxAlloc. 32 | // NOTE: MUST set any byte element before it's read. 33 | func Bytes(len, cap int) (b []byte) { 34 | if len < 0 || len > cap { 35 | panic("dirtmake.Bytes: len out of range") 36 | } 37 | p := mallocgc(uintptr(cap), nil, false) 38 | sh := (*slice)(unsafe.Pointer(&b)) 39 | sh.data = p 40 | sh.len = len 41 | sh.cap = cap 42 | return 43 | } 44 | -------------------------------------------------------------------------------- /lang/dirtmake/bytes_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package dirtmake 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "testing" 21 | ) 22 | 23 | var data []byte 24 | 25 | const block1kb = 1024 26 | 27 | func BenchmarkParallelDirtBytes(b *testing.B) { 28 | src := make([]byte, block1kb) 29 | for i := range src { 30 | src[i] = byte(i) 31 | } 32 | b.RunParallel(func(pb *testing.PB) { 33 | for pb.Next() { 34 | bs := Bytes(block1kb, block1kb) 35 | copy(bs, src) 36 | if !bytes.Equal(bs, src) { 37 | b.Fatalf("bytes not equal") 38 | } 39 | } 40 | }) 41 | } 42 | 43 | func BenchmarkDirtBytes(b *testing.B) { 44 | for size := block1kb; size < block1kb*20; size += block1kb * 2 { 45 | b.Run(fmt.Sprintf("size=%dkb", size/block1kb), func(b *testing.B) { 46 | for i := 0; i < b.N; i++ { 47 | data = Bytes(size, size) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func BenchmarkOriginBytes(b *testing.B) { 54 | for size := block1kb; size < block1kb*20; size += block1kb * 2 { 55 | b.Run(fmt.Sprintf("size=%dkb", size/block1kb), func(b *testing.B) { 56 | for i := 0; i < b.N; i++ { 57 | data = make([]byte, size) 58 | } 59 | }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lang/fastrand/fastrand_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package fastrand 16 | 17 | import ( 18 | "math/rand" 19 | "testing" 20 | ) 21 | 22 | func TestAll(t *testing.T) { 23 | _ = Uint32() 24 | 25 | p := make([]byte, 1000) 26 | n, err := Read(p) 27 | if n != len(p) || err != nil || (p[0] == 0 && p[1] == 0 && p[2] == 0) { 28 | t.Fatal() 29 | } 30 | 31 | a := Perm(100) 32 | for i := range a { 33 | var find bool 34 | for _, v := range a { 35 | if v == i { 36 | find = true 37 | } 38 | } 39 | if !find { 40 | t.Fatal() 41 | } 42 | } 43 | 44 | Shuffle(len(a), func(i, j int) { 45 | a[i], a[j] = a[j], a[i] 46 | }) 47 | for i := range a { 48 | var find bool 49 | for _, v := range a { 50 | if v == i { 51 | find = true 52 | } 53 | } 54 | if !find { 55 | t.Fatal() 56 | } 57 | } 58 | } 59 | 60 | func BenchmarkSingleCore(b *testing.B) { 61 | b.Run("math/rand-Uint32()", func(b *testing.B) { 62 | for i := 0; i < b.N; i++ { 63 | _ = rand.Uint32() 64 | } 65 | }) 66 | b.Run("fast-rand-Uint32()", func(b *testing.B) { 67 | for i := 0; i < b.N; i++ { 68 | _ = Uint32() 69 | } 70 | }) 71 | 72 | b.Run("math/rand-Uint64()", func(b *testing.B) { 73 | for i := 0; i < b.N; i++ { 74 | _ = rand.Uint64() 75 | } 76 | }) 77 | b.Run("fast-rand-Uint64()", func(b *testing.B) { 78 | for i := 0; i < b.N; i++ { 79 | _ = Uint64() 80 | } 81 | }) 82 | 83 | b.Run("math/rand-Read1000", func(b *testing.B) { 84 | p := make([]byte, 1000) 85 | b.ResetTimer() 86 | for i := 0; i < b.N; i++ { 87 | rand.Read(p) 88 | } 89 | }) 90 | b.Run("fast-rand-Read1000", func(b *testing.B) { 91 | p := make([]byte, 1000) 92 | b.ResetTimer() 93 | for i := 0; i < b.N; i++ { 94 | Read(p) 95 | } 96 | }) 97 | 98 | } 99 | 100 | func BenchmarkMultipleCore(b *testing.B) { 101 | b.Run("math/rand-Uint32()", func(b *testing.B) { 102 | b.RunParallel(func(pb *testing.PB) { 103 | for pb.Next() { 104 | _ = rand.Uint32() 105 | } 106 | }) 107 | }) 108 | b.Run("fast-rand-Uint32()", func(b *testing.B) { 109 | b.RunParallel(func(pb *testing.PB) { 110 | for pb.Next() { 111 | _ = Uint32() 112 | } 113 | }) 114 | }) 115 | 116 | b.Run("math/rand-Uint64()", func(b *testing.B) { 117 | b.RunParallel(func(pb *testing.PB) { 118 | for pb.Next() { 119 | _ = rand.Uint64() 120 | } 121 | }) 122 | }) 123 | b.Run("fast-rand-Uint64()", func(b *testing.B) { 124 | b.RunParallel(func(pb *testing.PB) { 125 | for pb.Next() { 126 | _ = Uint64() 127 | } 128 | }) 129 | }) 130 | 131 | b.Run("math/rand-Read1000", func(b *testing.B) { 132 | p := make([]byte, 1000) 133 | b.ResetTimer() 134 | b.RunParallel(func(pb *testing.PB) { 135 | for pb.Next() { 136 | rand.Read(p) 137 | } 138 | }) 139 | }) 140 | b.Run("fast-rand-Read1000", func(b *testing.B) { 141 | p := make([]byte, 1000) 142 | b.ResetTimer() 143 | b.RunParallel(func(pb *testing.PB) { 144 | for pb.Next() { 145 | Read(p) 146 | } 147 | }) 148 | }) 149 | } 150 | -------------------------------------------------------------------------------- /lang/fastrand/readme.md: -------------------------------------------------------------------------------- 1 | # fastrand 2 | 3 | Fastest pseudo-random number generator in Go. 4 | 5 | This generator actually come from Go runtime per-M structure, and the init-seed is provided by Go runtime, which means you can't add your own seed, but these methods scales very well on multiple cores. 6 | 7 | The generator passes the SmallCrush suite, part of TestU01 framework: http://simul.iro.umontreal.ca/testu01/tu01.html 8 | 9 | pseudo-random paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf 10 | 11 | fast-modulo-reduction: https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ 12 | 13 | 14 | 15 | ## Compare to math/rand 16 | 17 | - Much faster (~5x faster for single-core; ~200x faster for multiple-cores) 18 | - Scales well on multiple cores 19 | - **Not** provide a stable value stream (can't inject init-seed) 20 | - Fix bugs in math/rand `Float64` and `Float32` (since no need to preserve the value stream) 21 | 22 | 23 | 24 | ## Bencmark 25 | 26 | Go version: go1.15.6 linux/amd64 27 | 28 | CPU: AMD 3700x(8C16T), running at 3.6GHz 29 | 30 | OS: ubuntu 18.04 31 | 32 | MEMORY: 16G x 2 (3200MHz) 33 | 34 | 35 | 36 | ##### multiple-cores 37 | 38 | ``` 39 | name time/op 40 | MultipleCore/math/rand-Int31n(5)-16 152ns ± 0% 41 | MultipleCore/fast-rand-Int31n(5)-16 0.40ns ± 0% 42 | MultipleCore/math/rand-Int63n(5)-16 167ns ± 0% 43 | MultipleCore/fast-rand-Int63n(5)-16 3.04ns ± 0% 44 | MultipleCore/math/rand-Float32()-16 143ns ± 0% 45 | MultipleCore/fast-rand-Float32()-16 0.40ns ± 0% 46 | MultipleCore/math/rand-Uint32()-16 133ns ± 0% 47 | MultipleCore/fast-rand-Uint32()-16 0.23ns ± 0% 48 | MultipleCore/math/rand-Uint64()-16 140ns ± 0% 49 | MultipleCore/fast-rand-Uint64()-16 0.56ns ± 0% 50 | ``` 51 | 52 | 53 | 54 | ##### single-core 55 | 56 | ``` 57 | name time/op 58 | SingleCore/math/rand-Int31n(5)-16 15.5ns ± 0% 59 | SingleCore/fast-rand-Int31n(5)-16 3.91ns ± 0% 60 | SingleCore/math/rand-Int63n(5)-16 24.4ns ± 0% 61 | SingleCore/fast-rand-Int63n(5)-16 24.4ns ± 0% 62 | SingleCore/math/rand-Uint32()-16 10.9ns ± 0% 63 | SingleCore/fast-rand-Uint32()-16 2.86ns ± 0% 64 | SingleCore/math/rand-Uint64()-16 11.3ns ± 0% 65 | SingleCore/fast-rand-Uint64()-16 5.88ns ± 0% 66 | ``` 67 | 68 | 69 | 70 | ##### compare to other repo 71 | 72 | (multiple-cores) 73 | 74 | rand-1 -> this repo 75 | 76 | rand-2 -> https://github.com/valyala/fastrand 77 | 78 | ``` 79 | name time/op 80 | VS/fastrand-1-Uint32()-16 0.23ns ± 0% 81 | VS/fastrand-2-Uint32()-16 3.04ns ±23% 82 | VS/fastrand-1-Uint32n(5)-16 0.23ns ± 1% 83 | VS/fastrand-2-Uint32n(5)-16 3.13ns ±32% 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /lang/mcache/README.md: -------------------------------------------------------------------------------- 1 | # mcache 2 | 3 | ## Introduction 4 | 5 | `mcache` is a memory pool which preserves memory in `sync.Pool` to improve malloc performance. 6 | 7 | The usage is quite simple: call `mcache.Malloc` directly, and don't forget to `Free` it! 8 | -------------------------------------------------------------------------------- /lang/mcache/mcache.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mcache 16 | 17 | import ( 18 | "sync" 19 | "unsafe" 20 | 21 | "github.com/bytedance/gopkg/lang/dirtmake" 22 | ) 23 | 24 | const maxSize = 46 25 | 26 | // index contains []byte which cap is 1<= cap. 60 | func Malloc(size int, capacity ...int) []byte { 61 | if len(capacity) > 1 { 62 | panic("too many arguments to Malloc") 63 | } 64 | var c = size 65 | if len(capacity) > 0 && capacity[0] > size { 66 | c = capacity[0] 67 | } 68 | 69 | i := calcIndex(c) 70 | 71 | ret := []byte{} 72 | h := (*bytesHeader)(unsafe.Pointer(&ret)) 73 | h.Len = size 74 | h.Cap = 1 << i 75 | h.Data = caches[i].Get().(*byte) 76 | return ret 77 | } 78 | 79 | // Free should be called when the buf is no longer used. 80 | func Free(buf []byte) { 81 | size := cap(buf) 82 | if !isPowerOfTwo(size) { 83 | return 84 | } 85 | h := (*bytesHeader)(unsafe.Pointer(&buf)) 86 | caches[bsr(size)].Put(h.Data) 87 | } 88 | -------------------------------------------------------------------------------- /lang/mcache/mcache_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mcache 16 | 17 | import "testing" 18 | 19 | func TestMalloc(t *testing.T) { 20 | buf := Malloc(4096) 21 | t.Log(cap(buf)) 22 | } 23 | 24 | func BenchmarkNormal4096(b *testing.B) { 25 | var buf []byte 26 | b.ReportAllocs() 27 | for i := 0; i < b.N; i++ { 28 | buf = make([]byte, 0, 4096) 29 | } 30 | _ = buf 31 | } 32 | 33 | func BenchmarkMCache4096(b *testing.B) { 34 | var buf []byte 35 | b.ReportAllocs() 36 | for i := 0; i < b.N; i++ { 37 | buf = Malloc(4096) 38 | Free(buf) 39 | } 40 | _ = buf 41 | } 42 | 43 | func BenchmarkNormal10M(b *testing.B) { 44 | var buf []byte 45 | b.ReportAllocs() 46 | for i := 0; i < b.N; i++ { 47 | buf = make([]byte, 0, 1024*1024*10) 48 | } 49 | _ = buf 50 | } 51 | 52 | func BenchmarkMCache10M(b *testing.B) { 53 | var buf []byte 54 | b.ReportAllocs() 55 | for i := 0; i < b.N; i++ { 56 | buf = Malloc(1024 * 1024 * 10) 57 | Free(buf) 58 | } 59 | _ = buf 60 | } 61 | 62 | func BenchmarkNormal4096Parallel(b *testing.B) { 63 | b.ReportAllocs() 64 | b.RunParallel(func(pb *testing.PB) { 65 | var buf []byte 66 | for pb.Next() { 67 | for i := 0; i < b.N; i++ { 68 | buf = make([]byte, 0, 4096) 69 | } 70 | } 71 | _ = buf 72 | }) 73 | } 74 | 75 | func BenchmarkMCache4096Parallel(b *testing.B) { 76 | b.ReportAllocs() 77 | b.RunParallel(func(pb *testing.PB) { 78 | var buf []byte 79 | for pb.Next() { 80 | for i := 0; i < b.N; i++ { 81 | buf = Malloc(4096) 82 | Free(buf) 83 | } 84 | } 85 | _ = buf 86 | }) 87 | } 88 | 89 | func BenchmarkNormal10MParallel(b *testing.B) { 90 | b.ReportAllocs() 91 | b.RunParallel(func(pb *testing.PB) { 92 | var buf []byte 93 | for pb.Next() { 94 | for i := 0; i < b.N; i++ { 95 | buf = make([]byte, 0, 1024*1024*10) 96 | } 97 | } 98 | _ = buf 99 | }) 100 | } 101 | 102 | func BenchmarkMCache10MParallel(b *testing.B) { 103 | b.ReportAllocs() 104 | b.RunParallel(func(pb *testing.PB) { 105 | var buf []byte 106 | for pb.Next() { 107 | for i := 0; i < b.N; i++ { 108 | buf = Malloc(1024 * 1024 * 10) 109 | Free(buf) 110 | } 111 | } 112 | _ = buf 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /lang/mcache/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mcache 16 | 17 | import "math/bits" 18 | 19 | func bsr(x int) int { 20 | return bits.Len(uint(x)) - 1 21 | } 22 | 23 | func isPowerOfTwo(x int) bool { 24 | return (x != 0) && ((x & (-x)) == x) 25 | } 26 | -------------------------------------------------------------------------------- /lang/mcache/utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mcache 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestBsr(t *testing.T) { 24 | assert.Equal(t, bsr(4), 2) 25 | assert.Equal(t, bsr(24), 4) 26 | assert.Equal(t, bsr((1<<10)-1), 9) 27 | assert.Equal(t, bsr((1<<30)+(1<<19)+(1<<16)+(1<<1)), 30) 28 | } 29 | 30 | func BenchmarkBsr(b *testing.B) { 31 | num := (1 << 30) + (1 << 19) + (1 << 16) + (1 << 1) 32 | 33 | b.ReportAllocs() 34 | b.ResetTimer() 35 | for i := 0; i < b.N; i++ { 36 | _ = bsr(num + i) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lang/span/span.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package span 16 | 17 | import ( 18 | "math/bits" 19 | "sync/atomic" 20 | 21 | "github.com/bytedance/gopkg/lang/dirtmake" 22 | ) 23 | 24 | /* Span Cache: A thread-safe linear allocator 25 | 26 | Design: 27 | 1. [GC Friendly]: Centralize a batch of small bytes slice into a big size bytes slice to avoid malloc too many objects 28 | 2. [Thread Safe]: Multi thread may share a same span, but it should fall back to the native allocator if lock conflict 29 | */ 30 | 31 | const ( 32 | spanCacheSize = 10 33 | minSpanObject = 128 // 128 B 34 | maxSpanObject = (minSpanObject << spanCacheSize) - 1 // 128 KB 35 | minSpanClass = 8 // = spanClass(minSpanObject) 36 | ) 37 | 38 | type spanCache struct { 39 | spans [spanCacheSize]*span 40 | } 41 | 42 | // NewSpanCache returns a spanCache with the given spanSize, 43 | // each span is used to allocate a binary of a specific size level. 44 | func NewSpanCache(spanSize int) *spanCache { 45 | c := new(spanCache) 46 | for i := 0; i < len(c.spans); i++ { 47 | c.spans[i] = NewSpan(spanSize) 48 | } 49 | return c 50 | } 51 | 52 | // Make returns a [:n:n] bytes slice from a cached buffer 53 | // NOTE: Make will not clear the underlay bytes for performance concern. So caller MUST set every byte before read. 54 | func (c *spanCache) Make(n int) []byte { 55 | sclass := spanClass(n) - minSpanClass 56 | if sclass < 0 || sclass >= len(c.spans) { 57 | return dirtmake.Bytes(n, n) 58 | } 59 | return c.spans[sclass].Make(n) 60 | } 61 | 62 | // Copy is an alias function for make-and-copy pattern 63 | func (c *spanCache) Copy(buf []byte) (p []byte) { 64 | p = c.Make(len(buf)) 65 | copy(p, buf) 66 | return p 67 | } 68 | 69 | // NewSpan returns a span with given size 70 | func NewSpan(size int) *span { 71 | sp := new(span) 72 | sp.size = uint32(size) 73 | sp.buffer = dirtmake.Bytes(0, size) 74 | return sp 75 | } 76 | 77 | type span struct { 78 | lock uint32 79 | read uint32 // read index of buffer 80 | size uint32 // size of buffer 81 | buffer []byte 82 | } 83 | 84 | // Make returns a [:n:n] bytes slice from a cached buffer 85 | // NOTE: Make will not clear the underlay bytes for performance concern. So caller MUST set every byte before read. 86 | func (b *span) Make(_n int) []byte { 87 | n := uint32(_n) 88 | if n >= b.size || !atomic.CompareAndSwapUint32(&b.lock, 0, 1) { 89 | // fallback path: make a new byte slice if current goroutine cannot get the lock or n is out of size 90 | return dirtmake.Bytes(int(n), int(n)) 91 | } 92 | START: 93 | b.read += n 94 | // fast path 95 | if b.read <= b.size { 96 | buf := b.buffer[b.read-n : b.read : b.read] 97 | atomic.StoreUint32(&b.lock, 0) 98 | return buf 99 | } 100 | // slow path: create a new buffer 101 | b.buffer = dirtmake.Bytes(int(b.size), int(b.size)) 102 | b.read = 0 103 | goto START 104 | } 105 | 106 | // Copy is an alias function for make-and-copy pattern 107 | func (b *span) Copy(buf []byte) (p []byte) { 108 | p = b.Make(len(buf)) 109 | copy(p, buf) 110 | return p 111 | } 112 | 113 | // spanClass calc the minimum number of bits required to represent x 114 | // [2^sclass,2^(sclass+1)) bytes in a same span class 115 | func spanClass(size int) int { 116 | if size == 0 { 117 | return 0 118 | } 119 | return bits.Len(uint(size)) 120 | } 121 | -------------------------------------------------------------------------------- /lang/stringx/README.md: -------------------------------------------------------------------------------- 1 | # stringx 2 | 3 | ## Introduction 4 | Extension/Helper of String Operation. 5 | 6 | ## Features 7 | - Transform(Reverse, Rotate, Shuffle ...) 8 | - Construction(Pad, Repeat...) 9 | - Matching(IsAlpha, IsAlphanumeric, IsNumeric ...) 10 | -------------------------------------------------------------------------------- /lang/stringx/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package stringx provides extensions of string Operation. 16 | package stringx 17 | -------------------------------------------------------------------------------- /lang/stringx/exmaple_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stringx 16 | 17 | import ( 18 | "fmt" 19 | "unicode/utf8" 20 | ) 21 | 22 | func Example_sub() { 23 | fmt.Printf("Sub-[0:100]=%s\n", Sub("", 0, 100)) 24 | fmt.Printf("Sub-facgbheidjk[3:9]=%s\n", Sub("facgbheidjk", 3, 9)) 25 | fmt.Printf("Sub-facgbheidjk[-50:100]=%s\n", Sub("facgbheidjk", -50, 100)) 26 | fmt.Printf("Sub-facgbheidjk[-3:length]=%s\n", Sub("facgbheidjk", -3, utf8.RuneCountInString("facgbheidjk"))) 27 | fmt.Printf("Sub-facgbheidjk[-3:-1]=%s\n", Sub("facgbheidjk", -3, -1)) 28 | fmt.Printf("Sub-zh英文hun排[2:5]=%s\n", Sub("zh英文hun排", 2, 5)) 29 | fmt.Printf("Sub-zh英文hun排[2:-1]=%s\n", Sub("zh英文hun排", 2, -1)) 30 | fmt.Printf("Sub-zh英文hun排[-100:-1]=%s\n", Sub("zh英文hun排", -100, -1)) 31 | fmt.Printf("Sub-zh英文hun排[-100:-90]=%s\n", Sub("zh英文hun排", -100, -90)) 32 | fmt.Printf("Sub-zh英文hun排[-10:-90]=%s\n", Sub("zh英文hun排", -10, -90)) 33 | 34 | // Output: 35 | // Sub-[0:100]= 36 | // Sub-facgbheidjk[3:9]=gbheid 37 | // Sub-facgbheidjk[-50:100]=facgbheidjk 38 | // Sub-facgbheidjk[-3:length]=djk 39 | // Sub-facgbheidjk[-3:-1]=dj 40 | // Sub-zh英文hun排[2:5]=英文h 41 | // Sub-zh英文hun排[2:-1]=英文hun 42 | // Sub-zh英文hun排[-100:-1]=zh英文hun 43 | // Sub-zh英文hun排[-100:-90]= 44 | // Sub-zh英文hun排[-10:-90]= 45 | } 46 | 47 | func Example_substart() { 48 | fmt.Printf("SubStart-[0:]=%s\n", SubStart("", 0)) 49 | fmt.Printf("SubStart-[2:]=%s\n", SubStart("", 2)) 50 | fmt.Printf("SubStart-facgbheidjk[3:]=%s\n", SubStart("facgbheidjk", 3)) 51 | fmt.Printf("SubStart-facgbheidjk[-50:]=%s\n", SubStart("facgbheidjk", -50)) 52 | fmt.Printf("SubStart-facgbheidjk[-3:]=%s\n", SubStart("facgbheidjk", -3)) 53 | fmt.Printf("SubStart-zh英文hun排[3:]=%s\n", SubStart("zh英文hun排", 3)) 54 | 55 | // Output: 56 | // SubStart-[0:]= 57 | // SubStart-[2:]= 58 | // SubStart-facgbheidjk[3:]=gbheidjk 59 | // SubStart-facgbheidjk[-50:]=facgbheidjk 60 | // SubStart-facgbheidjk[-3:]=djk 61 | // SubStart-zh英文hun排[3:]=文hun排 62 | } 63 | 64 | func Example_pad() { 65 | 66 | fmt.Printf("PadLeft=[%s]\n", PadLeftSpace("abc", 7)) 67 | fmt.Printf("PadLeft=[%s]\n", PadLeftChar("abc", 7, '-')) 68 | fmt.Printf("PadCenter=[%s]\n", PadCenterChar("abc", 7, '-')) 69 | fmt.Printf("PadCenter=[%s]\n", PadCenterChar("abcd", 7, '-')) 70 | 71 | // Output: 72 | // PadLeft=[ abc] 73 | // PadLeft=[----abc] 74 | // PadCenter=[--abc--] 75 | // PadCenter=[-abcd--] 76 | } 77 | -------------------------------------------------------------------------------- /lang/stringx/is.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stringx 16 | 17 | import ( 18 | "unicode" 19 | ) 20 | 21 | // IsAlpha checks if the string contains only unicode letters. 22 | func IsAlpha(s string) bool { 23 | if s == "" { 24 | return false 25 | } 26 | for _, v := range s { 27 | if !unicode.IsLetter(v) { 28 | return false 29 | } 30 | } 31 | return true 32 | } 33 | 34 | // IsAlphanumeric checks if the string contains only Unicode letters or digits. 35 | func IsAlphanumeric(s string) bool { 36 | if s == "" { 37 | return false 38 | } 39 | for _, v := range s { 40 | if !isAlphanumeric(v) { 41 | return false 42 | } 43 | } 44 | return true 45 | } 46 | 47 | // IsNumeric Checks if the string contains only digits. A decimal point is not a digit and returns false. 48 | func IsNumeric(s string) bool { 49 | if s == "" { 50 | return false 51 | } 52 | for _, v := range s { 53 | if !unicode.IsDigit(v) { 54 | return false 55 | } 56 | } 57 | return true 58 | } 59 | 60 | func isAlphanumeric(v rune) bool { 61 | return unicode.IsDigit(v) || unicode.IsLetter(v) 62 | } 63 | -------------------------------------------------------------------------------- /lang/stringx/is_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package stringx 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestIs(t *testing.T) { 24 | is := assert.New(t) 25 | 26 | is.False(IsNumeric("")) 27 | is.False(IsNumeric(" ")) 28 | is.False(IsNumeric(" bob ")) 29 | is.True(IsNumeric("123")) 30 | 31 | is.False(IsAlpha("")) 32 | is.False(IsAlpha(" ")) 33 | is.False(IsAlpha(" Voa ")) 34 | is.False(IsAlpha("123")) 35 | is.True(IsAlpha("Voa")) 36 | is.True(IsAlpha("bròwn")) 37 | 38 | is.False(IsAlphanumeric("")) 39 | is.False(IsAlphanumeric(" ")) 40 | is.False(IsAlphanumeric(" Voa ")) 41 | is.True(IsAlphanumeric("Voa")) 42 | is.True(IsAlphanumeric("123")) 43 | is.True(IsAlphanumeric("v123oa")) 44 | is.False(IsAlphanumeric("v123oa,")) 45 | } 46 | -------------------------------------------------------------------------------- /lang/syncx/README.md: -------------------------------------------------------------------------------- 1 | # syncx 2 | 3 | ## 1. syncx.Pool 4 | ### Defects of sync.Pool 5 | 1. Get/Put func is unbalanced on P. 6 | 1. Usually Get and Put are not fired on the same goroutine, which will result in a difference in the fired times of Get and Put on each P. The difference part will trigger the steal operation(using CAS). 7 | 2. poolLocal.private has only one. 8 | 1. Get/Put operations are very frequent. In most cases, Get/Put needs to operate poolLocal.shared (using CAS) instead of the lock-free poolLocal.private. 9 | 3. Frequent GC. 10 | 1. sync.Pool is designed to serve objects with a short life cycle, but we just want to reuse, don’t want frequent GC. 11 | 12 | ### Optimize of syncx.Pool 13 | 1. Transform poolLocal.private into an array to increase the frequency of poolLocal.private use. 14 | 2. Batch operation poolLocal.shared, reduce CAS calls. 15 | 3. Allow No GC. 16 | 17 | ### Not Recommended 18 | 1. A single object is too large. 19 | 1. syncx.Pool permanently holds up to runtime.GOMAXPROC(0)*256 reusable objects. 20 | 2. For example, under a 4-core docker, a 4KB object will cause about 4MB of memory usage. 21 | 3. please evaluate it yourself. 22 | 23 | ### Performance 24 | | benchmark | sync ns/op | syncx ns/op | delta | 25 | | :---------- | :-----------: | :-----------: | :-----------: | 26 | | BenchmarkSyncPoolParallel-4(p=16) | 877 | 620 | -29.30% | 27 | | BenchmarkSyncPoolParallel-4(p=1024) | 49385 | 33465 | -32.24% | 28 | | BenchmarkSyncPoolParallel-4(p=4096) | 255671 | 149522 | -41.52% | 29 | 30 | ### Example 31 | ```go 32 | var pool = &syncx.Pool{ 33 | New: func() interface{} { 34 | return &struct{} 35 | }, 36 | NoGC: true, 37 | } 38 | 39 | func getput() { 40 | var obj = pool.Get().(*struct) 41 | pool.Put(obj) 42 | } 43 | ``` 44 | 45 | ## 2. syncx.RWMutex 46 | -------------------------------------------------------------------------------- /lang/syncx/asm.s: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. -------------------------------------------------------------------------------- /lang/syncx/linkname.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !race 16 | // +build !race 17 | 18 | package syncx 19 | 20 | import ( 21 | _ "sync" 22 | _ "unsafe" 23 | ) 24 | 25 | //go:noescape 26 | //go:linkname runtime_registerPoolCleanup sync.runtime_registerPoolCleanup 27 | func runtime_registerPoolCleanup(cleanup func()) 28 | 29 | //go:noescape 30 | //go:linkname runtime_poolCleanup sync.poolCleanup 31 | func runtime_poolCleanup() 32 | -------------------------------------------------------------------------------- /lang/syncx/pool_race.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build race 16 | // +build race 17 | 18 | package syncx 19 | 20 | import ( 21 | "sync" 22 | ) 23 | 24 | type Pool struct { 25 | p sync.Pool 26 | once sync.Once 27 | // New optionally specifies a function to generate 28 | // a value when Get would otherwise return nil. 29 | // It may not be changed concurrently with calls to Get. 30 | New func() interface{} 31 | // NoGC any objects in this Pool. 32 | NoGC bool 33 | } 34 | 35 | func (p *Pool) init() { 36 | p.once.Do(func() { 37 | p.p = sync.Pool{ 38 | New: p.New, 39 | } 40 | }) 41 | } 42 | 43 | // Put adds x to the pool. 44 | func (p *Pool) Put(x interface{}) { 45 | p.init() 46 | p.p.Put(x) 47 | } 48 | 49 | // Get selects an arbitrary item from the Pool, removes it from the 50 | // Pool, and returns it to the caller. 51 | // Get may choose to ignore the pool and treat it as empty. 52 | // Callers should not assume any relation between values passed to Put and 53 | // the values returned by Get. 54 | // 55 | // If Get would otherwise return nil and p.New is non-nil, Get returns 56 | // the result of calling p.New. 57 | func (p *Pool) Get() (x interface{}) { 58 | p.init() 59 | return p.p.Get() 60 | } 61 | -------------------------------------------------------------------------------- /lang/syncx/rwmutex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx 16 | 17 | import ( 18 | "runtime" 19 | "sync" 20 | "unsafe" 21 | 22 | "github.com/bytedance/gopkg/internal/runtimex" 23 | 24 | "golang.org/x/sys/cpu" 25 | ) 26 | 27 | const ( 28 | cacheLineSize = unsafe.Sizeof(cpu.CacheLinePad{}) 29 | ) 30 | 31 | var ( 32 | shardsLen int 33 | ) 34 | 35 | // RWMutex is a p-shard mutex, which has better performance when there's much more read than write. 36 | type RWMutex []rwMutexShard 37 | 38 | type rwMutexShard struct { 39 | sync.RWMutex 40 | _pad [cacheLineSize - unsafe.Sizeof(sync.RWMutex{})]byte 41 | } 42 | 43 | func init() { 44 | shardsLen = runtime.GOMAXPROCS(0) 45 | } 46 | 47 | // NewRWMutex creates a new RWMutex. 48 | func NewRWMutex() RWMutex { 49 | return make([]rwMutexShard, shardsLen) 50 | } 51 | 52 | func (m RWMutex) Lock() { 53 | for shard := range m { 54 | m[shard].Lock() 55 | } 56 | } 57 | 58 | func (m RWMutex) Unlock() { 59 | for shard := range m { 60 | m[shard].Unlock() 61 | } 62 | } 63 | 64 | func (m RWMutex) RLocker() sync.Locker { 65 | return m[runtimex.Pid()%shardsLen].RWMutex.RLocker() 66 | } 67 | -------------------------------------------------------------------------------- /lang/syncx/rwmutex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package syncx 16 | 17 | import ( 18 | "fmt" 19 | "sync" 20 | "testing" 21 | 22 | "github.com/bytedance/gopkg/lang/fastrand" 23 | ) 24 | 25 | func BenchmarkRWMutex100Read(b *testing.B) { 26 | for nWorker := 1; nWorker <= 256; nWorker <<= 2 { 27 | b.Run(fmt.Sprintf("syncx-%d", nWorker), func(b *testing.B) { 28 | benchmarkSyncxRWMutexNWriteNRead(b, nWorker, 0) 29 | }) 30 | 31 | b.Run(fmt.Sprintf("sync-%d", nWorker), func(b *testing.B) { 32 | benchmarkSyncRWMutexNWriteNRead(b, nWorker, 0) 33 | }) 34 | } 35 | } 36 | 37 | func BenchmarkRWMutex1Write99Read(b *testing.B) { 38 | for nWorker := 1; nWorker <= 256; nWorker <<= 2 { 39 | b.Run(fmt.Sprintf("syncx-%d", nWorker), func(b *testing.B) { 40 | benchmarkSyncxRWMutexNWriteNRead(b, nWorker, 1) 41 | }) 42 | 43 | b.Run(fmt.Sprintf("sync-%d", nWorker), func(b *testing.B) { 44 | benchmarkSyncRWMutexNWriteNRead(b, nWorker, 1) 45 | }) 46 | } 47 | } 48 | 49 | func BenchmarkRWMutex10Write90Read(b *testing.B) { 50 | for nWorker := 1; nWorker <= 256; nWorker <<= 2 { 51 | b.Run(fmt.Sprintf("syncx-%d", nWorker), func(b *testing.B) { 52 | benchmarkSyncxRWMutexNWriteNRead(b, nWorker, 10) 53 | }) 54 | 55 | b.Run(fmt.Sprintf("sync-%d", nWorker), func(b *testing.B) { 56 | benchmarkSyncRWMutexNWriteNRead(b, nWorker, 10) 57 | }) 58 | } 59 | } 60 | 61 | func BenchmarkRWMutex50Write50Read(b *testing.B) { 62 | for nWorker := 1; nWorker <= 256; nWorker <<= 2 { 63 | b.Run(fmt.Sprintf("syncx-%d", nWorker), func(b *testing.B) { 64 | benchmarkSyncxRWMutexNWriteNRead(b, nWorker, 50) 65 | }) 66 | 67 | b.Run(fmt.Sprintf("sync-%d", nWorker), func(b *testing.B) { 68 | benchmarkSyncRWMutexNWriteNRead(b, nWorker, 50) 69 | }) 70 | } 71 | } 72 | 73 | func benchmarkSyncRWMutexNWriteNRead(b *testing.B, nWorker, nWrite int) { 74 | var res int // A mock resource we contention for 75 | 76 | var mu sync.RWMutex 77 | mu.Lock() 78 | 79 | var wg sync.WaitGroup 80 | wg.Add(nWorker) 81 | 82 | n := b.N 83 | quota := n / nWorker 84 | 85 | for g := nWorker; g > 0; g-- { 86 | // Comuse remaining quota 87 | if g == 1 { 88 | quota = n 89 | } 90 | go func(quota int) { 91 | for i := 0; i < quota; i++ { 92 | if fastrand.Intn(100) > nWrite-1 { 93 | mu.RLock() 94 | _ = res 95 | mu.RUnlock() 96 | } else { 97 | mu.Lock() 98 | res++ 99 | mu.Unlock() 100 | } 101 | } 102 | wg.Done() 103 | }(quota) 104 | 105 | n -= quota 106 | } 107 | 108 | if n != 0 { 109 | b.Fatalf("Incorrect quota assignments: %v remaining", n) 110 | } 111 | 112 | b.ResetTimer() 113 | mu.Unlock() 114 | wg.Wait() 115 | } 116 | 117 | func benchmarkSyncxRWMutexNWriteNRead(b *testing.B, nWorker, nWrite int) { 118 | var res int // A mock resource we contention for 119 | 120 | mu := NewRWMutex() 121 | mu.Lock() 122 | 123 | var wg sync.WaitGroup 124 | wg.Add(nWorker) 125 | 126 | n := b.N 127 | quota := n / nWorker 128 | 129 | for g := nWorker; g > 0; g-- { 130 | // Comuse remaining quota 131 | if g == 1 { 132 | quota = n 133 | } 134 | go func(quota int) { 135 | rmu := mu.RLocker() 136 | for i := 0; i < quota; i++ { 137 | if fastrand.Intn(100) > nWrite-1 { 138 | rmu.Lock() 139 | _ = res 140 | rmu.Unlock() 141 | } else { 142 | mu.Lock() 143 | res++ 144 | mu.Unlock() 145 | } 146 | } 147 | wg.Done() 148 | }(quota) 149 | 150 | n -= quota 151 | } 152 | 153 | if n != 0 { 154 | b.Fatalf("Incorrect quota assignments: %v remaining", n) 155 | } 156 | 157 | b.ResetTimer() 158 | mu.Unlock() 159 | wg.Wait() 160 | } 161 | -------------------------------------------------------------------------------- /util/gctuner/README.md: -------------------------------------------------------------------------------- 1 | # gctuner 2 | 3 | ## Introduction 4 | 5 | Inspired 6 | by [How We Saved 70K Cores Across 30 Mission-Critical Services (Large-Scale, Semi-Automated Go GC Tuning @Uber)](https://eng.uber.com/how-we-saved-70k-cores-across-30-mission-critical-services/) 7 | . 8 | 9 | ```text 10 | _______________ => limit: host/cgroup memory hard limit 11 | | | 12 | |---------------| => gc_trigger: heap_live + heap_live * GCPercent / 100 13 | | | 14 | |---------------| 15 | | heap_live | 16 | |_______________| 17 | ``` 18 | 19 | Go runtime trigger GC when hit `gc_trigger` which affected by `GCPercent` and `heap_live`. 20 | 21 | Assuming that we have stable traffic, our application will always have 100 MB live heap, so the runtime will trigger GC 22 | once heap hits 200 MB(by default GOGC=100). The heap size will be changed like: `100MB => 200MB => 100MB => 200MB => ...`. 23 | 24 | But in real production, our application may have 4 GB memory resources, so no need to GC so frequently. 25 | 26 | The gctuner helps to change the GOGC(GCPercent) dynamically at runtime, set the appropriate GCPercent according to current 27 | memory usage. 28 | 29 | ### How it works 30 | 31 | ```text 32 | _______________ => limit: host/cgroup memory hard limit 33 | | | 34 | |---------------| => threshold: increase GCPercent when gc_trigger < threshold 35 | | | 36 | |---------------| => gc_trigger: heap_live + heap_live * GCPercent / 100 37 | | | 38 | |---------------| 39 | | heap_live | 40 | |_______________| 41 | 42 | threshold = inuse + inuse * (gcPercent / 100) 43 | => gcPercent = (threshold - inuse) / inuse * 100 44 | 45 | if threshold < 2*inuse, so gcPercent < 100, and GC positively to avoid OOM 46 | if threshold > 2*inuse, so gcPercent > 100, and GC negatively to reduce GC times 47 | ``` 48 | 49 | ## Usage 50 | 51 | The recommended threshold is 70% of the memory limit. 52 | 53 | ```go 54 | 55 | // Get mem limit from the host machine or cgroup file. 56 | limit := 4 * 1024 * 1024 * 1024 57 | threshold := limit * 0.7 58 | 59 | gctuner.Tuning(threshold) 60 | ``` 61 | -------------------------------------------------------------------------------- /util/gctuner/finalizer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gctuner 16 | 17 | import ( 18 | "runtime" 19 | "sync/atomic" 20 | ) 21 | 22 | type finalizerCallback func() 23 | 24 | type finalizer struct { 25 | ref *finalizerRef 26 | callback finalizerCallback 27 | stopped int32 28 | } 29 | 30 | type finalizerRef struct { 31 | parent *finalizer 32 | } 33 | 34 | func finalizerHandler(f *finalizerRef) { 35 | // stop calling callback 36 | if atomic.LoadInt32(&f.parent.stopped) > 0 { 37 | return 38 | } 39 | f.parent.callback() 40 | runtime.SetFinalizer(f, finalizerHandler) 41 | } 42 | 43 | // newFinalizer return a finalizer object and caller should save it to make sure it will not be gc. 44 | // the go runtime promise the callback function should be called every gc time. 45 | func newFinalizer(callback finalizerCallback) *finalizer { 46 | f := &finalizer{ 47 | callback: callback, 48 | } 49 | f.ref = &finalizerRef{parent: f} 50 | runtime.SetFinalizer(f.ref, finalizerHandler) 51 | f.ref = nil // trigger gc 52 | return f 53 | } 54 | 55 | func (f *finalizer) stop() { 56 | atomic.StoreInt32(&f.stopped, 1) 57 | } 58 | -------------------------------------------------------------------------------- /util/gctuner/finalizer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gctuner 16 | 17 | import ( 18 | "runtime" 19 | "runtime/debug" 20 | "sync/atomic" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestFinalizer(t *testing.T) { 27 | // disable gc 28 | debug.SetGCPercent(-1) 29 | defer debug.SetGCPercent(100) 30 | 31 | maxCount := int32(16) 32 | is := assert.New(t) 33 | var count int32 34 | f := newFinalizer(func() { 35 | n := atomic.AddInt32(&count, 1) 36 | if n > maxCount { 37 | t.Fatalf("cannot exec finalizer callback after f has been gc") 38 | } 39 | }) 40 | for atomic.LoadInt32(&count) < maxCount { 41 | runtime.GC() 42 | } 43 | is.Nil(f.ref) 44 | f.stop() 45 | // when f stopped, finalizer callback will not be called 46 | lastCount := atomic.LoadInt32(&count) 47 | for i := 0; i < 10; i++ { 48 | runtime.GC() 49 | is.Equal(lastCount, atomic.LoadInt32(&count)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /util/gctuner/mem.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gctuner 16 | 17 | import ( 18 | "runtime" 19 | ) 20 | 21 | var memStats runtime.MemStats 22 | 23 | func readMemoryInuse() uint64 { 24 | runtime.ReadMemStats(&memStats) 25 | return memStats.HeapInuse 26 | } 27 | -------------------------------------------------------------------------------- /util/gctuner/mem_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gctuner 16 | 17 | import ( 18 | "runtime" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestMem(t *testing.T) { 25 | defer runtime.GC() // make it will not affect other tests 26 | 27 | is := assert.New(t) 28 | const mb = 1024 * 1024 29 | 30 | heap := make([]byte, 100*mb+1) 31 | inuse := readMemoryInuse() 32 | t.Logf("mem inuse: %d MB", inuse/mb) 33 | is.GreaterOrEqual(inuse, uint64(100*mb)) 34 | heap[0] = 0 35 | } 36 | -------------------------------------------------------------------------------- /util/gctuner/tuner_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gctuner 16 | 17 | import ( 18 | "runtime" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | var testHeap []byte 25 | 26 | func TestTuner(t *testing.T) { 27 | is := assert.New(t) 28 | memLimit := uint64(100 * 1024 * 1024) //100 MB 29 | threshold := memLimit / 2 30 | tn := newTuner(threshold) 31 | currentGCPercent := tn.getGCPercent() 32 | is.Equal(tn.threshold, threshold) 33 | is.Equal(defaultGCPercent, currentGCPercent) 34 | 35 | // wait for tuner set gcPercent to maxGCPercent 36 | t.Logf("old gc percent before gc: %d", tn.getGCPercent()) 37 | for tn.getGCPercent() != maxGCPercent { 38 | runtime.GC() 39 | t.Logf("new gc percent after gc: %d", tn.getGCPercent()) 40 | } 41 | 42 | // 1/4 threshold 43 | testHeap = make([]byte, threshold/4) 44 | // wait for tuner set gcPercent to ~= 300 45 | t.Logf("old gc percent before gc: %d", tn.getGCPercent()) 46 | for tn.getGCPercent() == maxGCPercent { 47 | runtime.GC() 48 | t.Logf("new gc percent after gc: %d", tn.getGCPercent()) 49 | } 50 | currentGCPercent = tn.getGCPercent() 51 | is.GreaterOrEqual(currentGCPercent, uint32(250)) 52 | is.LessOrEqual(currentGCPercent, uint32(300)) 53 | 54 | // 1/2 threshold 55 | testHeap = make([]byte, threshold/2) 56 | // wait for tuner set gcPercent to ~= 100 57 | t.Logf("old gc percent before gc: %d", tn.getGCPercent()) 58 | for tn.getGCPercent() == currentGCPercent { 59 | runtime.GC() 60 | t.Logf("new gc percent after gc: %d", tn.getGCPercent()) 61 | } 62 | currentGCPercent = tn.getGCPercent() 63 | is.GreaterOrEqual(currentGCPercent, uint32(50)) 64 | is.LessOrEqual(currentGCPercent, uint32(100)) 65 | 66 | // 3/4 threshold 67 | testHeap = make([]byte, threshold/4*3) 68 | // wait for tuner set gcPercent to minGCPercent 69 | t.Logf("old gc percent before gc: %d", tn.getGCPercent()) 70 | for tn.getGCPercent() != minGCPercent { 71 | runtime.GC() 72 | t.Logf("new gc percent after gc: %d", tn.getGCPercent()) 73 | } 74 | is.Equal(minGCPercent, tn.getGCPercent()) 75 | 76 | // out of threshold 77 | testHeap = make([]byte, threshold+1024) 78 | t.Logf("old gc percent before gc: %d", tn.getGCPercent()) 79 | runtime.GC() 80 | for i := 0; i < 8; i++ { 81 | runtime.GC() 82 | is.Equal(minGCPercent, tn.getGCPercent()) 83 | } 84 | 85 | // no heap 86 | testHeap = nil 87 | // wait for tuner set gcPercent to maxGCPercent 88 | t.Logf("old gc percent before gc: %d", tn.getGCPercent()) 89 | for tn.getGCPercent() != maxGCPercent { 90 | runtime.GC() 91 | t.Logf("new gc percent after gc: %d", tn.getGCPercent()) 92 | } 93 | } 94 | 95 | func TestCalcGCPercent(t *testing.T) { 96 | is := assert.New(t) 97 | const gb = 1024 * 1024 * 1024 98 | // use default value when invalid params 99 | is.Equal(defaultGCPercent, calcGCPercent(0, 0)) 100 | is.Equal(defaultGCPercent, calcGCPercent(0, 1)) 101 | is.Equal(defaultGCPercent, calcGCPercent(1, 0)) 102 | 103 | is.Equal(maxGCPercent, calcGCPercent(1, 3*gb)) 104 | is.Equal(maxGCPercent, calcGCPercent(gb/10, 4*gb)) 105 | is.Equal(maxGCPercent, calcGCPercent(gb/2, 4*gb)) 106 | is.Equal(uint32(300), calcGCPercent(1*gb, 4*gb)) 107 | is.Equal(uint32(166), calcGCPercent(1.5*gb, 4*gb)) 108 | is.Equal(uint32(100), calcGCPercent(2*gb, 4*gb)) 109 | is.Equal(minGCPercent, calcGCPercent(3*gb, 4*gb)) 110 | is.Equal(minGCPercent, calcGCPercent(4*gb, 4*gb)) 111 | is.Equal(minGCPercent, calcGCPercent(5*gb, 4*gb)) 112 | } 113 | -------------------------------------------------------------------------------- /util/gopool/README.md: -------------------------------------------------------------------------------- 1 | # gopool 2 | 3 | ## Introduction 4 | 5 | `gopool` is a high-performance goroutine pool which aims to reuse goroutines and limit the number of goroutines. 6 | 7 | It is an alternative to the `go` keyword. 8 | 9 | ## Features 10 | 11 | - High Performance 12 | - Auto-recovering Panics 13 | - Limit Goroutine Numbers 14 | - Reuse Goroutine Stack 15 | 16 | ## QuickStart 17 | 18 | Just replace your `go func(){...}` with `gopool.Go(func(){...})`. 19 | 20 | old: 21 | ```go 22 | go func() { 23 | // do your job 24 | }() 25 | ``` 26 | 27 | new: 28 | ```go 29 | gopool.Go(func(){ 30 | /// do your job 31 | }) 32 | ``` 33 | -------------------------------------------------------------------------------- /util/gopool/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gopool 16 | 17 | const ( 18 | defaultScalaThreshold = 1 19 | ) 20 | 21 | // Config is used to config pool. 22 | type Config struct { 23 | // threshold for scale. 24 | // new goroutine is created if len(task chan) > ScaleThreshold. 25 | // defaults to defaultScalaThreshold. 26 | ScaleThreshold int32 27 | } 28 | 29 | // NewConfig creates a default Config. 30 | func NewConfig() *Config { 31 | c := &Config{ 32 | ScaleThreshold: defaultScalaThreshold, 33 | } 34 | return c 35 | } 36 | -------------------------------------------------------------------------------- /util/gopool/gopool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gopool 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "math" 21 | "sync" 22 | ) 23 | 24 | // defaultPool is the global default pool. 25 | var defaultPool Pool 26 | 27 | var poolMap sync.Map 28 | 29 | func init() { 30 | defaultPool = NewPool("gopool.DefaultPool", math.MaxInt32, NewConfig()) 31 | } 32 | 33 | // Go is an alternative to the go keyword, which is able to recover panic. 34 | // gopool.Go(func(arg interface{}){ 35 | // ... 36 | // }(nil)) 37 | func Go(f func()) { 38 | CtxGo(context.Background(), f) 39 | } 40 | 41 | // CtxGo is preferred than Go. 42 | func CtxGo(ctx context.Context, f func()) { 43 | defaultPool.CtxGo(ctx, f) 44 | } 45 | 46 | // SetCap is not recommended to be called, this func changes the global pool's capacity which will affect other callers. 47 | func SetCap(cap int32) { 48 | defaultPool.SetCap(cap) 49 | } 50 | 51 | // SetPanicHandler sets the panic handler for the global pool. 52 | func SetPanicHandler(f func(context.Context, interface{})) { 53 | defaultPool.SetPanicHandler(f) 54 | } 55 | 56 | // WorkerCount returns the number of global default pool's running workers 57 | func WorkerCount() int32 { 58 | return defaultPool.WorkerCount() 59 | } 60 | 61 | // RegisterPool registers a new pool to the global map. 62 | // GetPool can be used to get the registered pool by name. 63 | // returns error if the same name is registered. 64 | func RegisterPool(p Pool) error { 65 | _, loaded := poolMap.LoadOrStore(p.Name(), p) 66 | if loaded { 67 | return fmt.Errorf("name: %s already registered", p.Name()) 68 | } 69 | return nil 70 | } 71 | 72 | // GetPool gets the registered pool by name. 73 | // Returns nil if not registered. 74 | func GetPool(name string) Pool { 75 | p, ok := poolMap.Load(name) 76 | if !ok { 77 | return nil 78 | } 79 | return p.(Pool) 80 | } 81 | -------------------------------------------------------------------------------- /util/gopool/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gopool 16 | 17 | import ( 18 | "context" 19 | "sync" 20 | "sync/atomic" 21 | ) 22 | 23 | type Pool interface { 24 | // Name returns the corresponding pool name. 25 | Name() string 26 | // SetCap sets the goroutine capacity of the pool. 27 | SetCap(cap int32) 28 | // Go executes f. 29 | Go(f func()) 30 | // CtxGo executes f and accepts the context. 31 | CtxGo(ctx context.Context, f func()) 32 | // SetPanicHandler sets the panic handler. 33 | SetPanicHandler(f func(context.Context, interface{})) 34 | // WorkerCount returns the number of running workers 35 | WorkerCount() int32 36 | } 37 | 38 | var taskPool sync.Pool 39 | 40 | func init() { 41 | taskPool.New = newTask 42 | } 43 | 44 | type task struct { 45 | ctx context.Context 46 | f func() 47 | 48 | next *task 49 | } 50 | 51 | func (t *task) zero() { 52 | t.ctx = nil 53 | t.f = nil 54 | t.next = nil 55 | } 56 | 57 | func (t *task) Recycle() { 58 | t.zero() 59 | taskPool.Put(t) 60 | } 61 | 62 | func newTask() interface{} { 63 | return &task{} 64 | } 65 | 66 | type taskList struct { 67 | sync.Mutex 68 | taskHead *task 69 | taskTail *task 70 | } 71 | 72 | type pool struct { 73 | // The name of the pool 74 | name string 75 | 76 | // capacity of the pool, the maximum number of goroutines that are actually working 77 | cap int32 78 | // Configuration information 79 | config *Config 80 | // linked list of tasks 81 | taskHead *task 82 | taskTail *task 83 | taskLock sync.Mutex 84 | taskCount int32 85 | 86 | // Record the number of running workers 87 | workerCount int32 88 | 89 | // This method will be called when the worker panic 90 | panicHandler func(context.Context, interface{}) 91 | } 92 | 93 | // NewPool creates a new pool with the given name, cap and config. 94 | func NewPool(name string, cap int32, config *Config) Pool { 95 | p := &pool{ 96 | name: name, 97 | cap: cap, 98 | config: config, 99 | } 100 | return p 101 | } 102 | 103 | func (p *pool) Name() string { 104 | return p.name 105 | } 106 | 107 | func (p *pool) SetCap(cap int32) { 108 | atomic.StoreInt32(&p.cap, cap) 109 | } 110 | 111 | func (p *pool) Go(f func()) { 112 | p.CtxGo(context.Background(), f) 113 | } 114 | 115 | func (p *pool) CtxGo(ctx context.Context, f func()) { 116 | t := taskPool.Get().(*task) 117 | t.ctx = ctx 118 | t.f = f 119 | p.taskLock.Lock() 120 | if p.taskHead == nil { 121 | p.taskHead = t 122 | p.taskTail = t 123 | } else { 124 | p.taskTail.next = t 125 | p.taskTail = t 126 | } 127 | p.taskLock.Unlock() 128 | atomic.AddInt32(&p.taskCount, 1) 129 | // The following two conditions are met: 130 | // 1. the number of tasks is greater than the threshold. 131 | // 2. The current number of workers is less than the upper limit p.cap. 132 | // or there are currently no workers. 133 | if (atomic.LoadInt32(&p.taskCount) >= p.config.ScaleThreshold && p.WorkerCount() < atomic.LoadInt32(&p.cap)) || p.WorkerCount() == 0 { 134 | p.incWorkerCount() 135 | w := workerPool.Get().(*worker) 136 | w.pool = p 137 | w.run() 138 | } 139 | } 140 | 141 | // SetPanicHandler the func here will be called after the panic has been recovered. 142 | func (p *pool) SetPanicHandler(f func(context.Context, interface{})) { 143 | p.panicHandler = f 144 | } 145 | 146 | func (p *pool) WorkerCount() int32 { 147 | return atomic.LoadInt32(&p.workerCount) 148 | } 149 | 150 | func (p *pool) incWorkerCount() { 151 | atomic.AddInt32(&p.workerCount, 1) 152 | } 153 | 154 | func (p *pool) decWorkerCount() { 155 | atomic.AddInt32(&p.workerCount, -1) 156 | } 157 | -------------------------------------------------------------------------------- /util/gopool/pool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gopool 16 | 17 | import ( 18 | "runtime" 19 | "sync" 20 | "sync/atomic" 21 | "testing" 22 | ) 23 | 24 | const benchmarkTimes = 10000 25 | 26 | func DoCopyStack(a, b int) int { 27 | if b < 100 { 28 | return DoCopyStack(0, b+1) 29 | } 30 | return 0 31 | } 32 | 33 | func testFunc() { 34 | DoCopyStack(0, 0) 35 | } 36 | 37 | func testPanicFunc() { 38 | panic("test") 39 | } 40 | 41 | func TestPool(t *testing.T) { 42 | p := NewPool("test", 100, NewConfig()) 43 | var n int32 44 | var wg sync.WaitGroup 45 | for i := 0; i < 2000; i++ { 46 | wg.Add(1) 47 | p.Go(func() { 48 | defer wg.Done() 49 | atomic.AddInt32(&n, 1) 50 | }) 51 | } 52 | wg.Wait() 53 | if n != 2000 { 54 | t.Error(n) 55 | } 56 | } 57 | 58 | func TestPoolPanic(t *testing.T) { 59 | p := NewPool("test", 100, NewConfig()) 60 | p.Go(testPanicFunc) 61 | } 62 | 63 | func BenchmarkPool(b *testing.B) { 64 | config := NewConfig() 65 | config.ScaleThreshold = 1 66 | p := NewPool("benchmark", int32(runtime.GOMAXPROCS(0)), config) 67 | var wg sync.WaitGroup 68 | b.ReportAllocs() 69 | b.ResetTimer() 70 | for i := 0; i < b.N; i++ { 71 | wg.Add(benchmarkTimes) 72 | for j := 0; j < benchmarkTimes; j++ { 73 | p.Go(func() { 74 | testFunc() 75 | wg.Done() 76 | }) 77 | } 78 | wg.Wait() 79 | } 80 | } 81 | 82 | func BenchmarkGo(b *testing.B) { 83 | var wg sync.WaitGroup 84 | b.ReportAllocs() 85 | b.ResetTimer() 86 | for i := 0; i < b.N; i++ { 87 | wg.Add(benchmarkTimes) 88 | for j := 0; j < benchmarkTimes; j++ { 89 | go func() { 90 | testFunc() 91 | wg.Done() 92 | }() 93 | } 94 | wg.Wait() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /util/gopool/worker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gopool 16 | 17 | import ( 18 | "fmt" 19 | "runtime/debug" 20 | "sync" 21 | "sync/atomic" 22 | 23 | "github.com/bytedance/gopkg/util/logger" 24 | ) 25 | 26 | var workerPool sync.Pool 27 | 28 | func init() { 29 | workerPool.New = newWorker 30 | } 31 | 32 | type worker struct { 33 | pool *pool 34 | } 35 | 36 | func newWorker() interface{} { 37 | return &worker{} 38 | } 39 | 40 | func (w *worker) run() { 41 | go func() { 42 | for { 43 | var t *task 44 | w.pool.taskLock.Lock() 45 | if w.pool.taskHead != nil { 46 | t = w.pool.taskHead 47 | w.pool.taskHead = w.pool.taskHead.next 48 | atomic.AddInt32(&w.pool.taskCount, -1) 49 | } 50 | if t == nil { 51 | // if there's no task to do, exit 52 | w.close() 53 | w.pool.taskLock.Unlock() 54 | w.Recycle() 55 | return 56 | } 57 | w.pool.taskLock.Unlock() 58 | func() { 59 | defer func() { 60 | if r := recover(); r != nil { 61 | if w.pool.panicHandler != nil { 62 | w.pool.panicHandler(t.ctx, r) 63 | } else { 64 | msg := fmt.Sprintf("GOPOOL: panic in pool: %s: %v: %s", w.pool.name, r, debug.Stack()) 65 | logger.CtxErrorf(t.ctx, msg) 66 | } 67 | } 68 | }() 69 | t.f() 70 | }() 71 | t.Recycle() 72 | } 73 | }() 74 | } 75 | 76 | func (w *worker) close() { 77 | w.pool.decWorkerCount() 78 | } 79 | 80 | func (w *worker) zero() { 81 | w.pool = nil 82 | } 83 | 84 | func (w *worker) Recycle() { 85 | w.zero() 86 | workerPool.Put(w) 87 | } 88 | -------------------------------------------------------------------------------- /util/logger/README.md: -------------------------------------------------------------------------------- 1 | # logger 2 | 3 | `logger` is the package which provides logging functions for all the utilities in gopkg. -------------------------------------------------------------------------------- /util/logger/default.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logger 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "log" 21 | "os" 22 | ) 23 | 24 | var ( 25 | _ Logger = (*localLogger)(nil) 26 | ) 27 | 28 | // SetDefaultLogger sets the default logger. 29 | // This is not concurrency safe, which means it should only be called during init. 30 | func SetDefaultLogger(l Logger) { 31 | if l == nil { 32 | panic("logger must not be nil") 33 | } 34 | defaultLogger = l 35 | } 36 | 37 | var defaultLogger Logger = &localLogger{ 38 | logger: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds), 39 | } 40 | 41 | type localLogger struct { 42 | logger *log.Logger 43 | } 44 | 45 | func (ll *localLogger) logf(lv Level, format *string, v ...interface{}) { 46 | if level > lv { 47 | return 48 | } 49 | msg := lv.toString() 50 | if format != nil { 51 | msg += fmt.Sprintf(*format, v...) 52 | } else { 53 | msg += fmt.Sprint(v...) 54 | } 55 | ll.logger.Output(3, msg) 56 | if lv == LevelFatal { 57 | os.Exit(1) 58 | } 59 | } 60 | 61 | func (ll *localLogger) Fatal(v ...interface{}) { 62 | ll.logf(LevelFatal, nil, v...) 63 | } 64 | 65 | func (ll *localLogger) Error(v ...interface{}) { 66 | ll.logf(LevelError, nil, v...) 67 | } 68 | 69 | func (ll *localLogger) Warn(v ...interface{}) { 70 | ll.logf(LevelWarn, nil, v...) 71 | } 72 | 73 | func (ll *localLogger) Notice(v ...interface{}) { 74 | ll.logf(LevelNotice, nil, v...) 75 | } 76 | 77 | func (ll *localLogger) Info(v ...interface{}) { 78 | ll.logf(LevelInfo, nil, v...) 79 | } 80 | 81 | func (ll *localLogger) Debug(v ...interface{}) { 82 | ll.logf(LevelDebug, nil, v...) 83 | } 84 | 85 | func (ll *localLogger) Trace(v ...interface{}) { 86 | ll.logf(LevelTrace, nil, v...) 87 | } 88 | 89 | func (ll *localLogger) Fatalf(format string, v ...interface{}) { 90 | ll.logf(LevelFatal, &format, v...) 91 | } 92 | 93 | func (ll *localLogger) Errorf(format string, v ...interface{}) { 94 | ll.logf(LevelError, &format, v...) 95 | } 96 | 97 | func (ll *localLogger) Warnf(format string, v ...interface{}) { 98 | ll.logf(LevelWarn, &format, v...) 99 | } 100 | 101 | func (ll *localLogger) Noticef(format string, v ...interface{}) { 102 | ll.logf(LevelNotice, &format, v...) 103 | } 104 | 105 | func (ll *localLogger) Infof(format string, v ...interface{}) { 106 | ll.logf(LevelInfo, &format, v...) 107 | } 108 | 109 | func (ll *localLogger) Debugf(format string, v ...interface{}) { 110 | ll.logf(LevelDebug, &format, v...) 111 | } 112 | 113 | func (ll *localLogger) Tracef(format string, v ...interface{}) { 114 | ll.logf(LevelTrace, &format, v...) 115 | } 116 | 117 | func (ll *localLogger) CtxFatalf(ctx context.Context, format string, v ...interface{}) { 118 | ll.logf(LevelFatal, &format, v...) 119 | } 120 | 121 | func (ll *localLogger) CtxErrorf(ctx context.Context, format string, v ...interface{}) { 122 | ll.logf(LevelError, &format, v...) 123 | } 124 | 125 | func (ll *localLogger) CtxWarnf(ctx context.Context, format string, v ...interface{}) { 126 | ll.logf(LevelWarn, &format, v...) 127 | } 128 | 129 | func (ll *localLogger) CtxNoticef(ctx context.Context, format string, v ...interface{}) { 130 | ll.logf(LevelNotice, &format, v...) 131 | } 132 | 133 | func (ll *localLogger) CtxInfof(ctx context.Context, format string, v ...interface{}) { 134 | ll.logf(LevelInfo, &format, v...) 135 | } 136 | 137 | func (ll *localLogger) CtxDebugf(ctx context.Context, format string, v ...interface{}) { 138 | ll.logf(LevelDebug, &format, v...) 139 | } 140 | 141 | func (ll *localLogger) CtxTracef(ctx context.Context, format string, v ...interface{}) { 142 | ll.logf(LevelTrace, &format, v...) 143 | } 144 | -------------------------------------------------------------------------------- /util/xxhash3/README.md: -------------------------------------------------------------------------------- 1 | # XXH3 hash algorithm 2 | A Go implementation of the 64/128 bit xxh3 algorithm, added the SIMD vector instruction set: AVX2 and SSE2 support to accelerate the hash processing.\ 3 | The original repository can be found here: https://github.com/Cyan4973/xxHash. 4 | 5 | 6 | ## Overview 7 | 8 | For the input length larger than 240, the 64-bit version of xxh3 algorithm goes along with following steps to get the hash result. 9 | 10 | ### step1. Initialize 8 accumulators used to store the middle result of each iterator. 11 | ``` 12 | xacc[0] = prime32_3 13 | xacc[1] = prime64_1 14 | xacc[2] = prime64_2 15 | xacc[3] = prime64_3 16 | xacc[4] = prime64_4 17 | xacc[5] = prime32_2 18 | xacc[6] = prime64_5 19 | xacc[7] = prime32_1 20 | ``` 21 | 22 | ### step2. Process 1024 bytes of input data as one block each time 23 | ``` 24 | while remaining_length > 1024{ 25 | for i:=0, j:=0; i < 1024; i += 64, j+=8 { 26 | for n:=0; n<8; n++{ 27 | inputN := input[i+8*n:i+8*n+8] 28 | secretN := inputN ^ secret[j+8*n:j+8*n+8] 29 | 30 | xacc[n^1] += inputN 31 | xacc[n] += (secretN & 0xFFFFFFFF) * (secretN >> 32) 32 | } 33 | } 34 | 35 | xacc[n] ^= xacc[n] >> 47 36 | xacc[n] ^= secret[128+8*n:128+8*n:+8] 37 | xacc[n] *= prime32_1 38 | 39 | remaining_length -= 1024 40 | } 41 | ``` 42 | 43 | ### step3. Process remaining stripes (totally 1024 bytes at most) 44 | ``` 45 | 46 | for i:=0, j:=0; i < remaining_length; i += 64, j+=8 { 47 | for n:=0; n<8; n++{ 48 | inputN := input[i+8*n:i+8*n+8] 49 | secretN := inputN ^ secret[j+8*n:j+8*n+8] 50 | 51 | xacc[n^1] += inputN 52 | xacc[n] += (secretN & 0xFFFFFFFF) * (secretN >> 32) 53 | } 54 | 55 | remaining_length -= 64 56 | } 57 | ``` 58 | 59 | ### step4. Process last stripe (align to last 64 bytes) 60 | ``` 61 | for n:=0; n<8; n++{ 62 | inputN := input[(length-64): (length-64)+8] 63 | secretN := inputN ^ secret[121+8*n, 121+8*n+8] 64 | 65 | xacc[n^1] += inputN 66 | xacc[n] += (secretN & 0xFFFFFFFF) * (secretN >> 32) 67 | } 68 | ``` 69 | 70 | ### step5. Merge & Avalanche accumulators 71 | ``` 72 | acc = length * prime64_1 73 | acc += mix(xacc[0]^secret11, xacc[1]^secret19) + mix(xacc[2]^secret27, xacc[3]^secret35) + 74 | mix(xacc[4]^secret43, xacc[5]^secret51) + mix(xacc[6]^secret59, xacc[7]^secret67) 75 | 76 | acc ^= acc >> 37 77 | acc *= 0x165667919e3779f9 78 | acc ^= acc >> 32 79 | ``` 80 | 81 | If the input data size is not larger than 240 bytes, the calculating steps are similar to the above description. The major difference lies in the data alignment. In the case of smaller input, the alignment size is 16 bytes. 82 | 83 | ## Quickstart 84 | The SIMD assembly file can be generated by the following command: 85 | ``` 86 | cd internal/avo && ./build.sh 87 | ``` 88 | 89 | Use Hash functions in your code: 90 | ``` 91 | package main 92 | 93 | import "github.com/bytedance/gopkg/util/xxhash3" 94 | 95 | func main() { 96 | println(xxhash3.HashString("hello world!")) 97 | println(xxhash3.Hash128String("hello world!")) 98 | } 99 | ``` 100 | ## Benchmark 101 | go version: go1.15.10 linux/amd64\ 102 | CPU: Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz\ 103 | OS: Linux bluehorse 5.8.0-48-generic #54~20.04.1-Ubuntu SMP\ 104 | MEMORY: 32G 105 | 106 | ``` 107 | go test -run=None -bench=. -benchtime=1000x -count=10 > 1000_10.txt && benchstat 1000_10.txt 108 | ``` 109 | ``` 110 | name time/op 111 | Default/Len0_16/Target64-4 88.6ns ± 0% 112 | Default/Len0_16/Target128-4 176ns ± 0% 113 | Default/Len17_128/Target64-4 1.07µs ± 2% 114 | Default/Len17_128/Target128-4 1.76µs ± 1% 115 | Default/Len129_240/Target64-4 1.89µs ± 2% 116 | Default/Len129_240/Target128-4 2.82µs ± 3% 117 | Default/Len241_1024/Target64-4 47.9µs ± 0% 118 | Default/Len241_1024/Target128-4 52.8µs ± 1% 119 | Default/Scalar/Target64-4 3.52ms ± 2% 120 | Default/Scalar/Target128-4 3.52ms ± 1% 121 | Default/AVX2/Target64-4 1.93ms ± 2% 122 | Default/AVX2/Target128-4 1.91ms ± 1% 123 | Default/SSE2/Target64-4 2.61ms ± 2% 124 | Default/SSE2/Target128-4 2.63ms ± 4% 125 | ``` -------------------------------------------------------------------------------- /util/xxhash3/accum.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !amd64 16 | // +build !amd64 17 | 18 | package xxhash3 19 | 20 | import "unsafe" 21 | 22 | func accum(xacc *[8]uint64, xinput, xsecret unsafe.Pointer, l uintptr) { 23 | accumScalar(xacc, xinput, xsecret, l) 24 | } 25 | -------------------------------------------------------------------------------- /util/xxhash3/accum_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package xxhash3 16 | 17 | import "unsafe" 18 | 19 | func accumAVX2(acc *[8]uint64, xinput, xsecret unsafe.Pointer, len uintptr) 20 | func accumSSE2(acc *[8]uint64, xinput, xsecret unsafe.Pointer, len uintptr) 21 | 22 | func accum(xacc *[8]uint64, xinput, xsecret unsafe.Pointer, l uintptr) { 23 | if avx2 { 24 | accumAVX2(xacc, xinput, xsecret, l) 25 | } else if sse2 { 26 | accumSSE2(xacc, xinput, xsecret, l) 27 | } else { 28 | accumScalar(xacc, xinput, xsecret, l) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /util/xxhash3/internal/avo/build.sh: -------------------------------------------------------------------------------- 1 | go run sse.go avx.go gen.go -avx2 -out ./avx2.s 2 | go run sse.go avx.go gen.go -sse2 -out ./sse2.s 3 | sed -i '1i// Copyright 2021 ByteDance Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the "License");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an "AS IS" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n' ./avx2.s 4 | sed -i '1i// Copyright 2021 ByteDance Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the "License");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an "AS IS" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n' ./sse2.s 5 | -------------------------------------------------------------------------------- /util/xxhash3/internal/avo/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build ignore 16 | // +build ignore 17 | 18 | package main 19 | 20 | import "flag" 21 | 22 | var ( 23 | avx = flag.Bool("avx2", false, "avx2") 24 | sse = flag.Bool("sse2", false, "sse2") 25 | ) 26 | 27 | func main() { 28 | flag.Parse() 29 | 30 | if *avx { 31 | AVX2() 32 | } else if *sse { 33 | SSE2() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /util/xxhash3/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 ByteDance Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package xxhash3 implements https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h 16 | package xxhash3 17 | 18 | import ( 19 | "math/bits" 20 | "unsafe" 21 | 22 | "golang.org/x/sys/cpu" 23 | ) 24 | 25 | var ( 26 | avx2 = cpu.X86.HasAVX2 27 | sse2 = cpu.X86.HasSSE2 28 | hashfunc = [2]func(unsafe.Pointer, int) uint64{xxh3HashSmall, xxh3HashLarge} 29 | hashfunc128 = [2]func(unsafe.Pointer, int) [2]uint64{xxh3HashSmall128, xxh3HashLarge128} 30 | ) 31 | 32 | type funcUnsafe int 33 | 34 | const ( 35 | hashSmall funcUnsafe = iota 36 | hashLarge 37 | ) 38 | 39 | func mix(a, b uint64) uint64 { 40 | hi, lo := bits.Mul64(a, b) 41 | return hi ^ lo 42 | } 43 | func xxh3RRMXMX(h64 uint64, length uint64) uint64 { 44 | h64 ^= bits.RotateLeft64(h64, 49) ^ bits.RotateLeft64(h64, 24) 45 | h64 *= 0x9fb21c651e98df25 46 | h64 ^= (h64 >> 35) + length 47 | h64 *= 0x9fb21c651e98df25 48 | h64 ^= (h64 >> 28) 49 | return h64 50 | } 51 | 52 | func xxh64Avalanche(h64 uint64) uint64 { 53 | h64 *= prime64_2 54 | h64 ^= h64 >> 29 55 | h64 *= prime64_3 56 | h64 ^= h64 >> 32 57 | return h64 58 | } 59 | 60 | func xxh3Avalanche(x uint64) uint64 { 61 | x ^= x >> 37 62 | x *= 0x165667919e3779f9 63 | x ^= x >> 32 64 | return x 65 | } 66 | --------------------------------------------------------------------------------