Skip to content

Conversation

falsandtru
Copy link
Contributor

The current implementation disposes used empty arrays and creates a new array. This PR reuses used empty arrays instead of creating a new array. This patch would reduce memory allocation time for Array(2048).

@nodejs-github-bot nodejs-github-bot added the needs-ci PRs that need a full CI run. label Sep 1, 2022
@BridgeAR
Copy link
Member

BridgeAR commented Sep 1, 2022

I like the idea. Do you have a benchmark and a memory profile where you compare both situations in a heavy load scenario? It would be good to see that it does indeed reduce the memory amount. This is always tricky as V8 might already do a great job detecting the old arrays and to keep the memory low.

@falsandtru
Copy link
Contributor Author

I do not know how to benchmark. How can I benchmark?

@falsandtru

This comment was marked as resolved.

@falsandtru
Copy link
Contributor Author

falsandtru commented Sep 2, 2022

I got good benchmark scores from two new versions (However, need more confidence). @BridgeAR Which is better?

Non-reducible buffer version

                                        confidence improvement accuracy (*)    (**)   (***)
process/next-tick-breadth.js n=10000000         **     13.13 %       ±7.90% ±10.51% ±13.69%
process/next-tick-depth.js n=7000000                   -2.99 %       ±8.30% ±11.04% ±14.37%
process/next-tick-exec.js n=4000000                    -1.82 %       ±4.84%  ±6.44%  ±8.39%
process/next-tick-loop.js n=10000              ***      3.18 %       ±1.67%  ±2.22%  ±2.89%
process/next-tick-loop.js n=20000              ***     11.05 %       ±2.57%  ±3.43%  ±4.46%
process/next-tick-loop.js n=40000              ***      9.98 %       ±1.88%  ±2.50%  ±3.26%
class FixedCircularBuffer {
  constructor() {
    this.bottom = 0;
    this.top = 0;
    this.list = new Array(kSize);
    this.next = null;
  }

  isEmpty() {
    return this.top === this.bottom;
  }

  isFull() {
    return ((this.top + 1) & kMask) === this.bottom;
  }

  push(data) {
    this.list[this.top] = data;
    this.top = (this.top + 1) & kMask;
  }

  shift() {
    if (this.isEmpty())
      return null;
    const data = this.list[this.bottom];
    this.list[this.bottom] = undefined;
    this.bottom = (this.bottom + 1) & kMask;
    return data;
  }
}

module.exports = class FixedQueue {
  constructor() {
    this.head = this.tail = this.buffer = new FixedCircularBuffer();
  }

  isEmpty() {
    return this.head.isEmpty();
  }

  push(data) {
    const head = this.head;
    if (head.isFull()) {
      // Head is full: Reuses the empty tail or creates a new queue,
      // sets the old queue's `.next` to it, and sets it as the new main queue.
      const buffer = this.buffer;
      if (buffer.isEmpty()) {
        this.buffer = buffer.next;
        buffer.next = null;
        this.head = head.next = buffer;
      } else {
        this.head = head.next = new FixedCircularBuffer();
      }
    }
    this.head.push(data);
  }

  shift() {
    const tail = this.tail;
    const data = tail.shift();
    if (tail.isEmpty() && tail.next !== null) {
      // If the next is not empty, it forms the new tail.
      this.tail = tail.next;
    }
    return data;
  }
};

Reducible buffer version

                                         confidence improvement accuracy (*)   (**)   (***)
process/next-tick-breadth.js n=10000000                 3.02 %       ±6.45% ±8.60% ±11.25%
process/next-tick-depth.js n=7000000            **     -3.90 %       ±2.70% ±3.59%  ±4.68%
process/next-tick-exec.js n=4000000                    -1.92 %       ±4.11% ±5.48%  ±7.14%
process/next-tick-loop.js n=10000               **      3.90 %       ±2.48% ±3.31%  ±4.30%
process/next-tick-loop.js n=20000              ***      4.65 %       ±2.36% ±3.14%  ±4.10%
process/next-tick-loop.js n=40000              ***     15.17 %       ±2.43% ±3.24%  ±4.23%
class FixedCircularBuffer {
  constructor() {
    this.bottom = 0;
    this.top = 0;
    this.list = new Array(kSize);
    this.next = null;
  }

  isEmpty() {
    return this.top === this.bottom;
  }

  isFull() {
    return ((this.top + 1) & kMask) === this.bottom;
  }

  push(data) {
    this.list[this.top] = data;
    this.top = (this.top + 1) & kMask;
  }

  shift() {
    if (this.isEmpty())
      return null;
    const data = this.list[this.bottom];
    this.list[this.bottom] = undefined;
    this.bottom = (this.bottom + 1) & kMask;
    return data;
  }
}

module.exports = class FixedQueue {
  constructor() {
    this.head = this.tail = this.buffer = new FixedCircularBuffer();
  }

  isEmpty() {
    return this.head.isEmpty();
  }

  push(data) {
    const head = this.head;
    if (head.isFull()) {
      // Head is full: Reuses the empty tail or creates a new queue,
      // sets the old queue's `.next` to it, and sets it as the new main queue.
      let buffer = this.buffer;
      if (buffer.isEmpty()) {
        if (buffer.next?.isEmpty()) {
          buffer = buffer.next;
        }
        this.buffer = buffer.next;
        buffer.next = null;
        this.head = head.next = buffer;
      } else {
        this.head = head.next = new FixedCircularBuffer();
      }
    }
    this.head.push(data);
  }

  shift() {
    const tail = this.tail;
    const data = tail.shift();
    if (tail.isEmpty() && tail.next !== null) {
      // If the next is not empty, it forms the new tail.
      this.tail = tail.next;
    }
    return data;
  }
};

@falsandtru
Copy link
Contributor Author

Move to #44499

@falsandtru falsandtru closed this Sep 3, 2022
@falsandtru falsandtru deleted the fixed-queue branch September 3, 2022 10:25
@falsandtru
Copy link
Contributor Author

falsandtru commented Sep 3, 2022

Probably, management cost of an averagely longer linked list is higher than creating new arrays even if just replacing the contained value of a substantially fixed-length circular linked list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants