Well, the comparison with the 3js example is not that fair because this example uses a dedicated vertex shader to animate the triangles.
That said, the BJS instances can be optimized. By default, each instance World Matrix, then each BoundingInfo is recomputed each frame and each instance is checked against the camera frustum.
You could then skip all these computations and update by yourself the instance world matrix buffer directly like explained here : Use Instances - Babylon.js Documentation
In the example from the documentation, you can see 20K dodecahedrons moving at a decent framerate.
That’s what I’m using in this demo : Test Babylon Instances Terrain
There are 6000 instances (each way higher poly than a triangle or a dodecahedron) whose world matrix buffers are updated by hand.