so there are a few levels of performance, it depends on your target hardware and how many bullets you want to spawn!
The worst is using Instantiate, where every bullet has a script and a rigidbody.
The easiest optimisation is to ditch Instantiate and use a pooling system - I highly recommend LeanPool, it's so easy to use!
For another performance boost, get rid of the monobehaviour on every bullet, and have one Bullet Manager class that moves all the bullets.
Then, remove the rigidbody and collider on the bullet, and use Physics2D.OverlapCircle (or OverlapCapsule if you want continuous collision detection lite 😂) in the bullet manager in fixed update.
Then swap out OverlapCircle for OverlapCircleNonAlloc.
Then ditch the Gameobjects entirely, and use Graphics.DrawMesh to render your bullets.
Then ditch unity physics and write your own using jobs and burst. This is where my asset stops - it'll do 10,000 bullets on screen at a solid 60fps on my MacBook!
Then ditch all of that and do it all in a compute shader 😂
Remember to always filter your collisions! One of the best ways to optimise is to just do fewer things - bullets *do not* need to collide with each other, you can use Unity's layers and collision matric for that. Oh and circle/sphere colliders are computationally the cheapest. Use them wherever possible 😁