Skip to content

Wasm: Implement ArrayDeque without js.Array in Wasm backend #5164

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

tanishiking
Copy link
Contributor

@tanishiking tanishiking commented May 9, 2025

Simliar to ju.ArrayList #5162, add a Wasm version of implementation of ArrayDeque that doesn't have JS-interop for internal data structure.


I wrote a micro-benchmark for ArrayDeque, and compare the two implementation on JS (scala.Array vs js.Array), each benchmark basically add/poll integer 10000 times.
tanishiking/scalajs-benchmarks@13cd090

In WebAssembly, it works around 6~7x- faster than js.Array impl.

download (3)

https://colab.research.google.com/drive/17eevbKILkGiT21uqWCDuD3KAkj-de6BT?usp=sharing

The original implementation of ju.ArrayDeque uses js.Array for its internal data structure. When compiling to Wasm, operations on ju.ArrayDeque require JS interop calls to access the underlying js.Array, which causes a slow performance.

This commit introduces an implementation of ju.ArrayDeque for the Wasm backend. This version uses Scala's Array instead of js.Array to avoid JS-interop.
Comment on lines +369 to +370
val innerJS = this.innerJS // local copy
val innerWasm = this.innerWasm // local copy
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand correctly that, these local copies are for better performance (accessing local copy is faster than class field), right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's correct.

Copy link
Contributor Author

@tanishiking tanishiking May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[note]
This is also a problem, let's wait until we decide to use (or not to use) Array[AnyRef] anyway.
If we don't, we can do something like val innerJS = if (isWebAssembly) null else this.innerJS, or maybe we can do the same as PriorityQueue.

@tanishiking tanishiking changed the title Wasm: Implement ArrayDeque without js.Array in Wasm backend Wasm: Implement ArrayDeque and PriorityQueue without js.Array in Wasm backend May 9, 2025
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For ArrayDeque, it does not seem like we rely all that much on the growability of js.Arrays. The only place that grows is ensureCapacityForAdd(). It always doubles the size of the internal array. It's unlikely that the underlying VM could do a better job than just reallocating at about the same moments.

Perhaps it would be better to just always use Array[AnyRef], even on JS. That would avoid complicating the logic, which in this class is particularly dense already.

@gzm0 WDYT?

Copy link
Contributor Author

@tanishiking tanishiking May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For comparison, I wrote a Array[AnyRef] (even on JS) version b73e8a1

And, I wrote a micro-benchmark for ArrayDeque, and compare the two implementation on JS (scala.Array vs js.Array), each benchmark basically add/poll integer 10000 times.
tanishiking/scalajs-benchmarks@13cd090

The result shows that, js.Array is still slightly faster than scala.Array in most cases. However, considering the complexity of the program, the difference may be negligible.

download

https://colab.research.google.com/drive/17eevbKILkGiT21uqWCDuD3KAkj-de6BT?usp=sharing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were those benchmarks done in fullLink?

Copy link
Contributor Author

@tanishiking tanishiking May 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was in fastLink, in fullLink scala.Array and js.Array has a bit smaller difference in addLast bench

download (2)

https://colab.research.google.com/drive/17eevbKILkGiT21uqWCDuD3KAkj-de6BT?usp=sharing

it's interesting that, with scala.Array impl, FIFO and LIFO cases (last two benchmarks) are consistently faster than js.Array

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the overall sentiment, that just using scala.Array for JS as well might be worthwhile.

It seems that the investigation here as already gone beyond gut feeling, so I guess we can let the numbers speak.

The current benchmarks do look like there is some overhead not related to resizing. For example, I'd expect peekFirst_Repeatedly to not cause any mutations. Do we have an understanding of where these come from?

@tanishiking
Copy link
Contributor Author

Perhaps it would be better to split it into two PRs for ArrayDeque and PriorityQueue?

@tanishiking tanishiking changed the title Wasm: Implement ArrayDeque and PriorityQueue without js.Array in Wasm backend Wasm: Implement ArrayDeque without js.Array in Wasm backend May 15, 2025
@tanishiking
Copy link
Contributor Author

I took a liberty of splitting a PR for PriorityQueue #5168
Let's focus on ArrayDeque here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants