Skip to content

Ottimizzazione Prestazioni

Sharp è già una libreria ad alte prestazioni per l'elaborazione di immagini, ma attraverso alcune tecniche di ottimizzazione puoi migliorare ulteriormente le prestazioni.

Benchmark

Vantaggi prestazioni di Sharp rispetto ad altre librerie di elaborazione immagini:

  • 4-5 volte più veloce di ImageMagick
  • 4-5 volte più veloce di GraphicsMagick
  • Utilizzo memoria più basso
  • Supporto elaborazione streaming

Ottimizzazione Memoria

Usare Elaborazione Streaming

Per file grandi, l'elaborazione streaming può ridurre significativamente l'utilizzo memoria:

javascript
import fs from 'fs';

// ❌ Non raccomandato: caricare l'intero file in memoria
const buffer = fs.readFileSync('large-image.jpg');
await sharp(buffer).resize(800, 600).toFile('output.jpg');

// ✅ Raccomandato: usare elaborazione streaming
fs.createReadStream('large-image.jpg')
  .pipe(sharp().resize(800, 600).jpeg())
  .pipe(fs.createWriteStream('output.jpg'));

Elaborazione a Blocchi

Per file molto grandi, puoi elaborare a blocchi:

javascript
import fs from 'fs';

async function processLargeFile(inputPath, outputPath, chunkSize = 1024 * 1024) {
  const pipeline = sharp()
    .resize(800, 600)
    .jpeg({ quality: 80 });

  return new Promise((resolve, reject) => {
    const readStream = fs.createReadStream(inputPath, { highWaterMark: chunkSize });
    const writeStream = fs.createWriteStream(outputPath);

    readStream
      .pipe(pipeline)
      .pipe(writeStream)
      .on('finish', resolve)
      .on('error', reject);
  });
}

await processLargeFile('large-input.jpg', 'output.jpg');

Liberare Memoria Tempestivamente

javascript
// Pulire tempestivamente dopo l'elaborazione
async function processImage(inputPath, outputPath) {
  const sharpInstance = sharp(inputPath);
  
  try {
    await sharpInstance
      .resize(800, 600)
      .jpeg({ quality: 80 })
      .toFile(outputPath);
  } finally {
    // Pulizia manuale (anche se Node.js farà garbage collection automatica)
    sharpInstance.destroy();
  }
}

Ottimizzazione Concorrenza

Controllare Numero Concorrenza

javascript
async function processWithConcurrency(files, concurrency = 3) {
  const results = [];
  
  for (let i = 0; i < files.length; i += concurrency) {
    const batch = files.slice(i, i + concurrency);
    const batchPromises = batch.map(file => 
      sharp(file)
        .resize(300, 200)
        .jpeg({ quality: 80 })
        .toFile(`processed-${file}`)
    );
    
    const batchResults = await Promise.all(batchPromises);
    results.push(...batchResults);
  }
  
  return results;
}

const files = ['file1.jpg', 'file2.jpg', 'file3.jpg', 'file4.jpg'];
await processWithConcurrency(files, 2);

Usare Worker Thread

Per task intensivi CPU, puoi usare worker thread:

javascript
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';

if (isMainThread) {
  // Thread principale
  async function processWithWorkers(files, numWorkers = 4) {
    const workers = [];
    const results = [];
    
    for (let i = 0; i < numWorkers; i++) {
      const worker = new Worker('./image-worker.js', {
        workerData: { files: files.slice(i * Math.ceil(files.length / numWorkers), (i + 1) * Math.ceil(files.length / numWorkers)) }
      });
      
      worker.on('message', (result) => {
        results.push(result);
      });
      
      workers.push(worker);
    }
    
    await Promise.all(workers.map(worker => new Promise(resolve => worker.on('exit', resolve))));
    return results;
  }
  
  const files = ['file1.jpg', 'file2.jpg', 'file3.jpg'];
  await processWithWorkers(files);
} else {
  // Worker thread
  const { files } = workerData;
  
  for (const file of files) {
    await sharp(file)
      .resize(300, 200)
      .jpeg({ quality: 80 })
      .toFile(`processed-${file}`);
  }
  
  parentPort.postMessage('done');
}

Ottimizzazione Cache

Cachare Risultati Elaborazione

javascript
import crypto from 'crypto';
import fs from 'fs';

class ImageCache {
  constructor(cacheDir = './cache') {
    this.cacheDir = cacheDir;
    if (!fs.existsSync(cacheDir)) {
      fs.mkdirSync(cacheDir, { recursive: true });
    }
  }

  generateCacheKey(inputPath, options) {
    const content = JSON.stringify({ inputPath, options });
    return crypto.createHash('md5').update(content).digest('hex');
  }

  async getCachedResult(cacheKey) {
    const cachePath = `${this.cacheDir}/${cacheKey}.jpg`;
    if (fs.existsSync(cachePath)) {
      return cachePath;
    }
    return null;
  }

  async setCachedResult(cacheKey, resultPath) {
    const cachePath = `${this.cacheDir}/${cacheKey}.jpg`;
    fs.copyFileSync(resultPath, cachePath);
  }

