The experiment had as its object next targets:

Compare the performance difference between the same application implemented in Scala and Rust programming languages. Research how different computer specific optimizations could boost the performance of the application.

In the research, no optimizations related to algorithmic complexity were applied. At all. All optimizations were only about some computer architecture related things like internal CPU memory cache, compressing few objects into the smallest piece of computer memory, etc.

A genetic algorithm implementation was chosen as the target of optimizations because it intensively uses memory. The distinguishing feature of this algorithm is that each time it can find a solution to a problem at a different time. Moreover, it can search for the most optimal solution indefinitely. It only guarantees that it can find quickly enough one of the most optimal solutions.

Consequently, in order for the measurements between runs to be identical, I modified it a little bit so instead of using randoms on each genetical operation I used monotonically increasing number generators for each call. Therefore I was able to write integration tests which run each time with the same result (so after each optimization I was sure that algorithm was still the same). But even most important for the research was that benchmarks always run practically the same amount of time per run. Thanks to it I was able to measure performance boost pretty accurately after each optimization.

You can see each applied optimization step in those repositories: https://github.com/jinnzest/genetic-algorithm-scala, https://github.com/jinnzest/genetic-algorithm-rust.

In each step one kind of optimization is being applied in each implementation, performance measured and written to commit message. I used absolutely the same algorithm for both versions. Started from a rather functional approach (immutability everywhere, pattern matching, etc) and then step by step went to more imperative style (reusing all memory places for new objects instead of allocating new memory for each one, decomposing objects to bits, etc).

Results: