├── .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 | [](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 | [](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 |
--------------------------------------------------------------------------------