Looping through the bullet array and the collider array is brute force way of doing it and will cost (at worst)
Bullet * Collider amount of checks which can be very slow. Throwing multiple threads at the probably can make it a little better but overall it won't scale.
Here is a little list of things I can think of from my head you can do to speed things up in order of complexity (easiest to hardest):
Each collider usually has a shape which determines the amount of math needed to work if two are overlapping in space. Optimising this shape can lead to massive performance improvements (depending on what you are switching from). Circles are the cheapest to calculate, followed by squares, then convex hull (a mesh which doesn't go "in" at all), and actual meshes. It is often cheaper to have multiple circles represent something complex than a convex hull or even a square (if you have very optimised code).
If each bullet is a little circle and each enemy is a big circle all you have to check is if the distance between the centre points is larger than the combined radius of both circles, super fast check, great performance.
We know where all the enemies are and we know how big our play field is, so using that information we can divide the play field into squares (or rectangles) and then we'll keep a list of what is in each tile. When you then go to resolve a collision rather than looping through everything you do the following:
- What tile is the centre point of my bullet?
- Does my bullet collider go over the edge of the tile? (if so add each overlapping tile to the check tiles)
- Loop though each enemy in the tile(s) and check if they collide, pick the most appropriate collision from there
This has the advantage that you only really care about the enemies in the same area where the collision is going to take place, the worst case is that you have to check 4 tiles but if you divide your play field into 100 tiles (this number will need a little fine tuning to your game) you still have a lot less total calculations.
Take what you made with spatial partitioning and nest the tiles, navigating this structure can be cheap depending on how it's layed out in memory (see Morton Codes) because you can reduce the cache misses on the CPU and effectively give easy patterns for the CPU to pick up on and pre-calculate your results before your program even asks for the result.
You can take this concept further with dynamic nesting of tiles so you can subdivide where there are more enemies into fine grain tiles (so you effectively have a limit on the amount of enemies in each tile), though this has some trade offs with either breaking Moron Code compatibility or creating large empty arrays. Depends on the implementation
GPU compute shaders
Your CPU will have somewhere between 4 and 32 cores while using them all is really good the best you can do is use all of them, meanwhile your most GPUs are able to process thousands of things at the same time. If your target platform and performance requirements allow you to this can be a really good speedup (if your going for mobile this most likely won't be a silver bullet for you).
They differ based on the rendering API your using and have some quirks but if you look around for compute shader tutorials and the theory behind it you can move all the collision checks to the GPU while the CPU goes other things while waiting for the results.
There are other ways and many methods yet to be thought up so this sort of thing is always getting new optimisations and techniques...