├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── example └── main.go ├── go.mod ├── prime.go └── prime_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.17 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | run: go test -v -coverprofile=coverage.out ./... 26 | 27 | - name: Convert coverage to lcov 28 | uses: jandelgado/gcov2lcov-action@v1.0.5 29 | 30 | - name: Coveralls 31 | uses: coverallsapp/github-action@master 32 | with: 33 | path-to-lcov: coverage.lcov 34 | github-token: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Kaveh Mousavi Zamani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Prime 2 | ========= 3 | [![Go Lang](http://kavehmz.github.io/static/gopher/gopher-front.svg)](https://golang.org/) 4 | [![GoDoc](https://godoc.org/github.com/kavehmz/prime?status.svg)](https://godoc.org/github.com/kavehmz/prime) 5 | ![Build Status](https://github.com/kavehmz/prime/actions/workflows/go.yml/badge.svg?branch=master) 6 | [![Coverage Status](https://coveralls.io/repos/kavehmz/prime/badge.svg?branch=master&service=github)](https://coveralls.io/github/kavehmz/prime?branch=master) 7 | [![Go Report Card](https://goreportcard.com/badge/github.com/kavehmz/prime)](https://goreportcard.com/report/github.com/kavehmz/prime) 8 | [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/kavehmz/prime) 9 | 10 | This is a [Go](http://golang.org) library to produce prime numbers using all available cpu cores. 11 | 12 | 13 | ## Installation 14 | 15 | ```bash 16 | $ go get github.com/kavehmz/prime 17 | ``` 18 | 19 | # Usage 20 | 21 | ```go 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "github.com/kavehmz/prime" 27 | ) 28 | 29 | func main() { 30 | p := prime.Primes(1000000) 31 | fmt.Println("Number of primes:", len(p)) 32 | } 33 | ``` 34 | # Algorithm 35 | To find more about different methods to find a range of prime numbers you can look at following pages: 36 | 37 | * [Sieve of Eratosthenes](https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes) This is a more memory demanding method but faster by far for larger numbers. Here I have implemented both Segmented and non-Segmented methods. Segmented method had must less memory footprint. 38 | * [Trial division](https://en.wikipedia.org/wiki/Trial_division) Easier to understand and less memory consuming. 39 | 40 | # Performance 41 | Performance depends on the size of max number. But as an example, it needs about 3ms to produce the first 1,000,000 prime numbers. 42 | 43 | 44 | ```bash 45 | $ go test -bench . 46 | PASS 47 | BenchmarkPrimes-4 500 3181972 ns/op 48 | ok github.com/kavehmz/prime 1.618s 49 | ``` 50 | 51 | x |no segment |segmented 52 | -------------|-----------|------ 53 | 1,000,000 |0.003s | 0.007s 54 | 10,000,000 |0.035s | 0.044s 55 | 100,000,000 |0.642s | 0.345s 56 | 1,000,000,000|8.253s | 3.146s 57 | 58 | These calculations are done on a 3.1GHz Dual-core Intel Core i7. 59 | 60 | # Profiling 61 | 62 | If you like to see how profiling in Go works and you have a usage Go installation you can use pprof. 63 | 64 | First go and get the package 65 | ```bash 66 | $ go get github.com/kavehmz/prime 67 | $ cd $GOPATH/src/github.com/kavehmz/prime 68 | $ go build example/main.go 69 | $ ./main -cpuprofile=prime.prof -memprofile=prime.mprof 70 | $ # For inspecting memory usage do 71 | $ go tool pprof main prime.mprof 72 | $ # For inspecting cpu usage do 73 | $ go tool pprof main prime.prof 74 | 75 | Entering interactive mode (type "help" for commands) 76 | (pprof) list 77 | ``` 78 | 79 | To learn how you have use pprof look at the following links: 80 | 81 | - http://blog.golang.org/profiling-go-programs 82 | - https://golang.org/pkg/net/http/pprof/ 83 | 84 | # Why 85 | I used this simple library mainly to learn Go language, Go standards and for solving problems in https://projecteuler.net/ 86 | 87 | It can also be useful as a relatively fast implementation of prime numbers generator in Go. 88 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "runtime/pprof" 9 | "time" 10 | 11 | "github.com/kavehmz/prime" 12 | ) 13 | 14 | var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") 15 | var memprofile = flag.String("memprofile", "", "write memory profile to this file") 16 | var primeRange = flag.Uint64("primeRange", 1000000000, "Set the max prime number range") 17 | 18 | func main() { 19 | flag.Parse() 20 | if *cpuprofile != "" { 21 | f, err := os.Create(*cpuprofile) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | pprof.StartCPUProfile(f) 26 | defer pprof.StopCPUProfile() 27 | } 28 | 29 | start := time.Now() 30 | fmt.Println("number of primes (non-segmented-method):", len(prime.SieveOfEratosthenes(*primeRange))) 31 | fmt.Println("seconds it took:", time.Since(start)) 32 | 33 | start = time.Now() 34 | fmt.Println("number of primes (segmented-method):", len(prime.Primes(*primeRange))) 35 | fmt.Println("seconds it took:", time.Since(start)) 36 | 37 | if *memprofile != "" { 38 | f, err := os.Create(*memprofile) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | pprof.WriteHeapProfile(f) 43 | f.Close() 44 | return 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kavehmz/prime 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /prime.go: -------------------------------------------------------------------------------- 1 | // Package prime provides functionality to produce prime numbers using all 2 | // available cpu cores. https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes 3 | // can be an starting point to find more information about how to calculate 4 | // prime numbers. 5 | // 6 | // The method used in Primes function is Segmented sieve. Segmenting will 7 | // Reduce memory requirement of process. 8 | // The space complexity of the algorithm is O(√n). 9 | package prime 10 | 11 | import ( 12 | "math" 13 | "runtime" 14 | "sync" 15 | ) 16 | 17 | func fill(nums []bool, i uint64, max uint64) { 18 | a := 3 * i 19 | for a <= max { 20 | nums[a/2] = true 21 | a = a + 2*i 22 | } 23 | } 24 | 25 | func goFill(nums []bool, i uint64, max uint64, next chan bool) { 26 | fill(nums, i, max) 27 | <-next 28 | } 29 | 30 | // SieveOfEratosthenes returns a slice of all prime numbers equal or lower than max using Sieve Of Eratosthenes. 31 | // This is without segmenting. 32 | func SieveOfEratosthenes(n uint64) []uint64 { 33 | cores := runtime.NumCPU() 34 | next := make(chan bool, cores) 35 | 36 | var nums = make([]bool, n/2+1) 37 | m := uint64(math.Sqrt(float64(n))) 38 | 39 | for i := uint64(3); i <= m; i = i + 2 { 40 | if nums[i/2] == false { 41 | go goFill(nums, i, n, next) 42 | next <- true 43 | } 44 | } 45 | 46 | for i := 0; i < cores; i++ { 47 | next <- true 48 | } 49 | 50 | var ps []uint64 51 | if n >= 2 { 52 | ps = append(ps, 2) 53 | } 54 | for i := uint64(3); i <= n; i = i + 2 { 55 | if nums[i/2] == false { 56 | ps = append(ps, i) 57 | } 58 | } 59 | return ps 60 | } 61 | 62 | var csegPool sync.Pool 63 | 64 | func fillSegments(n uint64, basePrimes []uint64, allPrimes *[]uint64, segSize uint64, segNum uint64, next chan bool, nextTurn []chan bool) { 65 | cseg := (csegPool.Get()).([]bool) 66 | for i := uint64(0); i < segSize; i++ { 67 | cseg[i] = false 68 | } 69 | for i := 0; i < len(basePrimes); i++ { 70 | jMax := segSize * (segNum + 1) / basePrimes[i] 71 | for j := (segSize * segNum) / basePrimes[i]; j < jMax; j++ { 72 | sn := (j + 1) * basePrimes[i] 73 | cseg[sn-segSize*segNum-1] = true 74 | } 75 | } 76 | 77 | // This waiting for turn is to avoid sorts at the end. 78 | // Sorts are much more expensive than this wait even for a 79 | // mostly sorted list. 80 | if segNum > 1 { 81 | <-nextTurn[segNum] 82 | } 83 | 84 | for i := uint64(0); i < segSize; i++ { 85 | if !cseg[i] && segSize*segNum+i+1 <= n { 86 | *allPrimes = append(*allPrimes, segSize*segNum+i+1) 87 | } 88 | } 89 | 90 | <-next 91 | if int(segNum)+1 < len(nextTurn) { 92 | nextTurn[segNum+1] <- true 93 | } 94 | 95 | csegPool.Put(cseg) 96 | } 97 | 98 | // Primes is using Segmented sieve. This method will reduce memory usae of Sieve of Eratosthenes considerably. 99 | // besides memory allocation for Prime numbers slice, there is only O(sqrt(n)) extra memory required for the operation 100 | // You can learn more about it in https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes. 101 | func Primes(n uint64) (allPrimes []uint64) { 102 | if uint64(math.Log(float64(n))-1) == 0 { 103 | return SieveOfEratosthenes(n) 104 | } 105 | 106 | // There is a function pi(x) in math that will returns approximate number of prime numbers below n. 107 | allPrimes = make([]uint64, 0, n/uint64(math.Log(float64(n))-1)) 108 | segSize := uint64(math.Sqrt(float64(n))) 109 | 110 | csegPool.New = func() interface{} { 111 | return make([]bool, segSize) 112 | } 113 | 114 | basePrimes := SieveOfEratosthenes(segSize) 115 | allPrimes = append(allPrimes, basePrimes...) 116 | 117 | cores := runtime.NumCPU() 118 | next := make(chan bool, cores) 119 | var nextTurn []chan bool 120 | nextTurn = make([]chan bool, n/segSize+1) 121 | for i := uint64(0); i < n/segSize+1; i++ { 122 | nextTurn[i] = make(chan bool) 123 | } 124 | for segNum := uint64(1); segNum <= n/segSize; segNum++ { 125 | go fillSegments(n, basePrimes, &allPrimes, segSize, segNum, next, nextTurn) 126 | next <- true 127 | } 128 | for i := 0; i < cores; i++ { 129 | next <- true 130 | } 131 | 132 | return allPrimes 133 | } 134 | -------------------------------------------------------------------------------- /prime_test.go: -------------------------------------------------------------------------------- 1 | package prime 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestPrimes(t *testing.T) { 9 | p := Primes(1000000) 10 | if len(p) != 78498 { 11 | t.Error("Wrong number of prime numbers lower than 1M,", len(p)) 12 | } 13 | if p[0] != uint64(2) { 14 | t.Error("1st prime number is not 2,", p[0]) 15 | } 16 | if p[1] != uint64(3) { 17 | t.Error("2nd prime number is not 3,", p[1]) 18 | } 19 | if p[23423] != uint64(267391) { 20 | t.Error("23424th prime number is not 267391,", p[23423]) 21 | } 22 | p = Primes(100) 23 | if len(p) != 25 { 24 | t.Error("Wrong number of prime numbers lower than 100,", len(p)) 25 | } 26 | p = Primes(7) 27 | if p[3] != uint64(7) { 28 | t.Error("If max is 7 last prime must be 7, but it is", p[3]) 29 | } 30 | p = Primes(1) 31 | if len(p) != 0 { 32 | t.Error("Edge case of 1 not correct,", len(p)) 33 | } 34 | p = Primes(2) 35 | if len(p) != 1 { 36 | t.Error("Edge case of 2 not correct,", len(p)) 37 | } 38 | } 39 | 40 | func TestFill(t *testing.T) { 41 | p := make([]bool, 101) 42 | fill(p, 3, 100) 43 | var c uint64 44 | for _, v := range p { 45 | if v == true { 46 | c++ 47 | } 48 | } 49 | if c != 16 { 50 | t.Error("Filled cells are wrong ", c) 51 | } 52 | } 53 | 54 | func BenchmarkPrimes(b *testing.B) { 55 | for i := 0; i < b.N; i++ { 56 | Primes(1000000) 57 | } 58 | } 59 | 60 | func ExamplePrimes() { 61 | p := Primes(50) 62 | fmt.Println(p) 63 | // Output: 64 | // [2 3 5 7 11 13 17 19 23 29 31 37 41 43 47] 65 | 66 | } 67 | --------------------------------------------------------------------------------