I'm creating my own compiled language in Rust, any tips on performance?

50 points ImaMapletree

As the title states, I've been building my own language for the past couple months with heavy inspirations from Java and Python. On an implementation level, the language most heavily reflects Python with all Language-level data being reflected by an internal Rust struct. This creates a lot of overhead as even a simple int must be wrapped in a somewhat large struct to hold itself and other associated data. Right now, my solution to performance is to allocate a large heap for the structs and with some \very\ unsafe pointer magic I'm able to spin up and drop new objects very fast... but in benchmarking I'm still ending up 15-25% slower than CPython.

I've spent hours looking into guides about how to squeeze performance out of Rust, but I think at this point it's more than obvious that there's something wrong with my implementation and not Rust itself. Which is why I'm here!

I don't even know where to begin in regards of how to provide useful information to those who need it, but if there's anything off the top of your head that you can suggest, I'm willing to try just about anything to fix my overall system design! I can also provide any / all information regarding the language if it helps.

technical tl;dr

  • program compiles to bytecode which is then read by an interpreter (written in rust)
  • the interpreter first allocates a large heap (current default is 2048MB)
  • every "type" is reflected as a struct with an enum representing it's native data and a hashmap for the instance's members
  • structs are allocated and stored in the heap, all actual calculations are performed on smart references to that stored data (thus requiring deref and an counter)

Thanks!

Edit1 - Additional info copied from my comments:
\- Everything besides functions are represented through the common struct "object", and even functions themselves have a specific data-type which is simply just a pointer with a bit of metadata representing the actual function. And while that 100% creates a lot of overhead for a simple thing like a int, char, etc I don't believe it to be the bottleneck since the actual bytecode instructions are rather specific. They take a page from java where the native types have individualized instructions / execution that speed things up when doing calculations on common ints and floats. Unwrapping the data is also a singular call where I cast the packed data to an int, which is safe since the language is type-enforced during compilation.

\- Dropping the object is a bit more expensive because of how the heap works, once an object runs out of references it's obviously de-alloced but it's memory address is also recycled back into the heap so it can be used for re-allocation at a later time. Right now this is through a vec of "available" pointers with a slightly faster pushing / popping function.