Python / Performance and concurrency
Python Prefetching for Efficient Handling of Huge Data Sets
Contents
- Introduction to Python Prefetching
- Memory-Mapped Files
- NumPy Memory-Mapped Arrays
- Pandas Chunking
- Dask for Parallel Computing
- Itertools for Efficient Iteration
- Generators for Lazy Evaluation
- Multiprocessing for Parallel Data Processing
- PyTables for HDF5 File Format
- Asynchronous I/O with aiofiles
- Vaex for Out-of-Core DataFrames
- Numba for JIT Compilation
- Efficient String Processing with re2
- Additional Resources
Introduction to Python Prefetching
Prefetching is a technique used to optimize data access by loading data into memory before it's actually needed. This can significantly improve performance when working with huge datasets.
import prefetch_generator
@prefetch_generator.background()
def data_generator():
for i in range(1000000):
# Simulate time-consuming data retrieval
time.sleep(0.1)
yield f"Data item {i}"
# Usage
for item in data_generator():
print(item)
Memory-Mapped Files
Memory-mapped files allow you to access file content as if it were in memory, which can be faster than traditional file I/O for large datasets.
import mmap
with open('large_file.dat', 'rb') as f:
mmapped_file = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
# Read data from the memory-mapped file
data = mmapped_file[1000:2000]
NumPy Memory-Mapped Arrays
NumPy provides efficient memory-mapped array operations, which are particularly useful for large numerical datasets.
import numpy as np
# Create a memory-mapped array
mmap_array = np.memmap('large_array.dat', dtype='float32', mode='w+', shape=(1000000,))
# Perform operations on the array
mmap_array[:1000] = np.random.random(1000)
# Flush changes to disk
mmap_array.flush()
Pandas Chunking
Pandas allows you to process large datasets in chunks, reducing memory usage and enabling efficient processing of data that doesn't fit in memory.
import pandas as pd
chunk_size = 100000
for chunk in pd.read_csv('huge_dataset.csv', chunksize=chunk_size):
# Process each chunk
processed_chunk = chunk.apply(some_processing_function)
# Save or accumulate results
processed_chunk.to_csv('processed_data.csv', mode='a', header=False, index=False)
Dask for Parallel Computing
Dask provides parallel computing tools that can handle larger-than-memory datasets by breaking them into smaller chunks.
import dask.dataframe as dd
# Read a large CSV file into a Dask DataFrame
ddf = dd.read_csv('huge_dataset.csv')
# Perform operations on the Dask DataFrame
result = ddf.groupby('column_name').mean().compute()
Itertools for Efficient Iteration
Python's itertools module provides memory-efficient tools for working with iterators, which can be useful when processing large datasets.
from itertools import islice
def chunk_generator(iterable, chunk_size):
iterator = iter(iterable)
return iter(lambda: list(islice(iterator, chunk_size)), [])
# Usage
large_list = range(1000000)
for chunk in chunk_generator(large_list, 1000):
process_chunk(chunk)
Generators for Lazy Evaluation
Generators allow for lazy evaluation of data, which can significantly reduce memory usage when working with large datasets.
def large_data_generator():
for i in range(1000000):
# Simulate data retrieval or generation
yield f"Data item {i}"
# Process data without loading everything into memory
for item in large_data_generator():
process_item(item)
Multiprocessing for Parallel Data Processing
The multiprocessing module allows you to leverage multiple CPU cores for parallel data processing.
from multiprocessing import Pool
def process_chunk(chunk):
return [item * 2 for item in chunk]
if __name__ == '__main__':
large_data = range(1000000)
chunk_size = 100000
chunks = [large_data[i:i+chunk_size] for i in range(0, len(large_data), chunk_size)]
with Pool() as pool:
results = pool.map(process_chunk, chunks)
PyTables for HDF5 File Format
PyTables provides an efficient way to work with HDF5 files, which are well-suited for storing and accessing large datasets.
import tables
class Particle(tables.IsDescription):
name = tables.StringCol(16)
x = tables.Float32Col()
y = tables.Float32Col()
with tables.open_file("particles.h5", mode="w") as h5file:
table = h5file.create_table("/", 'particles', Particle)
# Add data to the table
particle = table.row
for i in range(1000000):
particle['name'] = f'particle_{i}'
particle['x'] = i
particle['y'] = i * 2
particle.append()
# Flush the table to disk
table.flush()
Asynchronous I/O with aiofiles
Asynchronous I/O can improve performance when dealing with multiple files or network resources.
import asyncio
import aiofiles
async def process_file(filename):
async with aiofiles.open(filename, mode='r') as f:
contents = await f.read()
return len(contents)
async def main():
files = ['file1.txt', 'file2.txt', 'file3.txt']
tasks = [process_file(file) for file in files]
results = await asyncio.gather(*tasks)
print(results)
asyncio.run(main())
Vaex for Out-of-Core DataFrames
Vaex is a Python library that provides out-of-core DataFrames for processing large datasets that don't fit in memory.
import vaex
# Open a large CSV file
df = vaex.from_csv('huge_dataset.csv', convert=True)
# Perform operations on the DataFrame
result = df.mean(['column1', 'column2'])
# Export results
df.export_hdf5('processed_data.hdf5')
Numba for JIT Compilation
Numba can significantly speed up numerical Python code through just-in-time (JIT) compilation.
from numba import jit
import numpy as np
@jit(nopython=True)
def process_array(arr):
result = np.empty_like(arr)
for i in range(len(arr)):
result[i] = arr[i] ** 2 + 2 * arr[i] - 1
return result
# Usage
large_array = np.random.random(1000000)
processed = process_array(large_array)
Efficient String Processing with re2
The re2 library provides a fast alternative to Python's built-in regex engine, which can be beneficial for processing large text datasets.
import re2
# Compile the regex pattern
pattern = re2.compile(r'\b\w+\b')
def count_words(text):
return len(pattern.findall(text))
# Process a large text file
with open('large_text_file.txt', 'r') as f:
total_words = sum(count_words(line) for line in f)
print(f"Total words: {total_words}")
Additional Resources
- "Efficient Data Processing in Python" - ArXiv:2105.05158 https://arxiv.org/abs/2105.05158
- "Large-Scale Data Processing with Python" - ArXiv:1907.05073 https://arxiv.org/abs/1907.05073
- Official documentation for libraries mentioned in the slides:
- NumPy: https://numpy.org/doc/
- Pandas: https://pandas.pydata.org/docs/
- Dask: https://docs.dask.org/
- PyTables: https://www.pytables.org/
- Vaex: https://vaex.io/docs/
- Numba: https://numba.pydata.org/