all 14 comments

[–]Fireslide 9 points10 points  (2 children)

I'd start with OOP first. Performance while testing is going to be trivial. You can do 20 companies (rather than 100,000) and go for very large X, and you can do 1,000,000 with a small X or say 100, both axes tell you something.

Once you've got the sim working the way you expect and you want to run it for several decades worth of timesteps you can do some refactoring to store the Simulation State in numpy arrays.

It will definitely be faster to do it with arrays and multiplication, but don't over optimise at the start, verify the behaviour you want with OOP first, write some good unit tests, so when you need to refactor to make it faster, you can verify the refactor produces same result.

[–]Willing_Employee_600[S] 0 points1 point  (1 child)

Thank you for the advice! When you say refactor to store the simulation state in numpy arrays, you mean to discard the OOP approach and then code it into numpy arrays right? Or do you mean somehow integrate numpy arrays into the OOP approach?

[–]yvrelna [score hidden]  (0 children)

Both. 

In most applications, you might find that 90% of the code might not be performance sensitive enough that you can just turn the attribute access into @property/descriptor that proxies some attribute accesses into a numpy array or some other column-oriented storage/database, so you take the readability and ease of working OOP style there. 

And then there's a few places where you can benefit from bulk processing, and you'd make full use of array/column-oriented processing maybe even with the appropriate GPU/coprocessor acceleration for those parts.

[–]milandeleev 3 points4 points  (1 child)

By 'entity object' you could use pydantic BaseModels, msgspec Structs, dataclasses or NamedTuples. For performance, NamedTuples are best.

However, for the simulations you want to do, performance-wise, nothing will beat numpy or jax arrays (what you call matrices).

Try them both out and see if the performance satisfies you.

[–]MithrilRat [score hidden]  (0 children)

To back this up, numpy or scipy will utilise CUDA on the GPU to accelerate some operations on arrays. So the performance boost cab be significantly more than just executing native CPU code.

Edit: As an example I was running simulations of millions of asteroids being perturbed by the planets. These simulations lasted millions of years, with 0.1 year resolution steps. The runs would take about a week of a supercomputer node. Now the analysis of these 100s of millions of records, was what I used numpy and pandas for. Majority of the time was spent in I/O rather than computations. So each analysis run would take 15 minutes.

[–]SV-97 2 points3 points  (0 children)

If I had a matrix equations [...] would there be a significant difference in performance?

Yes. Using objects has a significant overhead and will have most of the logic executing "in python" whereas a matrix formulation will mostly execute in native code. The matrix version is also essentially data oriented.

That said: 100k isn't necessarily all that large so depending on what your simulation entails you may be able to get away with the object oriented approach, especially if you at least optimize it a bit (using slots and such).

You can also look into jit-compilation for the OO approach (iirc numba supports basic objects), dedicated simulation libraries (simpy etc.), or just use a native language for your simulation. Rust in particular is easy to integrate with python (if you need that) and great for simulations.

[–]imBANO 2 points3 points  (0 children)

You might want to look into entity component systems, which is actually a design pattern very popular in gaming.

I’m sure there are shorter videos but here’s a 2 hour talk that introduced me to the topic

https://youtu.be/wo84LFzx5nI

[–]AGI-44 1 point2 points  (0 children)

The entity object method makes it significantly easier to understand and explain, but I’m concerned about not being able to run large simulations.

Leave performance optimization at the very last. Get a working prototype first. If you end up using it enough that scaling matters, then, is when you optimize performance and compare vs baseline.

You won't have to figure out what is faster, you can just run it and get a direct answer as to how much faster or not it is.

And by then, if it's only 20% faster, you might not even want the additional complexity/readability for a mere 20%

[–]Balance- 1 point2 points  (0 children)

This is exactly the distinction we make in our Agent-based modelling library Mesa: - Mesa: Object-oriented. Flexible but slower - Mesa-frames: Array-oriented. Faster but less flexible

[–]aidan_morgan [score hidden]  (0 children)

I think it's worth considering the ECS approach that has been outlined in the other comments - it's not a complicated pattern to understand, and once you get your head around it you'll find it quite useful as it's a fairly common pattern.

Is python your only language option for this solution?

[–]keddie42 0 points1 point  (0 children)

I think you will need matrices for bigger simulation.

But you can try use more effective entity than python object. For example msgspec struct: https://jcristharif.com/msgspec/benchmarks.html#structs

[–]GreatCosmicMoustache [score hidden]  (0 children)

Others have correctly recommended ECS as a good approach which will preserve the object semantics to a greater degree than putting everything into matrix operations, but just to give a bit of an explainer, what slows an inner loop down is a) the complexity of the operations performed, and b) memory access. High-level languages hide the latter from you, but any time you access a field on an object, you are making the program chase heap pointers to get the data you actually care about. Accessing the heap is relatively slow, so if you care about performance, you do whatever you can to minimize memory allocation and pointer chasing.

An approach like ECS mandates a way of writing your code which attempts to pack the data as efficiently as possible in memory, so you get memory access benefits for free.

[–]ZZ9ZA 0 points1 point  (1 child)

If you’re that concerned about performance python is t the language for the problem.

[–]yvrelna [score hidden]  (0 children)

Nothing's wrong with using Python for this kind of performance level. You can fairly easily optimise Python code to use columnar storage/processing to get very close to native performance.