├── .github └── workflows │ └── test-node.yml ├── .gitignore ├── LICENSE ├── README.md ├── SECURITY.md ├── bench.js ├── fixed-size.js ├── index.js ├── package.json └── test.js /.github/workflows/test-node.yml: -------------------------------------------------------------------------------- 1 | name: Build Status 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | node-version: [lts/*] 14 | os: [ubuntu-latest, macos-latest, windows-latest] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: npm install 23 | - run: npm test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | sandbox.js 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Mathias Buus 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fast-fifo 2 | 3 | A fast fifo implementation similar to the one powering nextTick in Node.js core 4 | 5 | ``` 6 | npm install fast-fifo 7 | ``` 8 | 9 | Uses a linked list of growing fixed sized arrays to implement the FIFO to avoid 10 | allocating a wrapper object for each item. 11 | 12 | ## Usage 13 | 14 | ``` js 15 | const FIFO = require('fast-fifo') 16 | 17 | const q = new FIFO() 18 | 19 | q.push('hello') 20 | q.push('world') 21 | 22 | q.shift() // returns hello 23 | q.shift() // returns world 24 | ``` 25 | 26 | ## API 27 | 28 | #### `q = new FIFO()` 29 | 30 | Create a new FIFO. 31 | 32 | #### `q.push(value)` 33 | 34 | Push a value to the FIFO. `value` can be anything other than undefined. 35 | 36 | #### `value = q.shift()` 37 | 38 | Return the oldest value from the FIFO. 39 | 40 | #### `q.clear()` 41 | 42 | Remove all values from the FIFO. 43 | 44 | #### `bool = q.isEmpty()` 45 | 46 | Returns `true` if the FIFO is empty and false otherwise. 47 | 48 | #### `value = q.peek()` 49 | 50 | Return the oldest value from the FIFO without shifting it out. 51 | 52 | #### `len = q.length` 53 | 54 | Get the number of entries remaining in the FIFO. 55 | 56 | ## Benchmarks 57 | 58 | Included in bench.js is a simple benchmark that benchmarks this against a simple 59 | linked list based FIFO. 60 | 61 | On my machine the benchmark looks like this: 62 | 63 | ``` 64 | fifo bulk push and shift: 2881.508ms 65 | fifo individual push and shift: 3248.437ms 66 | fast-fifo bulk push and shift: 1606.972ms 67 | fast-fifo individual push and shift: 1328.064ms 68 | fifo bulk push and shift: 3266.902ms 69 | fifo individual push and shift: 3320.944ms 70 | fast-fifo bulk push and shift: 1858.307ms 71 | fast-fifo individual push and shift: 1516.983ms 72 | ``` 73 | 74 | YMMV 75 | 76 | ## License 77 | 78 | MIT 79 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security contact information 2 | 3 | To report a security vulnerability, please use the 4 | [Tidelift security contact](https://tidelift.com/security). 5 | Tidelift will coordinate the fix and disclosure. 6 | -------------------------------------------------------------------------------- /bench.js: -------------------------------------------------------------------------------- 1 | const FastFIFO = require('./') 2 | const FIFO = require('fifo') 3 | 4 | run(new FIFO(), 'fifo') 5 | run(new FastFIFO(), 'fast-fifo') 6 | run(new FIFO(), 'fifo') 7 | run(new FastFIFO(), 'fast-fifo') 8 | 9 | function run (q, prefix) { 10 | const runs = 1024 11 | 12 | console.time(prefix + ' bulk push and shift') 13 | 14 | for (let j = 0; j < 1e5; j++) { 15 | for (let i = 0; i < runs; i++) { 16 | q.push(i) 17 | } 18 | for (let i = 0; i < runs; i++) { 19 | q.shift() 20 | } 21 | } 22 | 23 | console.timeEnd(prefix + ' bulk push and shift') 24 | console.time(prefix + ' individual push and shift') 25 | 26 | for (let j = 0; j < 1e5; j++) { 27 | for (let i = 0; i < runs; i++) { 28 | q.push(i) 29 | q.shift() 30 | } 31 | } 32 | 33 | console.timeEnd(prefix + ' individual push and shift') 34 | } 35 | -------------------------------------------------------------------------------- /fixed-size.js: -------------------------------------------------------------------------------- 1 | module.exports = class FixedFIFO { 2 | constructor (hwm) { 3 | if (!(hwm > 0) || ((hwm - 1) & hwm) !== 0) throw new Error('Max size for a FixedFIFO should be a power of two') 4 | this.buffer = new Array(hwm) 5 | this.mask = hwm - 1 6 | this.top = 0 7 | this.btm = 0 8 | this.next = null 9 | } 10 | 11 | clear () { 12 | this.top = this.btm = 0 13 | this.next = null 14 | this.buffer.fill(undefined) 15 | } 16 | 17 | push (data) { 18 | if (this.buffer[this.top] !== undefined) return false 19 | this.buffer[this.top] = data 20 | this.top = (this.top + 1) & this.mask 21 | return true 22 | } 23 | 24 | shift () { 25 | const last = this.buffer[this.btm] 26 | if (last === undefined) return undefined 27 | this.buffer[this.btm] = undefined 28 | this.btm = (this.btm + 1) & this.mask 29 | return last 30 | } 31 | 32 | peek () { 33 | return this.buffer[this.btm] 34 | } 35 | 36 | isEmpty () { 37 | return this.buffer[this.btm] === undefined 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const FixedFIFO = require('./fixed-size') 2 | 3 | module.exports = class FastFIFO { 4 | constructor (hwm) { 5 | this.hwm = hwm || 16 6 | this.head = new FixedFIFO(this.hwm) 7 | this.tail = this.head 8 | this.length = 0 9 | } 10 | 11 | clear () { 12 | this.head = this.tail 13 | this.head.clear() 14 | this.length = 0 15 | } 16 | 17 | push (val) { 18 | this.length++ 19 | if (!this.head.push(val)) { 20 | const prev = this.head 21 | this.head = prev.next = new FixedFIFO(2 * this.head.buffer.length) 22 | this.head.push(val) 23 | } 24 | } 25 | 26 | shift () { 27 | if (this.length !== 0) this.length-- 28 | const val = this.tail.shift() 29 | if (val === undefined && this.tail.next) { 30 | const next = this.tail.next 31 | this.tail.next = null 32 | this.tail = next 33 | return this.tail.shift() 34 | } 35 | 36 | return val 37 | } 38 | 39 | peek () { 40 | const val = this.tail.peek() 41 | if (val === undefined && this.tail.next) return this.tail.next.peek() 42 | return val 43 | } 44 | 45 | isEmpty () { 46 | return this.length === 0 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-fifo", 3 | "version": "1.3.2", 4 | "description": "A fast fifo implementation similar to the one powering nextTick in Node.js core", 5 | "main": "index.js", 6 | "files": [ 7 | "./index.js", 8 | "./fixed-size.js" 9 | ], 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "standard": "^17.1.0", 13 | "brittle": "^3.3.2" 14 | }, 15 | "scripts": { 16 | "test": "standard && brittle test.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/mafintosh/fast-fifo.git" 21 | }, 22 | "author": "Mathias Buus (@mafintosh)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/mafintosh/fast-fifo/issues" 26 | }, 27 | "homepage": "https://github.com/mafintosh/fast-fifo" 28 | } 29 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const FIFO = require('./') 3 | 4 | test('basic', function (t) { 5 | const q = new FIFO() 6 | const values = [ 7 | 1, 8 | 4, 9 | 4, 10 | 0, 11 | null, 12 | {}, 13 | Math.random(), 14 | '', 15 | 'hello', 16 | 9, 17 | 1, 18 | 4, 19 | 5, 20 | 6, 21 | 7, 22 | null, 23 | null, 24 | 0, 25 | 0, 26 | 15, 27 | 52.2, 28 | null 29 | ] 30 | 31 | t.is(q.shift(), undefined) 32 | t.ok(q.isEmpty()) 33 | t.is(q.length, 0) 34 | for (const value of values) q.push(value) 35 | while (!q.isEmpty()) { 36 | t.is(q.shift(), values.shift()) 37 | t.is(q.length, values.length) 38 | } 39 | t.is(q.shift(), undefined) 40 | t.ok(q.isEmpty()) 41 | }) 42 | 43 | test('long length', function (t) { 44 | const q = new FIFO() 45 | 46 | const len = 0x8f7 47 | for (let i = 0; i < len; i++) q.push(i) 48 | 49 | t.is(q.length, len) 50 | 51 | let shifts = 0 52 | while (!q.isEmpty()) { 53 | q.shift() 54 | shifts++ 55 | } 56 | 57 | t.is(shifts, len) 58 | t.is(q.length, 0) 59 | }) 60 | 61 | test('clear', function (t) { 62 | const q = new FIFO() 63 | 64 | q.push('a') 65 | q.push('a') 66 | q.clear() 67 | t.is(q.shift(), undefined) 68 | t.is(q.length, 0) 69 | 70 | for (let i = 0; i < 50; i++) { 71 | q.push('a') 72 | } 73 | 74 | q.clear() 75 | t.is(q.shift(), undefined) 76 | t.is(q.length, 0) 77 | }) 78 | 79 | test('basic length', function (t) { 80 | const q = new FIFO() 81 | 82 | q.push('a') 83 | t.is(q.length, 1) 84 | 85 | q.push('a') 86 | t.is(q.length, 2) 87 | 88 | q.shift() 89 | t.is(q.length, 1) 90 | 91 | q.shift() 92 | t.is(q.length, 0) 93 | 94 | q.shift() 95 | t.is(q.length, 0) 96 | }) 97 | 98 | test('peek', function (t) { 99 | const q = new FIFO() 100 | 101 | q.push('a') 102 | t.is(q.length, 1) 103 | t.is(q.peek(), 'a') 104 | t.is(q.peek(), 'a') 105 | 106 | q.push('b') 107 | t.is(q.length, 2) 108 | t.is(q.peek(), 'a') 109 | t.is(q.peek(), 'a') 110 | 111 | t.is(q.shift(), 'a') 112 | t.is(q.peek(), 'b') 113 | t.is(q.peek(), 'b') 114 | 115 | t.is(q.shift(), 'b') 116 | t.is(q.peek(), undefined) 117 | t.is(q.peek(), undefined) 118 | }) 119 | 120 | test('peek edgecase', function (t) { 121 | const q = new FIFO(4) 122 | 123 | q.push('a') 124 | q.push('b') 125 | q.push('c') 126 | q.push('d') 127 | q.push('e') 128 | 129 | t.is(q.peek(), q.shift()) 130 | t.is(q.peek(), q.shift()) 131 | t.is(q.peek(), q.shift()) 132 | t.is(q.peek(), q.shift()) 133 | t.is(q.peek(), q.shift()) 134 | t.is(q.peek(), q.shift()) 135 | }) 136 | 137 | test('invalid hwm', function (t) { 138 | t.exception(() => new FIFO(3)) 139 | }) 140 | --------------------------------------------------------------------------------