ddtxn

package module
v0.0.0-...-4e86714 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2015 License: MIT Imports: 23 Imported by: 0

README

This is the code for Doppel, an in-memory key/value transactional database. WARNING: This is research code, and does not include durability or RPC. Use at your own risk.

Doppel's design is described in "Phase Reconciliation for Contended In-Memory Transactions", presented at OSDI 2014.

Get the code:

go get github.com/narula/dlog
go get github.com/narula/prof
go get github.com/narula/wfmutex
go get github.com/narula/ddtxn

To run the tests, use go test.

Clone the list-cpus repo and add it to your path:

git clone git@github.com:narula/list-cpus.git

Add $GOPATH/bin and the list-cpus repo to your PATH environment variable

Run a benchmark:

cd $GOPATH/src/github.com/narula/ddtxn/benchmarks
go install ./single
python bm.py --exp=single --rlock --ncores=N

Documentation

Index

Constants

View Source
const (
	NUM_USERS      = 1000000
	NUM_CATEGORIES = 20
	NUM_REGIONS    = 62
	NUM_ITEMS      = 533000
	BIDS_PER_ITEM  = 10
	NUM_COMMENTS   = 506000
	BUY_NOW        = .1 * NUM_ITEMS
	FEEDBACK       = .95 * NUM_ITEMS
)
View Source
const (
	BUMP_EPOCH_MS = 80
	EPOCH_INCR    = 1 << 32
	TXID_MASK     = 0x00000000ffffffff
	CLEAR_TID     = 0xffffffff00000000
)
View Source
const (
	SPLIT = iota
	MERGE
	JOIN
)

Phases

View Source
const (
	SUM = iota
	MAX
	WRITE
	LIST
	OOWRITE
)
View Source
const (
	DOPPEL = iota
	OCC
	LOCKING
)
View Source
const (
	BUFFER     = 100000
	START_SIZE = 1000000
	TIMES      = 10
)
View Source
const (
	// Transactions
	D_BUY = iota
	D_BUY_AND_READ
	D_READ_ONE
	D_READ_TWO
	D_INCR_ONE
	D_ATOMIC_INCR_ONE

	RUBIS_BID         // 6   7%
	RUBIS_VIEWBIDHIST // 7   3%
	RUBIS_BUYNOW      // 8   3%
	RUBIS_COMMENT     // 9   1%
	RUBIS_NEWITEM     // 10  4%
	RUBIS_PUTBID      // 11  10%
	RUBIS_PUTCOMMENT  // 12  1%
	RUBIS_REGISTER    // 13  4%
	RUBIS_SEARCHCAT   // 14  27%
	RUBIS_SEARCHREG   // 15  12%
	RUBIS_VIEW        // 16  23%
	RUBIS_VIEWUSER    // 17  4%

	BIG_INCR
	BIG_RW
	LAST_TXN

	// Stats
	NABORTS
	NENOKEY
	NSTASHED
	NENORETRY
	NSAMPLES
	NGETKEYCALLS
	NDDWRITES
	NO_LOCK
	NFAIL_VERIFY
	NLOCKED
	NDIDSTASHED
	NREADABORTS
	LAST_STAT
)
View Source
const (
	CHUNKS = 256
)
View Source
const (
	DEFAULT_LIST_SIZE = 10
)

Variables

View Source
var (
	ENOKEY   = errors.New("doppel: no key")
	EABORT   = errors.New("doppel: abort")
	ESTASH   = errors.New("doppel: stash")
	ENORETRY = errors.New("app error: no retry")
	EEXISTS  = errors.New("doppel: trying to create key which already exists")
)
View Source
var Allocate = flag.Bool("allocate", true, "Allocate results")
View Source
var AlwaysSplit = flag.Bool("split", false, "Split every piece of data\n")
View Source
var ConflictWeight = flag.Float64("cw", 2.0, "Weight given to conflicts over writes\n")
View Source
var Conflicts = flag.Bool("conflicts", false, "Measure conflicts\n")
View Source
var CountKeys = flag.Bool("ck", false, "Count keys accessed")
View Source
var GStore = flag.Bool("gstore", false, "Use Gotomic Hash Map instead of Go maps\n")
View Source
var Latency = flag.Bool("latency", false, "Measure latency")
View Source
var NextEpoch int64
View Source
var Nfast int64
View Source
var NoConflictType = flag.Int("noconflict", -1, "Type of operation NOT to record conflicts on")
View Source
var PhaseLength = flag.Int("phase", 20, "Phase length in milliseconds, default 20")
View Source
var RMoved int64
View Source
var ReadWeight = flag.Float64("rw", 0.5, "Weight given to reads over stashes\n")
View Source
var SampleRate = flag.Int64("sr", 500, "Sample every sr transactions\n")
View Source
var Spinlock = flag.Bool("spinlock", false, "Use spinlocks for 2PL\n")
View Source
var SysType = flag.Int("sys", DOPPEL, "Type of system to run\n")
View Source
var Time_in_IE time.Duration
View Source
var Time_in_IE1 time.Duration
View Source
var TriggerCount = flag.Int("trigger", 100000, "How long the queue can get before triggering a phase change\n")
View Source
var UseRLocks = flag.Bool("rlock", true, "Use Rlocks\n")
View Source
var Version = flag.Int("v", 0, "Version counter to help distinguish runs\n")
View Source
var WMoved int64
View Source
var WRRatio = flag.Float64("wr", 2.0, "Ratio of sampled write conflicts and sampled writes to sampled reads at which to move a piece of data to split.  Default 3")

Functions

func CollectOne

func CollectOne(w *Worker) int64

func GetTxns

func GetTxns(bidrate float64) []float64

func IsRead

func IsRead(t int) bool

func PrintLockCounts

func PrintLockCounts(s *Store)

func PrintStats

func PrintStats(out string, stats []int64, f *os.File, coord *Coordinator, s *Store, nb int)

func RandN

func RandN(seed *uint32, n uint32) uint32

func Randstr

func Randstr(sz int) string

func StddevChunks

func StddevChunks(nc []int64) (int64, float64)

func StddevKeys

func StddevKeys(nc []int64) (int64, float64)

func UndoCKey

func UndoCKey(k Key) (uint64, rune)

func Validate

func Validate(c *Coordinator, s *Store, nkeys int, nproducts int, val []int32, n int) bool

func WriteChunkStats

func WriteChunkStats(s *Store, f *os.File)

func WriteCountKeyStats

func WriteCountKeyStats(coord *Coordinator, nb int, f *os.File)

Types

type BRecord

type BRecord struct {
	// contains filtered or unexported fields
}

func MakeBR

func MakeBR(k Key, val Value, kt KeyType) *BRecord

func (*BRecord) AddOneToRecord

func (br *BRecord) AddOneToRecord(e Entry)

func (*BRecord) Apply

func (br *BRecord) Apply(val Value)

Used during "merge" phase, along with br.mu

func (*BRecord) IsUnlocked

func (br *BRecord) IsUnlocked() (bool, uint64)

func (*BRecord) Lock

func (br *BRecord) Lock() (bool, uint64)

func (*BRecord) Own

func (br *BRecord) Own(last uint64) bool

func (*BRecord) SLock

func (br *BRecord) SLock()

func (*BRecord) SRLock

func (br *BRecord) SRLock()

func (*BRecord) SRUnlock

func (br *BRecord) SRUnlock()

func (*BRecord) SUnlock

func (br *BRecord) SUnlock()

func (*BRecord) Unlock

func (br *BRecord) Unlock(tid TID)

func (*BRecord) Value

func (br *BRecord) Value() Value

func (*BRecord) Verify

func (br *BRecord) Verify(last uint64) bool

type Bid

type Bid struct {
	ID     uint64
	Item   uint64
	Bidder uint64
	Price  int32
}

type BuyNow

type BuyNow struct {
	BuyerID uint64
	ItemID  uint64
	Qty     uint64
	Date    int
}

type Candidates

type Candidates struct {
	// contains filtered or unexported fields
}

m is very big; it should have every key the worker sampled. h is a heap of all keys we deemed interesting enough to add to the heap. This includes keys where the ratio is high enough to consider moving the key to dd, but also keys that are already dd. We add their statistics changes to the heap to be merged in on the next stats computation.

Since we limit what we add to h, it doesn't really have to be a heap. But one could imagine eliminating m and only looking at the top set of things in the heap instead.

func (*Candidates) Conflict

func (c *Candidates) Conflict(k Key, br *BRecord, op KeyType)

