Skip to content

Commit

Permalink
Merge pull request #4 from mustafaturan/optimizations
Browse files Browse the repository at this point in the history
Optimize for memory allocations and efficiency
  • Loading branch information
mustafaturan authored Apr 25, 2021
2 parents 1f2ac7e + 1ed88d8 commit 955f1b3
Show file tree
Hide file tree
Showing 19 changed files with 224 additions and 128 deletions.
7 changes: 2 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
language: go

go:
- 1.9.x
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
- 1.16.x
- master
- tip

before_install:
- go get golang.org/x/tools/cmd/cover
Expand Down
47 changes: 35 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
# Monoton

[![Build Status](https://travis-ci.org/mustafaturan/monoton.svg?branch=master)](https://travis-ci.org/mustafaturan/monoton)
[![Coverage Status](https://coveralls.io/repos/github/mustafaturan/monoton/badge.svg?branch=master)](https://coveralls.io/github/mustafaturan/monoton?branch=master)
[![Coverage Status](https://coveralls.io/repos/github/mustafaturan/monoton/badge.svg?branch=master)](https://coveralls.io/github/mustafaturan/monoton?branch=main)
[![Go Report Card](https://goreportcard.com/badge/github.com/mustafaturan/monoton)](https://goreportcard.com/report/github.com/mustafaturan/monoton)
[![GoDoc](https://godoc.org/github.com/mustafaturan/monoton?status.svg)](https://godoc.org/github.com/mustafaturan/monoton)
[![GoDoc](https://godoc.org/github.com/mustafaturan/monoton?status.svg)](https://godoc.org/github.com/mustafaturan/monoton/v2)

Highly scalable, single/multi node, predictable and incremental unique id
generator.
generator with zero allocation magic.

## Installation

Via go packages:
```go get github.com/mustafaturan/monoton```
```go get github.com/mustafaturan/monoton/v2```

## API

The method names and arities/args are stable now. No change should be expected
on the package for the version `1.x.x` except any bug fixes.
on the package for the version `2.x.x` except any bug fixes.

## Usage

### Using with Singleton

Create a new package like below, and then call `Next()` method:
Create a new package like below, and then call `Next()` or `NextBytes()` method:

```go
package uniqid

// Import packages
import (
"fmt"
"github.com/mustafaturan/monoton"
"github.com/mustafaturan/monoton/sequencer"
"github.com/mustafaturan/monoton/v2"
"github.com/mustafaturan/monoton/v2/sequencer"
)

var m monoton.Monoton

// On init configure the monoton
func init() {
m = *(newIDGenerator())
m = newIDGenerator()
}

func newIDGenerator() *monoton.Monoton {
func newIDGenerator() monoton.Monoton {
// Fetch your node id from a config server or generate from MAC/IP address
node := uint64(1)

Expand All @@ -64,6 +64,10 @@ func newIDGenerator() *monoton.Monoton {
func Generate() string {
m.Next()
}

func GeneateBytes() [16]byte {
m.NextBytes()
}
```

In any other package generate the ids like below:
Expand All @@ -89,8 +93,8 @@ package main
// Import packages
import (
"fmt"
"github.com/mustafaturan/monoton"
"github.com/mustafaturan/monoton/sequencer"
"github.com/mustafaturan/monoton/v2"
"github.com/mustafaturan/monoton/v2/sequencer"
)

func NewIDGenerator() *monoton.Monoton {
Expand Down Expand Up @@ -179,6 +183,25 @@ consequences.
The sequencers can be extended for any other time format, sequence format by
implementing the `monoton/sequencer.Sequencer` interface.

## Benchmarks

Command:
```
go test -benchtime 10000000x -benchmem -run=^$ -bench=. github.com/mustafaturan/monoton/v2
```

Results:
```
goos: darwin
goarch: amd64
pkg: github.com/mustafaturan/monoton/v2
cpu: Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz
BenchmarkNext-4 10000000 121.4 ns/op 0 B/op 0 allocs/op
BenchmarkNextBytes-4 10000000 115.5 ns/op 0 B/op 0 allocs/op
PASS
ok github.com/mustafaturan/monoton/v2 2.639s
```

## Contributing

All contributors should follow [Contributing Guidelines](CONTRIBUTING.md) and
Expand Down
26 changes: 26 additions & 0 deletions benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package monoton_test

import (
"testing"

"github.com/mustafaturan/monoton/v2"
"github.com/mustafaturan/monoton/v2/sequencer"
)

func BenchmarkNext(b *testing.B) {
b.ReportAllocs()

m, _ := monoton.New(sequencer.NewMillisecond(), 0, 0)
for n := 0; n < b.N; n++ {
_ = m.Next()
}
}

func BenchmarkNextBytes(b *testing.B) {
b.ReportAllocs()

m, _ := monoton.New(sequencer.NewMillisecond(), 0, 0)
for n := 0; n < b.N; n++ {
_ = m.NextBytes()
}
}
38 changes: 22 additions & 16 deletions encoder/encoder.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019 Mustafa Turan. All rights reserved.
// Copyright 2021 Mustafa Turan. All rights reserved.
// Use of this source code is governed by a Apache License 2.0 license that can
// be found in the LICENSE file.

Expand All @@ -7,20 +7,17 @@
//
package encoder

import (
"fmt"
"strconv"
)

const (
maxBase62 = uint64(62)
mapping = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
)

// ToBase62 converts int types to Base62 encoded string
func ToBase62(u uint64) string {
var a [65]byte // 64 + 1: +1 for sign of 64bit value in base 2
i := len(a)
// ToBase62WithPaddingZeros converts int types to Base62 encoded byte array
// with padding zeros
func ToBase62WithPaddingZeros(u uint64, length int) []byte {
const size = 65 // 64 + 1: +1 for sign of 64bit value in base 2
var a [size]byte
i := size
for u >= maxBase62 {
i--
// Avoid using r = a%b in addition to q = a/maxBase62
Expand All @@ -33,12 +30,21 @@ func ToBase62(u uint64) string {
// when u < maxBase62
i--
a[i] = mapping[u]
return string(a[i:])
for i > size-length {
i--
a[i] = mapping[0]
}
return a[i:]
}

// ToBase62WithPaddingZeros converts int types to Base62 encoded string with
// padding zeros
func ToBase62WithPaddingZeros(u uint64, padding int64) string {
formatter := "%+0" + strconv.FormatInt(padding, 10) + "s"
return fmt.Sprintf(formatter, ToBase62(u))
// Base62ByteSize returns the minimum byte size length requirement to allocate
// the given unsigned integer's value
func Base62ByteSize(u uint64) int {
i := 0
for u >= maxBase62 {
i++
q := u / maxBase62
u = q
}
return i + 1
}
45 changes: 25 additions & 20 deletions encoder/encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,46 @@ package encoder

import "testing"

func TestToBase62(t *testing.T) {
func TestToBase62WithPaddingZeros(t *testing.T) {
tests := []struct {
val uint64
want string
val uint64
padding int
want string
}{
{1, "1"},
{63, "11"},
{1<<64 - 1, "LygHa16AHYF"},
{1, 11, "00000000001"},
{63, 2, "11"},
{124, 3, "020"},
{125, 4, "0021"},
{1<<64 - 1, 11, "LygHa16AHYF"},
}

msg := "ToBase62(%d) = %v, but returned %v"
msg := "ToBase62WithPaddingZeros(%d, %d) = %v, but returned %v"
for _, test := range tests {
got := ToBase62(test.val)
if got != test.want {
t.Errorf(msg, test.val, test.want, got)
got := ToBase62WithPaddingZeros(test.val, test.padding)
if string(got) != test.want {
t.Errorf(msg, test.val, test.padding, test.want, string(got))
}
}
}

func TestToBase62WithPaddingZeros(t *testing.T) {
func TestBase62ByteSize(t *testing.T) {
tests := []struct {
val uint64
padding int64
want string
val uint64
want int
}{
{1, 11, "00000000001"},
{63, 11, "00000000011"},
{1<<64 - 1, 11, "LygHa16AHYF"},
{0, 1},
{1, 1},
{63, 2},
{124, 2},
{125, 2},
{1<<64 - 1, 11},
}

msg := "ToBase62WithPaddingZeros(%d, %d) = %v, but returned %v"
msg := "Base62ByteSize(%d) = %d, but returned %v"
for _, test := range tests {
got := ToBase62WithPaddingZeros(test.val, test.padding)
got := Base62ByteSize(test.val)
if got != test.want {
t.Errorf(msg, test.val, test.padding, test.want, got)
t.Errorf(msg, test.val, test.want, got)
}
}
}
6 changes: 3 additions & 3 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 Mustafa Turan. All rights reserved.
// Copyright 2021 Mustafa Turan. All rights reserved.
// Use of this source code is governed by a Apache License 2.0 license that can
// be found in the LICENSE file.

Expand All @@ -8,8 +8,8 @@ import (
"fmt"
"time"

"github.com/mustafaturan/monoton"
"github.com/mustafaturan/monoton/sequencer"
"github.com/mustafaturan/monoton/v2"
"github.com/mustafaturan/monoton/v2/sequencer"
)

func ExampleNew() {
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/mustafaturan/monoton
module github.com/mustafaturan/monoton/v2

go 1.9
go 1.16
Loading

0 comments on commit 955f1b3

Please sign in to comment.