Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib: reduce memory allocation for arrays #44474

Closed
wants to merge 1 commit into from
Closed

lib: reduce memory allocation for arrays #44474

wants to merge 1 commit into from

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.

None yet

3 participants