func (*Candidates) Merge

func (c *Candidates) Merge(c2 *Candidates)

func (*Candidates) Print

func (c *Candidates) Print()

func (*Candidates) Read

func (c *Candidates) Read(k Key, br *BRecord)

func (*Candidates) ReadWrite

func (c *Candidates) ReadWrite(k Key, br *BRecord)

func (*Candidates) Stash

func (c *Candidates) Stash(k Key)

func (*Candidates) Write

func (c *Candidates) Write(k Key, br *BRecord, op KeyType)

This is only used when a key is in split mode (can't count conflicts anymore because they don't happen). Make it count for more.

type Chunk

type Chunk struct {
	sync.RWMutex
	// contains filtered or unexported fields
}

type Comment

type Comment struct {
	ID      uint64
	From    uint64
	To      uint64
	Rating  uint64
	Item    uint64
	Date    uint64
	Comment string
}

type Coordinator

type Coordinator struct {
	Workers []*Worker

	Coordinate            bool
	PotentialPhaseChanges int64
	Done                  chan chan bool
	Accelerate            chan bool

	StartTime      time.Time
	Finished       []bool
	TotalCoordTime time.Duration
	GoTime         time.Duration
	ReadTime       time.Duration
	MergeTime      time.Duration
	// contains filtered or unexported fields
}

func NewCoordinator

func NewCoordinator(n int, s *Store) *Coordinator

func (*Coordinator) Finish

func (c *Coordinator) Finish()

func (*Coordinator) GetEpoch

func (c *Coordinator) GetEpoch() TID

func (*Coordinator) IncrementEpoch

func (c *Coordinator) IncrementEpoch(force bool)

func (*Coordinator) Latency

func (c *Coordinator) Latency() (string, string)

func (*Coordinator) NextGlobalTID

func (c *Coordinator) NextGlobalTID() TID

func (*Coordinator) Process

func (c *Coordinator) Process()

func (*Coordinator) Stats

func (c *Coordinator) Stats() (map[Key]bool, map[Key]bool)

type ETransaction

type ETransaction interface {
	Reset()
	Read(k Key) (*BRecord, error)
	WriteInt32(k Key, a int32, op KeyType) error
	WriteList(k Key, l Entry, op KeyType) error
	WriteOO(k Key, a int32, v Value, op KeyType) error
	Write(k Key, v Value, op KeyType)
	Abort() TID
	Commit() TID
	SetPhase(int)
	GetPhase() int
	Store() *Store
	Worker() *Worker

	// Tell 2PL I am going to read and potentially write this key.
	// This is because I don't know how to upgrade locks.
	MaybeWrite(k Key)

	// Tell Doppel not to count this transaction's reads and writes
	// when deciding if records should be split.
	NoCount()

	// Get a unique key; give it up
	UID(rune) uint64
	RelinquishKey(uint64, rune)
}

type Entry

type Entry struct {
	// contains filtered or unexported fields
}

func AddOneToList

func AddOneToList(lst []Entry, e Entry) []Entry

type Exp2

type Exp2 struct {
	// contains filtered or unexported fields
}

func MakeExp

func MakeExp(n int) *Exp2

func (*Exp2) Exp

func (e *Exp2) Exp(n int) uint32

type Item

type Item struct {
	ID        uint64
	Seller    uint64
	Qty       uint64
	Startdate int
	Enddate   int
	Name      string
	Desc      string
	Sprice    uint64
	Rprice    uint64
	Buynow    uint64
	Dur       uint64
	Categ     uint64
}

type Key

type Key gotomic.Key

type Key [16]byte

func BidKey

func BidKey(id uint64) Key

func BidsPerItemKey

func BidsPerItemKey(item uint64) Key

func BuyNowKey

func BuyNowKey(item uint64) Key

func CKey

func CKey(x uint64, ch rune) Key

func CommentKey

func CommentKey(item uint64) Key

func ItemKey

func ItemKey(item uint64) Key

func ItemsByCatKey

func ItemsByCatKey(item uint64) Key

func ItemsByRegKey

func ItemsByRegKey(region uint64, categ uint64) Key

func MaxBidBidderKey

func MaxBidBidderKey(item uint64) Key

func MaxBidKey

func MaxBidKey(item uint64) Key

func NicknameKey

func NicknameKey(bidder uint64) Key

