Performance Guide
This guide covers performance optimization techniques and best practices for the Simplex Noise Library.
Performance Characteristics
Baseline Performance
- Single noise call: ~1-5 microseconds
- Bulk generation: ~1M samples/second on modern hardware
- Memory usage: ~50KB base + configurable cache
- Thread safety: Safe for concurrent access
Performance Factors
- PRNG Algorithm - Different algorithms have different speeds
- Noise Variant - Some variants are more expensive than others
- Fractal Octaves - More octaves = more computation
- Caching - Can significantly improve repeated access
- Bulk Generation - Much faster than individual calls
Optimization Techniques
1. Choose the Right PRNG
simplex_config_t config = simplex_get_default_config();
// Fastest (but lower quality)
config.prng_type = SIMPLEX_PRG_LINEAR_CONGRUENTIAL;
// Balanced (good quality and speed)
config.prng_type = SIMPLEX_PRG_PCG;
// Highest quality (slower)
config.prng_type = SIMPLEX_PRG_MERSENNE_TWISTER;
Performance Ranking:
- Linear Congruential (fastest)
- PCG (balanced)
- Xorshift (good)
- Mersenne Twister (slowest, highest quality)
2. Use Bulk Generation
❌ Inefficient - Individual calls:
double* noise_data = malloc(width * height * sizeof(double));
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
noise_data[y * width + x] = simplex_noise_2d(x * 0.1, y * 0.1);
}
}
✅ Efficient - Bulk generation:
double* noise_data = malloc(width * height * sizeof(double));
simplex_noise_array_2d(0, 0, width, height, 0.1, noise_data);
Performance improvement: 3-5x faster for large arrays.
3. Enable Caching
simplex_config_t config = simplex_get_default_config();
config.enable_caching = 1;
config.cache_size_mb = 64.0; // Adjust based on needs
simplex_noise_init_advanced(&config);
When to use caching:
- Repeated access to same coordinates
- Interactive applications with real-time updates
- Memory is not a constraint
Cache hit rates:
- Terrain generation: 60-80%
- Texture synthesis: 40-60%
- Random sampling: 10-20%
4. Optimize Fractal Parameters
// Fewer octaves = faster generation
config.octaves = 4; // Instead of 8
// Lower persistence = less computation per octave
config.persistence = 0.5; // Instead of 0.7
// Higher lacunarity = fewer samples per octave
config.lacunarity = 2.0; // Good default
5. Use Appropriate Noise Variants
Performance ranking (fastest to slowest):
- Classic simplex noise
- Billowy noise
- Ridged noise
- Fractional Brownian Motion
- Hybrid Multi-Fractal
- Domain Warping
6. Memory Management
// Set appropriate memory limits
config.memory_limit_mb = 256.0; // Adjust based on available memory
config.cache_size_mb = 64.0; // Balance between speed and memory
// Clean up when done
simplex_cleanup();
Benchmarking
Performance Testing
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "simplex_noise.h"
void benchmark_noise_generation() {
const int iterations = 1000000;
clock_t start, end;
double time_taken;
simplex_noise_init(12345);
// Benchmark 2D noise
start = clock();
for (int i = 0; i < iterations; i++) {
double noise = simplex_noise_2d(i * 0.001, i * 0.001);
(void)noise; // Prevent optimization
}
end = clock();
time_taken = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("2D Noise: %.0f calls/second\n", iterations / time_taken);
// Benchmark fractal noise
start = clock();
for (int i = 0; i < iterations / 10; i++) { // Fewer iterations due to complexity
double noise = simplex_fractal_2d(i * 0.001, i * 0.001, 4, 0.5, 2.0);
(void)noise;
}
end = clock();
time_taken = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("Fractal Noise: %.0f calls/second\n", (iterations / 10) / time_taken);
}
int main() {
benchmark_noise_generation();
return 0;
}
Profiling with Built-in Stats
void profile_application() {
simplex_config_t config = simplex_get_default_config();
config.enable_profiling = 1;
config.enable_caching = 1;
simplex_noise_init_advanced(&config);
// Your application code here
for (int i = 0; i < 1000; i++) {
simplex_noise_2d(i * 0.1, i * 0.1);
}
// Get performance statistics
simplex_perf_stats_t stats;
simplex_get_performance_stats(&stats);
printf("Performance Statistics:\n");
printf("Function calls: %zu\n", stats.function_calls);
printf("Cache hits: %zu\n", stats.cache_hits);
printf("Cache misses: %zu\n", stats.cache_misses);
printf("Cache hit rate: %.1f%%\n",
(double)stats.cache_hits / stats.function_calls * 100.0);
printf("Average execution time: %.3f microseconds\n",
stats.average_execution_time);
}
Real-World Performance Tips
1. Terrain Generation
// For large terrain chunks, use bulk generation
void generate_terrain_chunk(int chunk_x, int chunk_z, int chunk_size) {
double* heightmap = malloc(chunk_size * chunk_size * sizeof(double));
// Use bulk generation for the entire chunk
simplex_noise_array_2d(
chunk_x * chunk_size * 0.01,
chunk_z * chunk_size * 0.01,
chunk_size,
chunk_size,
0.01,
heightmap
);
// Process heightmap...
free(heightmap);
}
2. Texture Generation
// For texture synthesis, consider caching
void generate_texture(int width, int height) {
simplex_config_t config = simplex_get_default_config();
config.enable_caching = 1; // Enable for repeated access
simplex_noise_init_advanced(&config);
// Generate texture with multiple octaves
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
double noise = simplex_fractal_2d(x * 0.01, y * 0.01, 3, 0.5, 2.0);
// Use noise for texture...
}
}
}
3. Real-time Applications
// For real-time applications, optimize for speed
void setup_realtime_noise() {
simplex_config_t config = simplex_get_default_config();
// Use fastest PRNG
config.prng_type = SIMPLEX_PRG_LINEAR_CONGRUENTIAL;
// Enable caching for repeated access
config.enable_caching = 1;
config.cache_size_mb = 32.0;
// Use fewer octaves for fractal noise
config.octaves = 3;
simplex_noise_init_advanced(&config);
}
Memory Optimization
1. Configurable Memory Limits
// Set memory limits based on available resources
simplex_config_t config = simplex_get_default_config();
// For memory-constrained systems
config.memory_limit_mb = 64.0;
config.cache_size_mb = 16.0;
// For high-performance systems
config.memory_limit_mb = 512.0;
config.cache_size_mb = 128.0;
2. Cleanup Strategies
// Clean up when switching between different noise types
void switch_noise_type(simplex_noise_variant_t new_variant) {
simplex_cleanup(); // Clear cache and reset state
simplex_config_t config = simplex_get_default_config();
config.noise_variant = new_variant;
simplex_noise_init_advanced(&config);
}
Threading Considerations
Thread Safety
The library is thread-safe for read operations, but configuration changes should be synchronized:
// Safe: Multiple threads can call noise functions
double noise1 = simplex_noise_2d(x1, y1); // Thread 1
double noise2 = simplex_noise_2d(x2, y2); // Thread 2
// Unsafe: Configuration changes need synchronization
simplex_set_prng(SIMPLEX_PRG_PCG); // Needs mutex protection
Multi-threaded Generation
#include <pthread.h>
typedef struct {
int start_y, end_y;
int width;
double* noise_data;
} thread_data_t;
void* generate_noise_thread(void* arg) {
thread_data_t* data = (thread_data_t*)arg;
for (int y = data->start_y; y < data->end_y; y++) {
for (int x = 0; x < data->width; x++) {
data->noise_data[y * data->width + x] =
simplex_noise_2d(x * 0.01, y * 0.01);
}
}
return NULL;
}
void generate_noise_parallel(int width, int height, int num_threads) {
double* noise_data = malloc(width * height * sizeof(double));
pthread_t* threads = malloc(num_threads * sizeof(pthread_t));
thread_data_t* thread_data = malloc(num_threads * sizeof(thread_data_t));
int rows_per_thread = height / num_threads;
for (int i = 0; i < num_threads; i++) {
thread_data[i].start_y = i * rows_per_thread;
thread_data[i].end_y = (i + 1) * rows_per_thread;
thread_data[i].width = width;
thread_data[i].noise_data = noise_data;
pthread_create(&threads[i], NULL, generate_noise_thread, &thread_data[i]);
}
for (int i = 0; i < num_threads; i++) {
pthread_join(threads[i], NULL);
}
free(threads);
free(thread_data);
free(noise_data);
}
Performance Monitoring
Built-in Profiling
void monitor_performance() {
simplex_config_t config = simplex_get_default_config();
config.enable_profiling = 1;
simplex_noise_init_advanced(&config);
// Your application code...
// Check performance periodically
simplex_perf_stats_t stats;
simplex_get_performance_stats(&stats);
if (stats.cache_hits < stats.function_calls * 0.5) {
printf("Warning: Low cache hit rate (%.1f%%)\n",
(double)stats.cache_hits / stats.function_calls * 100.0);
}
if (stats.memory_used > config.memory_limit_mb * 1024 * 1024 * 0.9) {
printf("Warning: High memory usage (%.1f MB)\n",
stats.memory_used / (1024.0 * 1024.0));
}
}
Best Practices Summary
- Use bulk generation for large arrays
- Enable caching for repeated access patterns
- Choose appropriate PRNG for your quality/speed needs
- Optimize fractal parameters based on visual requirements
- Monitor performance with built-in profiling
- Clean up resources when switching configurations
- Use threading for parallel generation when appropriate
- Set memory limits based on available resources
For more advanced optimization techniques, see Advanced Usage