├── .gitignore ├── LICENSE ├── README.md └── buffer ├── EasyJsonBuffer.go └── main ├── main.go └── main_test.go /.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 | 26 | /vendor 27 | /libs 28 | /.idea 29 | *.DS_Store 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Igor Makarov 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 | # Golang benchmarks 2 | 3 | ## Byte buffers 4 | 5 | [Bytes Buffer results](https://omgnull.github.io/go-benchmark/buffer/) 6 | 7 | #### Buffer libs used 8 | * [sync](https://golang.org/pkg/sync/) 9 | * [bpool](https://github.com/oxtoacart/bpool) 10 | * [bytebufferpool](https://github.com/valyala/bytebufferpool) 11 | * [easyjson](https://github.com/mailru/easyjson/blob/master/buffer/pool.go) 12 | 13 | 14 | #### Launch bench 15 | ```sh 16 | $ go test ./... -bench=. -benchmem 17 | ``` 18 | 19 | ##### Buffer tests 20 | ```sh 21 | $ cd buffer/main 22 | $ go build 23 | $ ./main 24 | ``` 25 | 26 | Flags: 27 | * `duration` Test duration in seconds (default 60) 28 | * `method` Function to run; allowed: "generic" "stack" "alloc" "sync" "bpool" "bbpool" (default "generic") 29 | * `out` Filename to write report; Prints into stdout by default 30 | * `queue` Number of goroutines; default is NumCPU (default 8) 31 | -------------------------------------------------------------------------------- /buffer/EasyJsonBuffer.go: -------------------------------------------------------------------------------- 1 | // see https://github.com/mailru/easyjson/blob/master/buffer/pool.go 2 | // 3 | // I need Reset() method to test, bu private functions prevents to embed Buffer 4 | package buffer 5 | 6 | import ( 7 | "sync" 8 | ) 9 | 10 | // PoolConfig contains configuration for the allocation and reuse strategy. 11 | type PoolConfig struct { 12 | StartSize int // Minimum chunk size that is allocated. 13 | PooledSize int // Minimum chunk size that is reused, reusing chunks too small will result in overhead. 14 | MaxSize int // Maximum chunk size that will be allocated. 15 | } 16 | 17 | var config = PoolConfig{ 18 | StartSize: 128, 19 | PooledSize: 512, 20 | MaxSize: 32768, 21 | } 22 | 23 | // Reuse pool: chunk size -> pool. 24 | var buffers = map[int]*sync.Pool{} 25 | 26 | func initBuffers() { 27 | for l := config.PooledSize; l <= config.MaxSize; l *= 2 { 28 | buffers[l] = new(sync.Pool) 29 | } 30 | } 31 | 32 | func init() { 33 | initBuffers() 34 | } 35 | 36 | // Init sets up a non-default pooling and allocation strategy. Should be run before serialization is done. 37 | func Init(cfg PoolConfig) { 38 | config = cfg 39 | initBuffers() 40 | } 41 | 42 | // putBuf puts a chunk to reuse pool if it can be reused. 43 | func putBuf(buf []byte) { 44 | size := cap(buf) 45 | if size < config.PooledSize { 46 | return 47 | } 48 | if c := buffers[size]; c != nil { 49 | c.Put(buf[:0]) 50 | } 51 | } 52 | 53 | // getBuf gets a chunk from reuse pool or creates a new one if reuse failed. 54 | func getBuf(size int) []byte { 55 | if size < config.PooledSize { 56 | return make([]byte, 0, size) 57 | } 58 | 59 | if c := buffers[size]; c != nil { 60 | v := c.Get() 61 | if v != nil { 62 | return v.([]byte) 63 | } 64 | } 65 | return make([]byte, 0, size) 66 | } 67 | 68 | // Buffer is a buffer optimized for serialization without extra copying. 69 | type EJBuffer struct { 70 | 71 | // Buf is the current chunk that can be used for serialization. 72 | Buf []byte 73 | 74 | toPool []byte 75 | bufs [][]byte 76 | } 77 | 78 | // EnsureSpace makes sure that the current chunk contains at least s free bytes, 79 | // possibly creating a new chunk. 80 | func (b *EJBuffer) EnsureSpace(s int) { 81 | if cap(b.Buf)-len(b.Buf) >= s { 82 | return 83 | } 84 | l := len(b.Buf) 85 | if l > 0 { 86 | if cap(b.toPool) != cap(b.Buf) { 87 | // Chunk was reallocated, toPool can be pooled. 88 | putBuf(b.toPool) 89 | } 90 | if cap(b.bufs) == 0 { 91 | b.bufs = make([][]byte, 0, 8) 92 | } 93 | b.bufs = append(b.bufs, b.Buf) 94 | l = cap(b.toPool) * 2 95 | } else { 96 | l = config.StartSize 97 | } 98 | 99 | if l > config.MaxSize { 100 | l = config.MaxSize 101 | } 102 | b.Buf = getBuf(l) 103 | b.toPool = b.Buf 104 | } 105 | 106 | // AppendBytes appends a byte slice to buffer. 107 | func (b *EJBuffer) Write(data []byte) { 108 | for len(data) > 0 { 109 | if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined. 110 | b.EnsureSpace(1) 111 | } 112 | 113 | sz := cap(b.Buf) - len(b.Buf) 114 | if sz > len(data) { 115 | sz = len(data) 116 | } 117 | 118 | b.Buf = append(b.Buf, data[:sz]...) 119 | data = data[sz:] 120 | } 121 | } 122 | 123 | // AppendByte appends a single byte to buffer. 124 | func (b *EJBuffer) WriteByte(data byte) { 125 | if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined. 126 | b.EnsureSpace(1) 127 | } 128 | b.Buf = append(b.Buf, data) 129 | } 130 | 131 | // AppendBytes appends a string to buffer. 132 | func (b *EJBuffer) WriteString(data string) { 133 | for len(data) > 0 { 134 | if cap(b.Buf) == len(b.Buf) { // EnsureSpace won't be inlined. 135 | b.EnsureSpace(1) 136 | } 137 | 138 | sz := cap(b.Buf) - len(b.Buf) 139 | if sz > len(data) { 140 | sz = len(data) 141 | } 142 | 143 | b.Buf = append(b.Buf, data[:sz]...) 144 | data = data[sz:] 145 | } 146 | } 147 | 148 | // Size computes the size of a buffer by adding sizes of every chunk. 149 | func (b *EJBuffer) Size() int { 150 | size := len(b.Buf) 151 | for _, buf := range b.bufs { 152 | size += len(buf) 153 | } 154 | return size 155 | } 156 | 157 | func (b *EJBuffer) Reset() { 158 | for _, buf := range b.bufs { 159 | putBuf(buf) 160 | } 161 | putBuf(b.toPool) 162 | 163 | b.bufs = nil 164 | b.Buf = nil 165 | b.toPool = nil 166 | } 167 | -------------------------------------------------------------------------------- /buffer/main/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "math" 10 | "os" 11 | "os/signal" 12 | "runtime" 13 | "strconv" 14 | "sync" 15 | "sync/atomic" 16 | "syscall" 17 | "time" 18 | 19 | "github.com/oxtoacart/bpool" 20 | "github.com/valyala/bytebufferpool" 21 | 22 | "github.com/omgnull/go-benchmark/buffer" 23 | ) 24 | 25 | type ( 26 | 27 | // Program config 28 | Config struct { 29 | // number of goroutines 30 | queue int 31 | 32 | // function to run 33 | method string 34 | 35 | // filename to write report 36 | out string 37 | 38 | // Run duration 39 | duration time.Duration 40 | } 41 | 42 | // Method type 43 | Fn func() 44 | ) 45 | 46 | var ( 47 | wg sync.WaitGroup 48 | 49 | // Number of methods runs 50 | runs uint64 51 | 52 | // Default config 53 | config = &Config{ 54 | queue: runtime.NumCPU(), 55 | method: "generic", 56 | duration: 60 * time.Second, 57 | } 58 | 59 | // Queue size 60 | queue = make(chan byte) 61 | 62 | // Allowed methods 63 | methods = map[string]Fn{ 64 | "generic": GenericBuf, 65 | "stack": GenericStackBuf, 66 | "alloc": AllocBuf, 67 | "sync": SyncBuf, 68 | "bpool": BpoolBuf, 69 | "bbpool": BBpoolBuf, 70 | "easyjson": EasyJsonBuf, 71 | } 72 | 73 | // Strings written to buf 74 | str = []string{ 75 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit", 76 | "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua", 77 | `Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris 78 | nisi ut aliquip ex ea commodo consequat. 79 | Duis aute irure dolor in reprehenderit in voluptate velit esse cillum 80 | dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, 81 | sunt in culpa qui officia deserunt mollit anim id est laborum`, 82 | "Sed ut perspiciatis", 83 | "sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt", 84 | "Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit", 85 | "laboriosam, nisi ut aliquid ex ea commodi consequatur", 86 | "Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur", 87 | "vel illum qui dolorem eum fugiat quo voluptas nulla pariatur", 88 | } 89 | 90 | // Generic size 91 | sPool = &sync.Pool{ 92 | New: func() interface{} { 93 | return &bytes.Buffer{} 94 | }, 95 | } 96 | 97 | // Default size 98 | bPool = bpool.NewBufferPool(20) 99 | ) 100 | 101 | // Simulate const handling 102 | func main() { 103 | var duration float64 104 | 105 | flag.IntVar(&config.queue, "queue", config.queue, "Number of goroutines; default is NumCPU") 106 | flag.StringVar(&config.method, "method", config.method, fmt.Sprintf("Function to run; allowed:%v", Methods())) 107 | flag.StringVar(&config.out, "out", config.out, "Filename to write report; Prints into stdout by default") 108 | flag.Float64Var(&duration, "duration", float64(config.duration.Seconds()), "Test duration in seconds") 109 | flag.Parse() 110 | 111 | config.queue = int(math.Max(1, float64(config.queue))) 112 | config.duration = time.Duration(math.Max(5, duration)) * time.Second 113 | 114 | run(config) 115 | } 116 | 117 | func run(c *Config) { 118 | queue = make(chan byte, config.queue) 119 | signals := make(chan os.Signal, 1) 120 | reports := make(chan byte, 1) 121 | 122 | method := GetMethod(config.method) 123 | signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP) 124 | 125 | start := time.Now() 126 | 127 | if c.out != "" { 128 | go MakeReport(start, c.out, reports) 129 | } else { 130 | go PrintReport(start, reports) 131 | } 132 | 133 | LOOP: 134 | for { 135 | select { 136 | case <-signals: 137 | // Stop 138 | reports <- 0 139 | break LOOP 140 | case queue <- 0: 141 | wg.Add(1) 142 | atomic.AddUint64(&runs, 1) 143 | go method() 144 | default: 145 | if time.Since(start).Seconds() >= config.duration.Seconds() { 146 | // Stop 147 | reports <- 0 148 | break LOOP 149 | } 150 | } 151 | } 152 | 153 | wg.Wait() 154 | } 155 | 156 | func GetMethod(name string) Fn { 157 | fn, ok := methods[name] 158 | if !ok { 159 | log.Fatalf("Could not find method [%s]; allowed methods are: %v", name, Methods()) 160 | } 161 | return fn 162 | } 163 | 164 | func Methods() string { 165 | var out string 166 | for k := range methods { 167 | out += fmt.Sprintf(" %q", k) 168 | } 169 | return out 170 | } 171 | 172 | func WorkWithBuf(b *bytes.Buffer) { 173 | for _, s := range str { 174 | b.WriteString(s) 175 | } 176 | } 177 | 178 | func WorkWithByteBuf(b *bytebufferpool.ByteBuffer) { 179 | for _, s := range str { 180 | b.WriteString(s) 181 | } 182 | } 183 | 184 | func WorkWithEJBuf(b *buffer.EJBuffer) { 185 | for _, s := range str { 186 | b.WriteString(s) 187 | } 188 | } 189 | 190 | func GenericStackBuf() { 191 | var buf bytes.Buffer 192 | buf.Reset() 193 | for _, s := range str { 194 | buf.WriteString(s) 195 | } 196 | Done() 197 | } 198 | 199 | func GenericBuf() { 200 | var buf bytes.Buffer 201 | buf.Reset() 202 | WorkWithBuf(&buf) 203 | Done() 204 | } 205 | 206 | func AllocBuf() { 207 | buf := new(bytes.Buffer) 208 | buf.Reset() 209 | WorkWithBuf(buf) 210 | Done() 211 | } 212 | 213 | func SyncBuf() { 214 | buf := sPool.Get().(*bytes.Buffer) 215 | WorkWithBuf(buf) 216 | buf.Reset() 217 | sPool.Put(buf) 218 | Done() 219 | } 220 | 221 | func BpoolBuf() { 222 | buf := bPool.Get() 223 | WorkWithBuf(buf) 224 | bPool.Put(buf) 225 | Done() 226 | } 227 | 228 | func BBpoolBuf() { 229 | buf := bytebufferpool.Get() 230 | WorkWithByteBuf(buf) 231 | bytebufferpool.Put(buf) 232 | Done() 233 | } 234 | 235 | func EasyJsonBuf() { 236 | buf := buffer.EJBuffer{} 237 | WorkWithEJBuf(&buf) 238 | buf.Reset() 239 | Done() 240 | } 241 | 242 | func Done() { 243 | select { 244 | case <-queue: 245 | wg.Done() 246 | default: 247 | // skip 248 | } 249 | } 250 | 251 | func MakeReport(start time.Time, filepath string, reports chan byte) { 252 | file, err := os.OpenFile(filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660) 253 | if err != nil { 254 | log.Fatal(err) 255 | } 256 | defer file.Close() 257 | 258 | m := new(runtime.MemStats) 259 | w := bufio.NewWriter(file) 260 | 261 | // Write headers 262 | file.WriteString("Time;Runs;Alloc;TotalAlloc;Mallocs;Frees;HeapAlloc;HeapSys;HeapIdle;HeapReleased;StackInuse\n") 263 | 264 | REPORT_FILE: 265 | for { 266 | select { 267 | case <-reports: 268 | break REPORT_FILE 269 | default: 270 | runtime.ReadMemStats(m) 271 | 272 | // Elapsed time 273 | w.WriteString(strconv.FormatInt(int64(time.Since(start).Seconds()), 10)) 274 | w.WriteByte(';') 275 | 276 | // Number of runs 277 | w.WriteString(strconv.FormatUint(runs, 10)) 278 | w.WriteByte(';') 279 | 280 | // ------------- ALLOC 281 | // Alloc 282 | w.WriteString(strconv.FormatUint(m.Alloc, 10)) 283 | w.WriteByte(';') 284 | 285 | // TotalAlloc 286 | w.WriteString(strconv.FormatUint(m.TotalAlloc, 10)) 287 | w.WriteByte(';') 288 | 289 | // Mallocs 290 | w.WriteString(strconv.FormatUint(m.Mallocs, 10)) 291 | w.WriteByte(';') 292 | 293 | // Frees 294 | w.WriteString(strconv.FormatUint(m.Frees, 10)) 295 | w.WriteByte(';') 296 | // ------------- ALLOC 297 | 298 | // ------------- HEAP 299 | // HeapAlloc 300 | w.WriteString(strconv.FormatUint(m.HeapAlloc, 10)) 301 | w.WriteByte(';') 302 | 303 | // HeapSys 304 | w.WriteString(strconv.FormatUint(m.HeapSys, 10)) 305 | w.WriteByte(';') 306 | 307 | // HeapIdle 308 | w.WriteString(strconv.FormatUint(m.HeapIdle, 10)) 309 | w.WriteByte(';') 310 | 311 | // HeapReleased 312 | w.WriteString(strconv.FormatUint(m.HeapReleased, 10)) 313 | w.WriteByte(';') 314 | // ------------- HEAP 315 | 316 | // ------------- STACK 317 | // StackInuse 318 | w.WriteString(strconv.FormatUint(m.StackInuse, 10)) 319 | w.WriteByte('\n') 320 | // ------------- STACK 321 | 322 | w.Flush() 323 | time.Sleep(time.Second) 324 | } 325 | } 326 | } 327 | 328 | func PrintReport(start time.Time, reports chan byte) { 329 | m := new(runtime.MemStats) 330 | 331 | REPORT: 332 | for { 333 | select { 334 | case <-reports: 335 | break REPORT 336 | default: 337 | runtime.ReadMemStats(m) 338 | 339 | // Elapsed time 340 | fmt.Printf("%v;", int64(time.Since(start).Seconds())) 341 | 342 | // Number of runs 343 | fmt.Printf("%v;", runs) 344 | 345 | // ------------- ALLOC 346 | // Alloc 347 | fmt.Printf("%v;", m.Alloc) 348 | 349 | // TotalAlloc 350 | fmt.Printf("%v;", m.TotalAlloc) 351 | 352 | // Mallocs 353 | fmt.Printf("%v;", m.Mallocs) 354 | 355 | // Frees 356 | fmt.Printf("%v;", m.Frees) 357 | // ------------- ALLOC 358 | 359 | // ------------- HEAP 360 | // HeapAlloc 361 | fmt.Printf("%v;", m.HeapAlloc) 362 | 363 | // HeapSys 364 | fmt.Printf("%v;", m.HeapSys) 365 | 366 | // HeapIdle 367 | fmt.Printf("%v;", m.HeapIdle) 368 | 369 | // HeapReleased 370 | fmt.Printf("%v;", m.HeapReleased) 371 | // ------------- HEAP 372 | 373 | // ------------- STACK 374 | // StackInuse 375 | fmt.Printf("%v;", m.StackInuse) 376 | // ------------- ALLOC 377 | 378 | fmt.Print("\n") 379 | time.Sleep(time.Second) 380 | } 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /buffer/main/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/omgnull/go-benchmark/buffer" 7 | ) 8 | 9 | func BenchmarkGenericBuf(b *testing.B) { 10 | b.RunParallel(func(pb *testing.PB) { 11 | for pb.Next() { 12 | GenericBuf() 13 | } 14 | }) 15 | } 16 | 17 | func BenchmarkGenericStackBuf(b *testing.B) { 18 | b.RunParallel(func(pb *testing.PB) { 19 | for pb.Next() { 20 | GenericStackBuf() 21 | } 22 | }) 23 | } 24 | 25 | func BenchmarkAllocBuf(b *testing.B) { 26 | b.RunParallel(func(pb *testing.PB) { 27 | for pb.Next() { 28 | AllocBuf() 29 | } 30 | }) 31 | } 32 | 33 | func BenchmarkSyncPoolBuf(b *testing.B) { 34 | b.RunParallel(func(pb *testing.PB) { 35 | for pb.Next() { 36 | SyncBuf() 37 | } 38 | }) 39 | } 40 | 41 | func BenchmarkBpoolPoolBuf(b *testing.B) { 42 | b.RunParallel(func(pb *testing.PB) { 43 | for pb.Next() { 44 | BpoolBuf() 45 | } 46 | }) 47 | } 48 | 49 | func BenchmarkByteBufferPoolBuf(b *testing.B) { 50 | b.RunParallel(func(pb *testing.PB) { 51 | for pb.Next() { 52 | BBpoolBuf() 53 | } 54 | }) 55 | } 56 | 57 | func BenchmarkEasyJsonBuffer(b *testing.B) { 58 | b.RunParallel(func(pb *testing.PB) { 59 | for pb.Next() { 60 | EasyJsonBuf() 61 | } 62 | }) 63 | } 64 | 65 | func BenchmarkEasyJsonBuffer_OptimizedConfig(b *testing.B) { 66 | buffer.Init(buffer.PoolConfig{ 67 | StartSize: 2048, 68 | PooledSize: 2048, 69 | MaxSize: 32768, 70 | }) 71 | 72 | b.RunParallel(func(pb *testing.PB) { 73 | for pb.Next() { 74 | EasyJsonBuf() 75 | } 76 | }) 77 | } 78 | --------------------------------------------------------------------------------