func NumBidsKey

func NumBidsKey(item uint64) Key

func PairBidKey

func PairBidKey(bidder uint64, product uint64) Key

func PairKey

func PairKey(x uint32, y uint32, ch rune) Key

func ProductKey

func ProductKey(product int) Key

func RatingKey

func RatingKey(user uint64) Key

func SKey

func SKey(s string) Key

func TKey

func TKey(x uint64, y uint64) Key

func UserKey

func UserKey(bidder uint64) Key

func (Key) String

func (k Key) String() string

type KeyGenFunc

type KeyGenFunc func(uint64) Key

type KeyType

type KeyType int

type LTransaction

type LTransaction struct {
	// contains filtered or unexported fields
}

Not threadsafe. Tracks execution of transaction.

func StartLTransaction

func StartLTransaction(w *Worker) *LTransaction

func (*LTransaction) Abort

func (tx *LTransaction) Abort() TID

func (*LTransaction) Commit

func (tx *LTransaction) Commit() TID

func (*LTransaction) GetPhase

func (tx *LTransaction) GetPhase() int

func (*LTransaction) MaybeWrite

func (tx *LTransaction) MaybeWrite(k Key)

This is when I am reading a key and I might write it later; acquire the write lock *before* the read.

func (*LTransaction) NoCount

func (tx *LTransaction) NoCount()

func (*LTransaction) Read

func (tx *LTransaction) Read(k Key) (*BRecord, error)

func (*LTransaction) RelinquishKey

func (tx *LTransaction) RelinquishKey(n uint64, r rune)

func (*LTransaction) Reset

func (tx *LTransaction) Reset()

func (*LTransaction) SetPhase

func (tx *LTransaction) SetPhase(p int)

func (*LTransaction) Store

func (tx *LTransaction) Store() *Store

func (*LTransaction) UID

func (tx *LTransaction) UID(f rune) uint64

func (*LTransaction) Worker

func (tx *LTransaction) Worker() *Worker

func (*LTransaction) Write

func (tx *LTransaction) Write(k Key, v Value, op KeyType)

func (*LTransaction) WriteInt32

func (tx *LTransaction) WriteInt32(k Key, a int32, op KeyType) error

func (*LTransaction) WriteList

func (tx *LTransaction) WriteList(k Key, l Entry, op KeyType) error

func (*LTransaction) WriteOO

func (tx *LTransaction) WriteOO(k Key, a int32, v Value, op KeyType) error

type LocalStore

type LocalStore struct {
	Ncopy int64
	// contains filtered or unexported fields
}

func NewLocalStore

func NewLocalStore(s *Store) *LocalStore

func (*LocalStore) Apply

func (ls *LocalStore) Apply(key Key, key_type KeyType, v Value, op KeyType)

func (*LocalStore) ApplyInt32

func (ls *LocalStore) ApplyInt32(key Key, key_type KeyType, a int32, op KeyType)

func (*LocalStore) ApplyList

func (ls *LocalStore) ApplyList(key Key, entry Entry)

func (*LocalStore) ApplyOO

func (ls *LocalStore) ApplyOO(key Key, a int32, v Value)

func (*LocalStore) Merge

func (ls *LocalStore) Merge()

type OTransaction

type OTransaction struct {
	// contains filtered or unexported fields
}

Tracks execution of transaction.

func StartOTransaction

func StartOTransaction(w *Worker) *OTransaction

func (*OTransaction) Abort

func (tx *OTransaction) Abort() TID

func (*OTransaction) Commit

func (tx *OTransaction) Commit() TID

func (*OTransaction) GetPhase

func (tx *OTransaction) GetPhase() int

func (*OTransaction) MaybeWrite

func (tx *OTransaction) MaybeWrite(k Key)

func (*OTransaction) NoCount

func (tx *OTransaction) NoCount()

func (*OTransaction) Read

func (tx *OTransaction) Read(k Key) (*BRecord, error)

func (*OTransaction) RelinquishKey

func (tx *OTransaction) RelinquishKey(n uint64, r rune)

func (*OTransaction) Reset

func (tx *OTransaction) Reset()

func (*OTransaction) SetPhase

func (tx *OTransaction) SetPhase(p int)

func (*OTransaction) Store

func (tx *OTransaction) Store() *Store

func (*OTransaction) UID

