Idea 1: Only consider tiles that intersect with your camera’s frustum. It seems you have already tried this.
Idea 2: Upload your tiles as vertex data to a GPU buffer once or infrequently and just don’t even try to figure out what tiles need to be rendered on the CPU every tick.