PETSc does give you the tools to precisely specify the sparsity pattern of your matrix, but it gets quite involved quite quickly (MatCreateAIJ — PETSc 3.24.0 documentation )
Maybe just setting the NNZ (number of non-zeros) already gives you the performance you need? I’ve played around with that quite a while ago (Assembly of FEniCS submatrices in large PETSc matrix: preallocation - #6 by Stein)
The PETSc matrix call M.getInfo() is quite useful for checking whether PETSc is actually doing what you intend.
For more targeted help you’d have to supply a MWE.