Performance Tips¶
This page provides guidance on how to write CVXPY code that compiles and solves efficiently. CVXPY problems have two main cost components: compile time (how long CVXPY takes to transform your problem for a solver) and solve time (how long the solver takes to find a solution). The tips below primarily address compile time, which can be the bottleneck for large problems.
Vectorize your problem¶
The single most impactful thing you can do to reduce compile time is to vectorize your CVXPY expressions. This means expressing constraints and objectives over entire vectors or matrices at once, rather than writing scalar operations in Python loops.
As a rule of thumb, you should minimize the number (and not just the dimension) of CVXPY Variable, Constraint, and
Expression objects needed to specify your model. Each CVXPY object adds overhead during
compilation, so fewer objects means faster compile times.
Bad (scalarized) — slow:
import cvxpy as cp
import numpy as np
m, n = 500, 200
A = np.random.randn(m, n)
b = np.random.randn(m)
x = cp.Variable(n)
# Slow: creates m separate Constraint objects
constraints = []
for i in range(m):
constraints.append(A[i, :] @ x == b[i])
prob = cp.Problem(cp.Minimize(cp.sum_squares(x)), constraints)
prob.solve()
Good (vectorized) — fast:
import cvxpy as cp
import numpy as np
m, n = 500, 200
A = np.random.randn(m, n)
b = np.random.randn(m)
x = cp.Variable(n)
# Fast: creates a single Constraint object
constraints = [A @ x == b]
prob = cp.Problem(cp.Minimize(cp.sum_squares(x)), constraints)
prob.solve()
The vectorized version creates a single constraint object instead of m separate ones.
For large m, this difference can be the gap between milliseconds and seconds of compile time.
Similarly, for simple bound constraints, consider using the bounds attribute on the variable directly, which is the most efficient approach:
# Slow
constraints = [x[i] >= 0 for i in range(n)] + [x[i] <= 1 for i in range(n)]
# Better (vectorized)
constraints = [x >= 0, x <= 1]
# Best (use bounds attribute)
x = cp.Variable(n, bounds=[0, 1])
The bounds attribute is the most efficient way to specify simple variable bounds.
It supports scalars, NumPy arrays, parameters, and affine functions of parameters:
import numpy as np
import cvxpy as cp
n = 10
# Scalar bounds
x = cp.Variable(n, bounds=[0, 1])
# NumPy array bounds
lb = np.zeros(n)
ub = np.ones(n)
x = cp.Variable(n, bounds=[lb, ub])
# Parameter bounds (useful for repeated solves with changing bounds)
lb_param = cp.Parameter(n)
ub_param = cp.Parameter(n)
x = cp.Variable(n, bounds=[lb_param, ub_param])
Use cp.sum, not Python’s built-in sum¶
When summing CVXPY expressions, always use cp.sum rather than Python’s built-in sum.
Python’s sum builds up a chain of binary + operations, creating a deep expression tree
that is slow to compile. cp.sum handles the entire sum in a single, efficient operation.
# Slow: Python's sum() creates a deep binary tree of additions
objective = cp.Minimize(sum(cp.square(x)))
# Fast: single efficient operation
objective = cp.Minimize(cp.sum(cp.square(x)))
Use parameters for repeated solves¶
If you need to solve the same problem multiple times with different data values, use
Parameter objects instead of creating a new problem
each time. Parameters, used correctly, allow CVXPY to compile the problem structure once and reuse it across
solves, which is known as DPP (Disciplined Parameterized Programming).
import cvxpy as cp
import numpy as np
n = 100
x = cp.Variable(n)
gamma = cp.Parameter(nonneg=True)
data = cp.Parameter(n)
prob = cp.Problem(cp.Minimize(cp.sum_squares(x - data) + gamma * cp.norm1(x)))
# First solve — compiles and caches the problem structure
gamma.value = 0.1
data.value = np.random.randn(n)
prob.solve()
# Subsequent solves — reuses compiled structure, much faster
gamma.value = 1.0
data.value = np.random.randn(n)
prob.solve()
You can verify your problem is DPP-compliant by calling prob.is_dpp().
Use cp.sum_squares for quadratic objectives¶
When your objective is a sum of squares, always use cp.sum_squares rather than
cp.quad_form with an identity matrix. Using cp.quad_form(x, np.eye(n)) constructs
a dense n×n matrix explicitly, which causes excessive memory usage and slow compile times
for large problems.
import cvxpy as cp
import numpy as np
n = 1000
x = cp.Variable(n)
# Slow and memory-intensive: constructs a dense 1000x1000 identity matrix
objective = cp.Minimize(cp.quad_form(x, np.eye(n)))
# Fast: purpose-built atom, no matrix construction
objective = cp.Minimize(cp.sum_squares(x))
More generally, cp.quad_form(x, P) should only be used when P is a
non-trivial positive semidefinite matrix. Sparse matrices work perfectly fine
with cp.quad_form and do not cause the memory issues described above.
For diagonal P, use cp.quad_form(x, scipy.sparse.diags_array(P_diag)) or cp.sum(cp.multiply(P_diag, cp.square(x)))
or restructure your problem to avoid it entirely.
Choose the right canonicalization backend¶
CVXPY supports multiple canonicalization backends that can significantly affect compile time
depending on your problem structure. You can select a backend via the canon_backend
keyword argument to .solve():
prob.solve(canon_backend=cp.SCIPY_CANON_BACKEND)
The available backends are:
CPP (default): The original C++ implementation. Works well for most problems.
SCIPY: A pure Python implementation using SciPy sparse matrices. Often faster for already-vectorized problems.
COO: A pure Python implementation using 3D COO sparse tensors. Best for DPP-compliant problems with large parameters.
Use verbose mode to diagnose slow problems¶
Solving with verbose=True prints useful diagnostic information:
prob.solve(verbose=True)
The output includes DCP verification time, expression tree node count, and a time breakdown for each compilation step. A large node count strongly suggests the problem needs vectorization.
Summary of tips¶
Tip |
Impact |
|---|---|
Vectorize constraints and objectives |
Very high — can reduce compile time by orders of magnitude |
Use |
High for large sums |
Use parameters for repeated solves (DPP) |
High — amortizes compile cost across solves |
Choose the right canonicalization backend |
Moderate — problem-dependent |
Use |
Diagnostic — helps identify what to fix |
For a detailed benchmark, see the original notebook that inspired this page.