func (tx *OTransaction) UID(f rune) uint64

func (*OTransaction) Worker

func (tx *OTransaction) Worker() *Worker

func (*OTransaction) Write

func (tx *OTransaction) Write(k Key, v Value, op KeyType)

func (*OTransaction) WriteInt32

func (tx *OTransaction) WriteInt32(k Key, a int32, op KeyType) error

func (*OTransaction) WriteList

func (tx *OTransaction) WriteList(k Key, l Entry, op KeyType) error

func (*OTransaction) WriteOO

func (tx *OTransaction) WriteOO(k Key, a int32, v Value, op KeyType) error

type OneStat

type OneStat struct {
	// contains filtered or unexported fields
}

type Overwrite

type Overwrite struct {
	// contains filtered or unexported fields
}

type Query

type Query struct {
	TXN int
	W   chan struct {
		R *Result
		E error
	}
	T  TID
	K1 Key
	K2 Key
	A  int32
	U1 uint64
	U2 uint64
	U3 uint64
	U4 uint64
	U5 uint64
	U6 uint64
	U7 uint64
	S1 string
	S2 string
	I  int
	TS time.Time
	S  time.Time
}

I tried keeping a slice of interfaces; the reflection was costly. Hard code in random parameter types to re-use for now.

type ReadKey

type ReadKey struct {
	// contains filtered or unexported fields
}

type Rec

type Rec struct {
	// contains filtered or unexported fields
}

type Result

type Result struct {
	V Value
}

func AtomicIncr

func AtomicIncr(t Query, tx ETransaction) (*Result, error)

This is special. It does not use the Commit() protocol, instead it just performs atomic increments on keys. It is impossible to abort, and no stats are kept to indicate this key should be in split phase or not. This shouldn't be run in a mix with any other transaction types.

func BigIncrTxn

func BigIncrTxn(t Query, tx ETransaction) (*Result, error)

func BigRWTxn

func BigRWTxn(t Query, tx ETransaction) (*Result, error)