  async processImage(inputPath, options) {
    const cacheKey = this.generateCacheKey(inputPath, options);
    const cached = await this.getCachedResult(cacheKey);
    
    if (cached) {
      console.log('Usare risultato cache');
      return cached;
    }

    const outputPath = `output-${Date.now()}.jpg`;
    await sharp(inputPath)
      .resize(options.width, options.height)
      .jpeg({ quality: options.quality })
      .toFile(outputPath);

    await this.setCachedResult(cacheKey, outputPath);
    return outputPath;
  }
}

const cache = new ImageCache();
await cache.processImage('input.jpg', { width: 300, height: 200, quality: 80 });

Ottimizzazione Algoritmo

Scegliere Algoritmo Ridimensionamento Appropriato

javascript
// Per foto, usare kernel lanczos3
await sharp('photo.jpg')
  .resize(800, 600, { kernel: sharp.kernel.lanczos3 })
  .toFile('photo-resized.jpg');

// Per icone o immagini lineari, usare kernel nearest
await sharp('icon.png')
  .resize(32, 32, { kernel: sharp.kernel.nearest })
  .toFile('icon-resized.png');

// Per immagini che richiedono elaborazione rapida, usare kernel cubic
await sharp('image.jpg')
  .resize(300, 200, { kernel: sharp.kernel.cubic })
  .toFile('image-resized.jpg');

Ottimizzare Qualità JPEG

javascript
// Regolare qualità in base al contenuto immagine
async function optimizeJPEGQuality(inputPath, outputPath) {
  const metadata = await sharp(inputPath).metadata();
  
  // Regolare qualità in base alle dimensioni immagine
  let quality = 80;
  if (metadata.width > 1920 || metadata.height > 1080) {
    quality = 85; // Immagini grandi usano qualità più alta
  } else if (metadata.width < 800 && metadata.height < 600) {
    quality = 75; // Immagini piccole possono usare qualità più bassa
  }
  
  await sharp(inputPath)
    .jpeg({ 
      quality,
      progressive: true, // JPEG progressivo
      mozjpeg: true      // Usare ottimizzazione mozjpeg
    })
    .toFile(outputPath);
}

Ottimizzazione Rete

Pre-generare Dimensioni Diverse

javascript
const sizes = [
  { width: 320, suffix: 'sm' },
  { width: 640, suffix: 'md' },
  { width: 1024, suffix: 'lg' },
  { width: 1920, suffix: 'xl' }
];

async function pregenerateSizes(inputPath) {
  const promises = sizes.map(size => 
    sharp(inputPath)
      .resize(size.width, null, { fit: 'inside' })
      .jpeg({ quality: 80 })
      .toFile(`output-${size.suffix}.jpg`)
  );
  
  await Promise.all(promises);
}

await pregenerateSizes('input.jpg');

Usare Formati Moderni

javascript
// Generare più formati per supportare diversi browser
async function generateModernFormats(inputPath) {
  const promises = [
    // JPEG come fallback
    sharp(inputPath)
      .resize(800, 600)
      .jpeg({ quality: 80 })
      .toFile('output.jpg'),
    
    // WebP per browser moderni
    sharp(inputPath)
      .resize(800, 600)
      .webp({ quality: 80 })
      .toFile('output.webp'),
    
    // AVIF per browser più recenti
    sharp(inputPath)
      .resize(800, 600)
      .avif({ quality: 80 })
      .toFile('output.avif')
  ];
  
  await Promise.all(promises);
}

Monitoraggio e Debug

Monitoraggio Prestazioni

javascript
import { performance } from 'perf_hooks';

async function measurePerformance(fn) {
  const start = performance.now();
  const result = await fn();
  const end = performance.now();
  
  console.log(`Tempo esecuzione: ${end - start}ms`);
  return result;
}

await measurePerformance(async () => {
  await sharp('input.jpg')
    .resize(800, 600)
    .jpeg({ quality: 80 })
    .toFile('output.jpg');
});

Monitoraggio Utilizzo Memoria

javascript
import { performance } from 'perf_hooks';

function getMemoryUsage() {
  const usage = process.memoryUsage();
  return {
    rss: `${Math.round(usage.rss / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)}MB`,
    heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
    external: `${Math.round(usage.external / 1024 / 1024)}MB`
  };
}

console.log('Utilizzo memoria prima elaborazione:', getMemoryUsage());

await sharp('input.jpg')
  .resize(800, 600)
  .jpeg({ quality: 80 })
  .toFile('output.jpg');

console.log('Utilizzo memoria dopo elaborazione:', getMemoryUsage());

Riepilogo Best Practice

  1. Usare elaborazione streaming per file grandi
  2. Controllare numero concorrenza
  3. Cachare risultati elaborazione
  4. Scegliere algoritmo ridimensionamento appropriato
  5. Ottimizzare formato output e qualità
  6. Pre-generare dimensioni comuni
  7. Monitorare metriche prestazioni

Confronto Prestazioni

OperazioneSharpImageMagickGraphicsMagick
Ridimensionamento100ms450ms420ms
Conversione formato80ms380ms360ms
Applicazione filtro120ms520ms480ms
Utilizzo memoriaBassoAltoAlto

Prossimi Passi

  • Consultare la documentazione API per conoscere tutti i metodi disponibili
  • Imparare dagli esempi per maggiori utilizzi
  • Consultare il changelog per conoscere le ultime funzionalità

Rilasciato sotto licenza Apache 2.0.