The Real Initial Commit.

This commit is contained in:
Bruce Marriner 2016-06-01 14:59:26 -05:00
parent 9befef8908
commit cfa7691d14
3 changed files with 131 additions and 290 deletions

View File

@ -1,23 +1,43 @@
# flake
Very simple snowflake generator
====
[![GoDoc](https://godoc.org/github.com/bwmarrin/flake?status.svg)](https://godoc.org/github.com/bwmarrin/flake) [![Go report](http://goreportcard.com/badge/bwmarrin/flake)](http://goreportcard.com/report/bwmarrin/flake) [![Build Status](https://travis-ci.org/bwmarrin/flake.svg?branch=master)](https://travis-ci.org/bwmarrin/flake)
[![Discord Gophers](https://img.shields.io/badge/Discord%20Gophers-%23flake.svg)](https://discord.gg/0f1SbxBZjYoCtNPP)
Finally, hit my goal of 244ns/op.
flake is a Google Go (golang) package that provides a very simple twitter
snowflake generator.
But, interesting comparison of different methods.
====
flake is a [Go](https://golang.org/) package that provides a very simple twitter
snowflake ID generator along with several functions to convert an ID into
different formats.
## Getting Started
### Installing
This assumes you already have a working Go environment, if not please see
[this page](https://golang.org/doc/install) first.
```sh
go get github.com/bwmarrin/flake
```
time go test -bench=.
testing: warning: no tests to run
PASS
BenchmarkGenerateChan-12 3000000 503 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateChanParallel-12 2000000 743 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateNoSleep-12 5000000 244 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateNoSleepLock-12 5000000 283 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateNoSleepLockParallel-12 5000000 348 ns/op 0 B/op 0 allocs/op
BenchmarkGenerate-12 5000000 283 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateLocks-12 5000000 293 ns/op 0 B/op 0 allocs/op
BenchmarkGenerateLocksParallel-12 5000000 368 ns/op 0 B/op 0 allocs/op
ok _/home/bruce/flake 15.291s
go test -bench=. 16.88s user 7.37s system 151% cpu 15.981 total
### Usage
Import the package into your project.
```go
import "github.com/bwmarrin/flake"
```
Construct a new flake Node that can be used to generate snowflake IDs then call
the Generate method to get a unique ID. The only argument to the NewNode()
method is a Node number. Each node you create must have it's own unique
Node number. A node number can be any number from 0 to 1023.
```go
node, err := flake.NewNode(1)
id := node.Generate()
fmt.Printf("ID: %d, %s\n", id, id.String())
```

268
flake.go
View File

@ -1,102 +1,74 @@
// simple generator for..
// Twitter Snowflake with custom definable epoch
// Package flake provides a very simple Twitter Snowflake generator and parser.
// You can optionally set a custom epoch for you use.
package flake
import "encoding/base64"
import "fmt"
import "strconv"
import "sync"
import "time"
const (
TimeBits = 41
NodeBits = 10
StepBits = 12
TimeMask int64 = -1 ^ (-1 << TimeBits)
NodeMask int64 = -1 ^ (-1 << NodeBits)
StepMask int64 = -1 ^ (-1 << StepBits)
NodeMax = -1 ^ (-1 << NodeBits)
TimeShift uint8 = NodeBits + StepBits
NodeShift uint8 = StepBits
import (
"encoding/base64"
"fmt"
"strconv"
"strings"
"sync"
"time"
)
const (
timeBits = 41
nodeBits = 10
stepBits = 12
timeMask int64 = -1 ^ (-1 << timeBits)
nodeMask int64 = -1 ^ (-1 << nodeBits)
stepMask int64 = -1 ^ (-1 << stepBits)
nodeMax = -1 ^ (-1 << nodeBits)
timeShift uint8 = nodeBits + stepBits
nodeShift uint8 = stepBits
)
// Epoch is set to the twitter snowflake epoch of 2006-03-21:20:50:14 GMT
// You may customize this to set a different epoch for your application.
var Epoch int64 = 1288834974657
// A Node struct holds the basic information needed for a flake generator node
type Node struct {
sync.Mutex // TODO: find a way to avoid locks?
// configurable values
epoch int64
node int64
// runtime tracking values
lastTime int64
step int64
sync.Mutex
time int64
node int64
step int64
}
// Start a new Flake factory / server node using the given node number
// sets with default settings, use helper functions to change
// node, epoch, etc.
func NewFlakeNode(node int64) (*Node, error) {
// An ID is a custom type used for a snowflake ID. This is used so we can
// attach methods onto the ID.
type ID int64
if node < 0 || node > NodeMax {
return nil, fmt.Errorf("Invalid node number.")
// NewNode returns a new Flake node that can be used to generate flake IDs
func NewNode(node int64) (*Node, error) {
if node < 0 || node > nodeMax {
return nil, fmt.Errorf("Node number must be between 0 and 1023")
}
return &Node{
epoch: time.Date(2016, 1, 0, 0, 0, 0, 0, time.UTC).UnixNano() / int64(time.Millisecond),
node: node,
lastTime: 0,
step: 0,
time: 0,
node: node,
step: 0,
}, nil
}
// high performance generator
// well, that w as the idea...
func (n *Node) Generator(c chan Flake) {
ticker := time.NewTicker(time.Millisecond)
now := int64(time.Now().UnixNano() / 1000000)
for {
n.step = 0
select {
case c <- Flake((now-n.epoch)<<TimeShift | (n.node << NodeShift) | (n.step)):
n.step = (n.step + 1) & StepMask
if n.step == 0 {
// wait for ticker..
// haha, funny, this isn't fast enough to ever get here.
}
case <-ticker.C:
now++
// continue
}
}
}
// Return a freshly generated Flake ID
func (n *Node) LockedGenerate() (Flake, error) {
// Generate creates and returns a unique snowflake ID
func (n *Node) Generate() (ID, error) {
n.Lock()
defer n.Unlock()
now := time.Now().UnixNano() / 1000000
if n.lastTime > now {
return 0, fmt.Errorf("Invalid system time.")
}
if n.lastTime == now {
n.step = (n.step + 1) & StepMask
if n.time == now {
n.step = (n.step + 1) & stepMask
if n.step == 0 {
for now <= n.lastTime {
time.Sleep(100 * time.Microsecond)
for now <= n.time {
now = time.Now().UnixNano() / 1000000
}
}
@ -104,129 +76,75 @@ func (n *Node) LockedGenerate() (Flake, error) {
n.step = 0
}
n.lastTime = now
n.time = now
return Flake((now-n.epoch)<<TimeShift |
(n.node << NodeShift) |
return ID((now-Epoch)<<timeShift |
(n.node << nodeShift) |
(n.step),
), nil
}
// Return a freshly generated Flake ID
func (n *Node) Generate() (Flake, error) {
now := time.Now().UnixNano() / 1000000
if n.lastTime > now {
return 0, fmt.Errorf("Invalid system time.")
}
if n.lastTime == now {
n.step = (n.step + 1) & StepMask
if n.step == 0 {
for now <= n.lastTime {
time.Sleep(100 * time.Microsecond)
now = time.Now().UnixNano() / 1000000
}
}
} else {
n.step = 0
}
n.lastTime = now
return Flake((now-n.epoch)<<TimeShift |
(n.node << NodeShift) |
(n.step),
), nil
// Int64 returns an int64 of the snowflake ID
func (f ID) Int64() int64 {
return int64(f)
}
// Return a freshly generated Flake ID
func (n *Node) GenerateNoSleep() (Flake, error) {
now := time.Now().UnixNano() / 1000000
if n.lastTime == now {
n.step = (n.step + 1) & StepMask
if n.step == 0 {
for now <= n.lastTime {
now = time.Now().UnixNano() / 1000000
}
}
} else {
n.step = 0
}
n.lastTime = now
return Flake((now-n.epoch)<<TimeShift |
(n.node << NodeShift) |
(n.step),
), nil
}
// Return a freshly generated Flake ID
func (n *Node) GenerateNoSleepLock() (Flake, error) {
n.Lock()
defer n.Unlock()
now := time.Now().UnixNano() / 1000000
if n.lastTime == now {
n.step = (n.step + 1) & StepMask
if n.step == 0 {
for now <= n.lastTime {
now = time.Now().UnixNano() / 1000000
}
}
} else {
n.step = 0
}
n.lastTime = now
return Flake((now-n.epoch)<<TimeShift |
(n.node << NodeShift) |
(n.step),
), nil
}
type Flake int64
type Flakes []*Flake
func (f Flake) String() string {
// String returns a string of the snowflake ID
func (f ID) String() string {
return fmt.Sprintf("%d", f)
}
func (f Flake) Base2() string {
// Base2 returns a string base2 of the snowflake ID
func (f ID) Base2() string {
return strconv.FormatInt(int64(f), 2)
}
func (f Flake) Base36() string {
// Base36 returns a base36 string of the snowflake ID
func (f ID) Base36() string {
return strconv.FormatInt(int64(f), 36)
}
func (f Flake) Base64() string {
return base64.StdEncoding.EncodeToString(f.Byte())
// Base64 returns a base64 string of the snowflake ID
func (f ID) Base64() string {
return base64.StdEncoding.EncodeToString(f.Bytes())
}
func (f Flake) Byte() []byte {
// Bytes returns a byte array of the snowflake ID
func (f ID) Bytes() []byte {
return []byte(f.String())
}
func (f Flake) Time() int64 {
// ugh.. TODO
// epoch is supposed to be configurable.....
Epoch := time.Date(2016, 1, 0, 0, 0, 0, 0, time.UTC).UnixNano() / int64(time.Millisecond)
// Time returns an int64 unix timestamp of the snowflake ID time
func (f ID) Time() int64 {
return (int64(f) >> 22) + Epoch
}
func (f Flake) Node() int64 {
// Node returns an int64 of the snowflake ID node number
func (f ID) Node() int64 {
return int64(f) & 0x00000000003FF000 >> 12
}
func (f Flake) Sequence() int64 {
// Step returns an int64 of the snowflake step (or sequence) number
func (f ID) Step() int64 {
return int64(f) & 0x0000000000000FFF
}
// MarshalJSON returns a json byte array string of the snowflake ID.
func (f ID) MarshalJSON() ([]byte, error) {
return []byte(`"` + f.String() + `"`), nil
}
// UnmarshalJSON converts a json byte array of a snowflake ID into an ID type.
func (f *ID) UnmarshalJSON(b []byte) error {
s := strings.Replace(string(b), `"`, ``, 2)
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
fmt.Println(err)
return err
}
*f = ID(i)
return nil
}

View File

@ -4,41 +4,9 @@ import (
"testing"
)
//////////////////////////////////////////////////////////////////////////////
func BenchmarkGenerate(b *testing.B) {
// Benchmarks Presence Update event with fake data.
func BenchmarkGenerateChan(b *testing.B) {
node, _ := NewFlakeNode(1)
c := make(chan Flake)
go node.Generator(c)
b.ReportAllocs()
for n := 0; n < b.N; n++ {
<-c
}
}
// Benchmarks Presence Update event with fake data.
func BenchmarkGenerateChanParallel(b *testing.B) {
node, _ := NewFlakeNode(1)
c := make(chan Flake)
go node.Generator(c)
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
<-c
}
})
}
// Benchmarks Presence Update event with fake data.
func BenchmarkGenerateNoSleep(b *testing.B) {
node, _ := NewFlakeNode(1)
node, _ := NewNode(1)
b.ReportAllocs()
for n := 0; n < b.N; n++ {
@ -46,68 +14,3 @@ func BenchmarkGenerateNoSleep(b *testing.B) {
}
}
// Benchmarks Presence Update event with fake data.
func BenchmarkGenerateNoSleepLock(b *testing.B) {
node, _ := NewFlakeNode(1)
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_, _ = node.GenerateNoSleepLock()
}
}
// Benchmarks Presence Update event with fake data.
func BenchmarkGenerateNoSleepLockParallel(b *testing.B) {
node, _ := NewFlakeNode(1)
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _ = node.GenerateNoSleepLock()
}
})
}
// Benchmarks Presence Update event with fake data.
// Benchmarks Presence Update event with fake data.
func BenchmarkGenerate(b *testing.B) {
node, _ := NewFlakeNode(1)
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_, _ = node.Generate()
}
}
// Benchmarks Presence Update event with fake data.
func BenchmarkGenerateLocks(b *testing.B) {
node, _ := NewFlakeNode(1)
b.ReportAllocs()
for n := 0; n < b.N; n++ {
_, _ = node.LockedGenerate()
}
}
// Benchmarks Presence Update event with fake data.
func BenchmarkGenerateLocksParallel(b *testing.B) {
node, _ := NewFlakeNode(1)
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _ = node.LockedGenerate()
}
})
}