Optimization Patterns and Recommendations
This document provides language-specific optimization patterns based on common performance bottlenecks.
Python Optimization Patterns
1. String Concatenation
Problem: Repeated string concatenation with + creates many intermediate objects.
Bad:
result = ""
for item in items:
result += str(item) # O(n²) complexity
Good:
result = "".join(str(item) for item in items) # O(n) complexity
2. List Comprehensions vs Loops
Problem: Explicit loops are slower than comprehensions.
Bad:
squares = []
for x in range(1000):
squares.append(x * x)
Good:
squares = [x * x for x in range(1000)]
3. Avoid Repeated Lookups
Problem: Dictionary/attribute lookups in loops are expensive.
Bad:
for item in items:
process(math.sqrt(item)) # Repeated module lookup
Good:
sqrt = math.sqrt
for item in items:
process(sqrt(item))
4. Use Built-in Functions
Problem: Python loops are slow; built-ins are implemented in C.
Bad:
total = 0
for x in numbers:
total += x
Good:
total = sum(numbers)
5. Generator Expressions for Large Data
Problem: List comprehensions load everything into memory.
Bad:
total = sum([x * x for x in range(10000000)]) # Uses lots of memory
Good:
total = sum(x * x for x in range(10000000)) # Generator, constant memory
6. Use __slots__ for Many Objects
Problem: Instance dictionaries consume memory.
Bad:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
Good:
class Point:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
7. Cache Expensive Computations
Problem: Recomputing the same values repeatedly.
Good:
from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_function(n):
# Complex computation
return result
Java Optimization Patterns
1. StringBuilder for String Concatenation
Problem: String concatenation creates many temporary objects.
Bad:
String result = "";
for (String item : items) {
result += item; // Creates new String each time
}
Good:
StringBuilder sb = new StringBuilder();
for (String item : items) {
sb.append(item);
}
String result = sb.toString();
2. Pre-size Collections
Problem: Dynamic resizing causes array copying.
Bad:
List<String> list = new ArrayList<>(); // Default capacity 10
for (int i = 0; i < 10000; i++) {
list.add("item" + i); // Multiple resizes
}
Good:
List<String> list = new ArrayList<>(10000); // Pre-sized
for (int i = 0; i < 10000; i++) {
list.add("item" + i);
}
3. Use Primitive Collections
Problem: Autoboxing creates wrapper objects.
Bad:
List<Integer> numbers = new ArrayList<>(); // Boxes each int
Good:
// Use libraries like fastutil or Eclipse Collections
IntArrayList numbers = new IntArrayList();
4. Avoid Reflection in Hot Paths
Problem: Reflection is 10-100x slower than direct calls.
Bad:
for (Object obj : objects) {
method.invoke(obj); // Reflection in loop
}
Good:
// Cache method handles or use direct calls
for (MyClass obj : objects) {
obj.myMethod();
}
5. Use EnumMap/EnumSet
Problem: HashMap has overhead for enum keys.
Bad:
Map<MyEnum, String> map = new HashMap<>();
Good:
Map<MyEnum, String> map = new EnumMap<>(MyEnum.class);
6. Lazy Initialization
Problem: Initializing expensive objects that may not be used.
Good:
private volatile ExpensiveObject instance;
public ExpensiveObject getInstance() {
if (instance == null) {
synchronized (this) {
if (instance == null) {
instance = new ExpensiveObject();
}
}
}
return instance;
}
7. Stream API Considerations
Problem: Streams have overhead for small collections.
Bad (for small n):
list.stream().filter(x -> x > 0).count(); // Overhead for small lists
Good (for small n):
int count = 0;
for (int x : list) {
if (x > 0) count++;
}
C/C++ Optimization Patterns
1. Reserve Vector Capacity
Problem: Dynamic resizing causes reallocations.
Bad:
std::vector<int> vec;
for (int i = 0; i < 10000; i++) {
vec.push_back(i); // Multiple reallocations
}
Good:
std::vector<int> vec;
vec.reserve(10000); // Single allocation
for (int i = 0; i < 10000; i++) {
vec.push_back(i);
}
2. Pass by const Reference
Problem: Passing large objects by value causes copying.
Bad:
void process(std::string str) { // Copies string
// ...
}
Good:
void process(const std::string& str) { // No copy
// ...
}
3. Use Move Semantics
Problem: Unnecessary copies when transferring ownership.
Bad:
std::vector<int> create() {
std::vector<int> result;
// fill result
return result; // May copy (pre-C++11)
}
Good:
std::vector<int> create() {
std::vector<int> result;
// fill result
return std::move(result); // Move, no copy
}
4. Inline Small Functions
Problem: Function call overhead for tiny functions.
Good:
inline int square(int x) {
return x * x;
}
5. Use emplace Instead of push
Problem: push_back constructs temporary then copies.
Bad:
vec.push_back(MyClass(a, b, c)); // Construct temp, then copy
Good:
vec.emplace_back(a, b, c); // Construct in-place
6. Avoid Virtual Functions in Hot Paths
Problem: Virtual dispatch prevents inlining.
Consider: Use templates or CRTP for compile-time polymorphism.
7. Memory Alignment
Problem: Unaligned access is slower.
Good:
alignas(64) struct CacheLine {
int data[16];
};
8. Loop Optimizations
Problem: Inefficient loop patterns.
Bad:
for (int i = 0; i < vec.size(); i++) { // size() called each iteration
process(vec[i]);
}
Good:
const size_t n = vec.size();
for (size_t i = 0; i < n; i++) {
process(vec[i]);
}
Cross-Language Patterns
1. Algorithm Complexity
Always choose the right algorithm first:
- O(n²) → O(n log n): Use better sorting/searching
- O(n) → O(1): Use hash tables instead of linear search
- O(2ⁿ) → O(n): Use dynamic programming
2. Data Structure Selection
- Frequent lookups: Hash table/map
- Ordered iteration: Tree-based map
- Stack operations: Vector/ArrayList
- Queue operations: Deque/LinkedList
- Many small objects: Object pooling
3. I/O Optimization
- Buffer reads/writes
- Use binary formats over text
- Batch operations
- Async I/O for concurrent operations
4. Memory Access Patterns
- Sequential access is faster than random
- Cache-friendly data structures
- Avoid pointer chasing
- Structure of Arrays (SoA) vs Array of Structures (AoS)
5. Parallelization
- Identify independent operations
- Use thread pools, avoid creating threads
- Consider data parallelism
- Watch for false sharing
Optimization Workflow
- Profile first: Don't guess, measure
- Focus on hotspots: 80/20 rule applies
- One change at a time: Measure impact
- Consider readability: Don't over-optimize
- Test correctness: Ensure optimizations don't break functionality
- Document tradeoffs: Explain non-obvious optimizations
When NOT to Optimize
- Premature optimization is the root of all evil
- Code that runs once or rarely
- Code that's already fast enough
- When it hurts readability significantly
- When it makes maintenance difficult