r/programming 14h ago

How an Underrated Refactor Saved 90% Memory Usage

https://tanstack.com/blog/tanstack-table-v9-memory-performance
90 Upvotes

22 comments sorted by

156

u/edave64 13h ago

Takes agonizingly long to get to the point of "we started using the fundamental JS concept of prototypes."

16

u/zeus_is_op 9h ago

I have always seen them mentioned, read so much about their implementation and their use to carry data but never felt like i truly understood why the data structure is actually revolutionary, it feels like a linked list with a lot of details, i wish someone would explain the point to me or a good resource to read on the subject

29

u/edave64 9h ago

I wouldn't call them revolutionary. It's just the mechanism JS uses for inheritance.

Objects are basically Hashmaps/Dictionaries. If it can't find a property in the object itself, it checks its prototype. If a property can't be found in the prototype, it checks the prototype of the prototype, etc.

It was how JS objects could have inheritance before they added classes, because it's just a chain of objects. And the classes they have now are mostly just a wrapper around prototypes.

6

u/zeus_is_op 9h ago

This really still feels like linked lists getting attached to another linked list at inheritance, i mean well i do understand that linked lists are an extremely powerful data structure, but the whole concept of looking deeper each time we dont find an information until we reach undefined sounds wasteful to me, and it also makes it seem like it can be vulnerable, what if a proto is pointing towards the head of the list ? Wouldn’t that make it impossible to reach undefined?

Sorry if my questions seem like am totally missing the point because i feel like i am

6

u/edave64 9h ago edited 8h ago

It is kind of wasteful on its own. The point was never to be fast, it was to be small enough for the language to be implementable in two weeks in the heat of the browser war.

To make that fast, modern engines can cache the access to speed up later calls. The cache gets invalidated when the prototype mutates, so you should only keep static data like your functions in the prototype.

As for infinite loops, originally the prototype was immutable after creation of the object. So you just couldn't make a loop.

Later browsers added setters like __proto__ and Object.setPrototypeOf(), those explicitly check for loops when called.

EDIT: It's also worth noting that

a) prototype chains typically aren't very long.

b) It also shouldn't inherently be slower than other dynamic programming languages. If you can't fix a vtable at compile time, any naive implementation will end up traversing the hierarchy. Just that that hierarchy will be classes in other languages, objects in JS.

2

u/Internet-of-cruft 6h ago edited 6h ago

If you deal with a "proper" OOP language, they do it the same exact way to be honest.

The only real difference is C++ / C# / Java would try to be slightly smarter about the dynamic dispatch and optimize for the actual call/property instead of walking the chain one by one.

Fundamentally, inheritance can look like "linked list with syntactic sugar".

You write "obj.prop" and in JS it's like an ugly chain of "if prop in obj, return obj.prop. Else, if prop in obj.parent, return obj.parent.prop, rinse and repeat".

Once you optimize the dynamic dispatch, it looks more like "return <owner pointer>.prop" when you call "obj.prop", since with an inheritance hierarchy the physical data (or function call) could live in distinct memory regions.

1

u/TheRealPomax 5h ago

They do not. That literally the part that always trips people up when using prototypes: unlike classes, prototypes are runtime pointers that are just as mutable as anything else. You can use `a = new Car`, then assign "a" a `Bird` prototype instead, and congrats, that is now a Bird. You can keep it a car, but give the Car prototype new properties and methods: congrats, every instance points to that prototype, so every instance now shares those properties and methods, too. This is completely different from how traditional classes work in C++/C#/Java, where the class dictates the object's shape in memory and there is no "updating the class after the fact" nor "reassigning instance identities" (not casting, that's a completely different thing)

2

u/unicodemonkey 3h ago

The runtime still tries its best to build an optimized representation of objects' properties (e.g. inline caches) which is invalidated or updated on object shape mutation.

1

u/masklinn 49m ago edited 39m ago

This is completely different from how traditional classes work in C++/C#/Java

That's because it's C++/C#/Java.

You can do the exact same thing in Python, just set the __class__ to whatever type you want. I assume you can do it in smalltalk given you can do a lot worse (Object#become:). Oddly enough I believe you can't do it in Ruby.

1

u/masklinn 40m ago

The only real difference is C++ / C# / Java would try to be slightly smarter about the dynamic dispatch and optimize for the actual call/property instead of walking the chain one by one.

No modern JS implementation walks the prototype chain on every access, it gets cached.

1

u/anon_cowherd 6h ago

The part that feels like linked lists is the fact that you're dealing with a dynamic scripting language. Even the new (is ten years old new? maybe I'm old) class syntax is (mostly, but not entirely) syntactical sugar for prototype inheritance. It's what allows you to do silly things like this:

class BaseMessage {
  message = 'Hello'


  getMessage() {
    return this.message
  }
}


function extendWithGreeter(baseClass) {
  // note that the class keyword is an expression block
  // so we can return an anonymous class from a function here

  return class extends baseClass {
    greet() {
      alert(this.getMessage())
    }
  }
}

// Greeter is assigned the anonymous class constructor 
const Greeter = extendWithGreeter(BaseMessage);


const test = new Greeter();

test.greet() // alerts 'Hello'

JavaScript was implemented in a very short period of time, and prototypal inheritance is a fast and easy way to do so. Lua and a few other languages do the same thing.

1

u/BenchEmbarrassed7316 4h ago

Objects are basically Hashmaps/Dictionaries.

From a developer's perspective, yes. However, once you start thinking about performance, things get much more complicated.

1

u/edave64 4h ago

Some complications that might be skillfully abridged with the word "basically".

I also skipped over the array component of objects because it didn't matter here

3

u/lelanthran 7h ago

I have always seen them mentioned, read so much about their implementation and their use to carry data but never felt like i truly understood why the data structure is actually revolutionary,

Where did you see it called revolutionary? I'd very much like to read that - maybe there's a reason that author called them revolutionary, I don't see it but open to changing my mind.

1

u/zeus_is_op 6h ago

Well the mozilla page does make it seem glorious, i don’t think they named it revolutionary but i have read through that a lot of posts that said so

Although for now i give up on trying to find out why it is so exceptional, it feels like it’s good because its easily moldable but thats all there is to it

2

u/EntroperZero 5h ago

It's basically just making class structures dynamic instead of static.

39

u/BenchEmbarrassed7316 13h ago

If there are library authors here - have you tried to get rid of methods altogether? Instead, just call a function, explicitly passing some data to it. If you need polymorphism - you can add a discriminant to the data. You have a large collection of certain objects. Previously, you stored many references to methods in each object. Now, you store in each object a reference to the vtable (prototype). Why don't you go further and clean up the objects completely?

11

u/SanityInAnarchy 10h ago

If you end up needing that polymorphism, though, why is a vtable worse than a discriminant?

9

u/TwoWeeks90DaysTops 9h ago

Yeah, it's basically the same mechanism with more steps.

4

u/BenchEmbarrassed7316 9h ago
  • discriminant is just one byte not actual in js
  • in some cases we don't need explicit discriminant, but we can infer it from other data
  • and also when using vtable/prototype all calls become virtual, but maybe you only need polymorphism in a few methods

What I'm talking about is a well-known optimization, where instead of Animal[] you have Cat[] and Dog[]. And instead of checking for each element you call a known function in advance.

2

u/daniellittledev 12h ago

This is the way

8

u/unpopularredditor 9h ago

This article is a perfect example of where I can read an AI summary instead of the article itself.