Version of Big that puts keys in read set (doesn't rely on commutativity)

func BuyAndReadTxn

func BuyAndReadTxn(t Query, tx ETransaction) (*Result, error)

func BuyTxn

func BuyTxn(t Query, tx ETransaction) (*Result, error)

func IncrTxn

func IncrTxn(t Query, tx ETransaction) (*Result, error)

func NewItemTxn

func NewItemTxn(t Query, tx ETransaction) (*Result, error)

func PutBidTxn

func PutBidTxn(t Query, tx ETransaction) (*Result, error)

func PutCommentTxn

func PutCommentTxn(t Query, tx ETransaction) (*Result, error)

func ReadOneTxn

func ReadOneTxn(t Query, tx ETransaction) (*Result, error)

func ReadTxn

func ReadTxn(t Query, tx ETransaction) (*Result, error)

func RegisterUserTxn

func RegisterUserTxn(t Query, tx ETransaction) (*Result, error)

func SearchItemsCategTxn

func SearchItemsCategTxn(t Query, tx ETransaction) (*Result, error)

func SearchItemsRegionTxn

func SearchItemsRegionTxn(t Query, tx ETransaction) (*Result, error)

func StoreBidTxn

func StoreBidTxn(t Query, tx ETransaction) (*Result, error)

TODO: Check and see if I need more tx.MaybeWrite()s

func StoreBuyNowTxn

func StoreBuyNowTxn(t Query, tx ETransaction) (*Result, error)

func StoreCommentTxn

func StoreCommentTxn(t Query, tx ETransaction) (*Result, error)

func ViewBidHistoryTxn

func ViewBidHistoryTxn(t Query, tx ETransaction) (*Result, error)

func ViewItemTxn

func ViewItemTxn(t Query, tx ETransaction) (*Result, error)

func ViewUserInfoTxn

func ViewUserInfoTxn(t Query, tx ETransaction) (*Result, error)

type RetryHeap

type RetryHeap []Query

func (RetryHeap) Len

func (h RetryHeap) Len() int

func (RetryHeap) Less

func (h RetryHeap) Less(i, j int) bool

func (*RetryHeap) Pop

func (h *RetryHeap) Pop() interface{}

func (*RetryHeap) Push

func (h *RetryHeap) Push(x interface{})

func (RetryHeap) Swap

func (h RetryHeap) Swap(i, j int)

type StatsHeap

type StatsHeap []*OneStat

func (StatsHeap) Len

func (h StatsHeap) Len() int

func (StatsHeap) Less

func (h StatsHeap) Less(i, j int) bool

func (*StatsHeap) Pop

func (h *StatsHeap) Pop() interface{}

func (*StatsHeap) Push

func (h *StatsHeap) Push(x interface{})

func (StatsHeap) Swap

func (h StatsHeap) Swap(i, j int)

type Store

type Store struct {
	NChunksAccessed []int64
	// contains filtered or unexported fields
}

Global data

func NewStore

func NewStore() *Store

func (*Store) CreateKey

func (s *Store) CreateKey(k Key, v Value, kt KeyType) *BRecord

func (*Store) CreateLockedKey

func (s *Store) CreateLockedKey(k Key, kt KeyType) (*BRecord, error)

func (*Store) CreateMuLockedKey

func (s *Store) CreateMuLockedKey(k Key, kt KeyType) (*BRecord, error)

func (*Store) CreateMuRLockedKey

func (s *Store) CreateMuRLockedKey(k Key, kt KeyType) (*BRecord, error)

func (*Store) DD

func (s *Store) DD() map[Key]bool

func (*Store) Get

func (s *Store) Get(k Key) (*BRecord, error)

func (*Store) IsDD

func (s *Store) IsDD(k Key) bool

func (*Store) PrecomputeHashCode

func (s *Store) PrecomputeHashCode(k Key)

func (*Store) Set

func (s *Store) Set(br *BRecord, v Value, op KeyType)

func (*Store) SetInt32

func (s *Store) SetInt32(br *BRecord, v int32, op KeyType)

func (*Store) SetList

func (s *Store) SetList(br *BRecord, ve Entry, op KeyType)

func (*Store) SetOO

func (s *Store) SetOO(br *BRecord, a int32, v Value, op KeyType)

type TID

type TID uint64

type TStore

type TStore struct {
	// contains filtered or unexported fields
}

func TSInit

func TSInit(n int) *TStore

func (*TStore) Add

func (ts *TStore) Add(t Query) bool

type TransactionFunc

type TransactionFunc func(Query, ETransaction) (*Result, error)

type User

type User struct {
	ID       uint64
	Name     string
	Nickname string
	Rating   uint64
	Region   uint64
}

type Value

type Value interface{}

type Worker

type Worker struct {
	sync.RWMutex

	ID int

	E ETransaction

	// Stats
	Nstats       []int64
	Nwait        time.Duration
	Nmerge       time.Duration
	Nmergewait   time.Duration
	Njoin        time.Duration
	Njoinwait    time.Duration
	Nnoticed     time.Duration
	NKeyAccesses []int64

	// Rubis junk
	LastKey      []int
	CurrKey      []int
	PreAllocated bool
	// contains filtered or unexported fields
}

func NewWorker

func NewWorker(id int, s *Store, c *Coordinator) *Worker

func (*Worker) Finished

func (w *Worker) Finished()

func (*Worker) GiveBack

func (w *Worker) GiveBack(n uint64, r rune)

func (*Worker) NextKey

func (w *Worker) NextKey(f rune) uint64

func (*Worker) One

func (w *Worker) One(t Query) (*Result, error)

func (*Worker) PreallocateRubis

func (w *Worker) PreallocateRubis(nx, nb, start int)

func (*Worker) Register

func (w *Worker) Register(fn int, transaction TransactionFunc)

func (*Worker) Store

func (w *Worker) Store() *Store

type WriteKey

type WriteKey struct {
	// contains filtered or unexported fields
}

TODO: Handle writing more than once to a key in one transaction

type Zipf

type Zipf struct {
	// contains filtered or unexported fields
}

A Zipf generates Zipf distributed variates.

func NewZipf

func NewZipf(r *rand.Rand, s float64, v float64, imax uint64) *Zipf

NewZipf returns a Zipf generating variates p(k) on [0, imax] proportional to (v+k)**(-s) where s>1 and k>=0, and v>=1.

func (*Zipf) Uint64

func (z *Zipf) Uint64() uint64

Uint64 returns a value drawn from the Zipf distribution described by the Zipf object.

Directories

Path Synopsis
benchmarks
bid
big
buy

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL