Hello,
I suspect the issue is with PETSc rather than with FEniCS, but given the problem set-up I think this is mostly useful for FEnICS users.
Suppose I wish to assemble a large system matrix from different blocks, each assembled by FEniCS (for e.g. domain decomposition, hybridization, multimesh methods, multiphysics coupling, etc). I am noticing a very rapid speed drop when I repeat the operation of copying the FEniCS matrix into the large matrix. Here is a MWE that illustrates my problem (in reality I don’t actually have a purely block diagonal system):
from dolfin import *
import numpy as np
from petsc4py import PETSc
import resource
import time
# Parameters
MANUAL_PREALLOCATION = True
width = 1
elements_x_per_block = 100
total_blocks = 15
# Generate the block matrix
mesh = UnitSquareMesh(elements_x_per_block,elements_x_per_block)
V = FunctionSpace(mesh,'CG',1)
phi = TrialFunction(V)
v = TestFunction(V)
A_block = as_backend_type( assemble( inner(grad(phi),grad(v))*dx ) )
A_size = A_block.size(0)
# "Preallocate" the large matrix
memory_start = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
M_size = A_size*total_blocks
M = PETSc.Mat().create()
M.setSizes(M_size)
M.setType('aij')
if MANUAL_PREALLOCATION:
# Manually set the anticipated nz values per row
M.setPreallocationNNZ( A_block.nnz()//A_size )
# This option seems to defeat the point but mitigates an error
M.setOption(PETSc.Mat.Option.NEW_NONZERO_ALLOCATION_ERR, False)
else:
M.setUp()
M.assemble()
print("Memory used for preallocation:",resource.getrusage(resource.RUSAGE_SELF).ru_maxrss-memory_start)
# Place blocks
time_start = time.time()
for block_nr in range(total_blocks):
offset = block_nr*A_size
# CSR arrays from A_block matrix
rows_ind,cols,vals = A_block.mat().getValuesCSR()
# Creating the CSR arrays for A matrix
cols_to = cols + offset
rows_ind_to = np.ones(M_size+1,dtype=np.int32)*len(vals)
rows_ind_to[0:offset] = 0
rows_ind_to[offset:offset+len(rows_ind)-1] = rows_ind[0:-1]
# Placing into A matrix
M.setValuesCSR(rows_ind_to, cols_to, vals, addv=PETSc.InsertMode.ADD)
M.assemble()
# Output
if (block_nr+1)%5 == 0:
time_end = time.time()
print("Time to assemble block %i till block %i:"%(block_nr-4,block_nr+1), time_end-time_start)
time_start = time_end
if block_nr == 0:
print("Time to assemble the first block:", time.time()-time_start)
print("Memory used for preallocation plus assembly:",resource.getrusage(resource.RUSAGE_SELF).ru_maxrss-memory_start)
with output:
Memory used for preallocation: 4272
Time to assemble the first block: 0.8050854206085205
Time to assemble block 0 till block 5: 5.991081237792969
Time to assemble block 5 till block 10: 16.175475358963013
Time to assemble block 10 till block 15: 54.92933130264282
Memory used for preallocation plus assembly: 57696
I expect this is due to faulty preallocation of the large matrix. However, I get (roughly) the same result whether I set “MANUAL_PREALLOCATION” to True or False in the above code snippet. Am I doing this wrong?