Skip to content

Exemples d'optimisation des performances

Voici quelques exemples et meilleures pratiques pour optimiser les performances de Sharp.

Optimisation mémoire

Traitement en flux pour les gros fichiers

javascript
import sharp from 'sharp';
import fs from 'fs';

// Traitement en flux, éviter de charger tout le fichier en mémoire
fs.createReadStream('large-image.jpg')
  .pipe(sharp().resize(800, 600))
  .pipe(fs.createWriteStream('output.jpg'));

Utiliser Buffer au lieu de fichier

javascript
// Pour les petits fichiers, utiliser Buffer est plus efficace
const inputBuffer = fs.readFileSync('input.jpg');
const outputBuffer = await sharp(inputBuffer)
  .resize(300, 200)
  .jpeg({ quality: 80 })
  .toBuffer();

fs.writeFileSync('output.jpg', outputBuffer);

Libérer les ressources en temps opportun

javascript
// Libérer en temps opportun après le traitement
const image = sharp('input.jpg');
await image.resize(300, 200).toFile('output.jpg');
// L'instance image sera automatiquement récupérée par le garbage collector

Contrôle de concurrence

Limiter le nombre de concurrences

javascript
// Définir le nombre maximum de concurrences
sharp.concurrency(4);

// Contrôler la concurrence lors du traitement par lot
async function batchProcess(files) {
  const batchSize = 4;
  const results = [];
  
  for (let i = 0; i < files.length; i += batchSize) {
    const batch = files.slice(i, i + batchSize);
    const batchPromises = batch.map(file => 
      sharp(file).resize(300, 200).jpeg().toFile(`output_${file}`)
    );
    
    await Promise.all(batchPromises);
    results.push(...batch);
  }
  
  return results;
}

Utiliser une file d'attente de traitement

javascript
class ImageProcessor {
  constructor(concurrency = 4) {
    this.concurrency = concurrency;
    this.queue = [];
    this.running = 0;
  }
  
  async add(task) {
    return new Promise((resolve, reject) => {
      this.queue.push({ task, resolve, reject });
      this.process();
    });
  }
  
  async process() {
    if (this.running >= this.concurrency || this.queue.length === 0) {
      return;
    }
    
    this.running++;
    const { task, resolve, reject } = this.queue.shift();
    
    try {
      const result = await task();
      resolve(result);
    } catch (error) {
      reject(error);
    } finally {
      this.running--;
      this.process();
    }
  }
}

// Exemple d'utilisation
const processor = new ImageProcessor(4);

for (const file of files) {
  processor.add(async () => {
    await sharp(file).resize(300, 200).jpeg().toFile(`output_${file}`);
  });
}

Optimisation du cache

Mettre en cache les résultats de traitement

javascript
const cache = new Map();

async function processWithCache(inputPath, width, height) {
  const key = `${inputPath}_${width}_${height}`;
  
  if (cache.has(key)) {
    return cache.get(key);
  }
  
  const result = await sharp(inputPath)
    .resize(width, height)
    .jpeg({ quality: 80 })
    .toBuffer();
  
  cache.set(key, result);
  return result;
}

Vider le cache Sharp

javascript
// Vider périodiquement le cache pour libérer la mémoire
setInterval(() => {
  sharp.cache(false);
}, 60000); // Vider une fois par minute

Sélection d'algorithmes

Choisir l'algorithme de redimensionnement approprié

javascript
// Pour la réduction, utiliser un algorithme plus rapide
await sharp('input.jpg')
  .resize(300, 200, { kernel: sharp.kernel.cubic })
  .toFile('output.jpg');

// Pour l'agrandissement, utiliser un algorithme de meilleure qualité
await sharp('input.jpg')
  .resize(1200, 800, { kernel: sharp.kernel.lanczos3 })
  .toFile('output.jpg');

Optimisation du traitement par lot

javascript
async function optimizedBatchProcess(files) {
  // Grouper par taille pour le traitement
  const smallFiles = [];
  const largeFiles = [];
  
  for (const file of files) {
    const metadata = await sharp(file).metadata();
    if (metadata.width * metadata.height < 1000000) {
      smallFiles.push(file);
    } else {
      largeFiles.push(file);
    }
  }
  
  // Petits fichiers utilisent un algorithme rapide
  await Promise.all(smallFiles.map(file =>
    sharp(file)
      .resize(300, 200, { kernel: sharp.kernel.cubic })
      .jpeg({ quality: 80 })
      .toFile(`output_${file}`)
  ));
  
  // Gros fichiers utilisent un algorithme de haute qualité
  await Promise.all(largeFiles.map(file =>
    sharp(file)
      .resize(800, 600, { kernel: sharp.kernel.lanczos3 })
      .jpeg({ quality: 90 })
      .toFile(`output_${file}`)
  ));
}

Optimisation réseau

Réponse en flux

javascript
// Exemple Express.js
app.get('/image/:filename', async (req, res) => {
  const filename = req.params.filename;
  
  try {
    const imageStream = sharp(`images/${filename}`)
      .resize(300, 200)
      .jpeg({ quality: 80 });
    
    res.set('Content-Type', 'image/jpeg');
    imageStream.pipe(res);
  } catch (error) {
    res.status(404).send('Image not found');
  }
});

Traitement conditionnel

javascript
app.get('/image/:filename', async (req, res) => {
  const { filename } = req.params;
  const { width, height, quality = 80 } = req.query;
  
  try {
    let image = sharp(`images/${filename}`);
    
    if (width || height) {
      image = image.resize(parseInt(width), parseInt(height));
    }
    
    if (req.headers.accept?.includes('image/webp')) {
      image = image.webp({ quality: parseInt(quality) });
      res.set('Content-Type', 'image/webp');
    } else {
      image = image.jpeg({ quality: parseInt(quality) });
      res.set('Content-Type', 'image/jpeg');
    }
    
    image.pipe(res);
  } catch (error) {
    res.status(404).send('Image not found');
  }
});

Surveillance et débogage

Surveillance des performances

javascript
async function processWithTiming(inputPath, outputPath) {
  const startTime = Date.now();
  
  try {
    await sharp(inputPath)
      .resize(800, 600)
      .jpeg({ quality: 80 })
      .toFile(outputPath);
    
    const endTime = Date.now();
    console.log(`Temps de traitement: ${endTime - startTime}ms`);
  } catch (error) {
    console.error('Échec du traitement:', error.message);
  }
}

Surveillance de l'utilisation mémoire

javascript
const process = require('process');

function logMemoryUsage() {
  const usage = process.memoryUsage();
  console.log('Utilisation mémoire:', {
    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`
  });
}

// Enregistrer l'utilisation mémoire avant et après le traitement
logMemoryUsage();
await sharp('input.jpg').resize(800, 600).toFile('output.jpg');
logMemoryUsage();

Gestion des erreurs et nouvelle tentative

Mécanisme de nouvelle tentative

javascript
async function processWithRetry(inputPath, outputPath, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      await sharp(inputPath)
        .resize(800, 600)
        .jpeg({ quality: 80 })
        .toFile(outputPath);
      
      console.log(`Traitement réussi, nombre de tentatives: ${attempt}`);
      return;
    } catch (error) {
      console.error(`Tentative ${attempt} échouée:`, error.message);
      
      if (attempt === maxRetries) {
        throw new Error(`Échec du traitement, ${maxRetries} tentatives effectuées`);
      }
      
      // Attendre un moment avant de réessayer
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

Traitement des erreurs par catégorie

javascript
async function robustProcess(inputPath, outputPath) {
  try {
    await sharp(inputPath)
      .resize(800, 600)
      .jpeg({ quality: 80 })
      .toFile(outputPath);
  } catch (error) {
    if (error.code === 'VipsForeignLoad') {
      console.error('Format d'image non supporté');
    } else if (error.code === 'VipsForeignLoadLimit') {
      console.error('Image trop grande, tentative de réduction');
      // Essayer de traiter une version plus petite
      await sharp(inputPath, { limitInputPixels: 268402689 })
        .resize(400, 300)
        .jpeg({ quality: 80 })
        .toFile(outputPath);
    } else if (error.code === 'ENOSPC') {
      console.error('Espace disque insuffisant');
    } else {
      console.error('Erreur inconnue:', error.message);
    }
  }
}

Résumé des meilleures pratiques

1. Choisir la méthode de traitement appropriée

javascript
// Petits fichiers : traitement direct
if (fileSize < 1024 * 1024) {
  await sharp(file).resize(300, 200).toFile(output);
}

// Gros fichiers : traitement en flux
else {
  fs.createReadStream(file)
    .pipe(sharp().resize(300, 200))
    .pipe(fs.createWriteStream(output));
}

2. Optimisation du traitement par lot

javascript
// Utiliser Promise.all pour le traitement concurrent
const promises = files.map(file => 
  sharp(file).resize(300, 200).jpeg().toFile(`output_${file}`)
);
await Promise.all(promises);

3. Gestion de la mémoire

javascript
// Nettoyer périodiquement le cache
setInterval(() => {
  sharp.cache(false);
}, 300000); // Nettoyer toutes les 5 minutes

4. Gestion des erreurs

javascript
// Toujours utiliser try-catch
try {
  await sharp(input).resize(300, 200).toFile(output);
} catch (error) {
  console.error('Échec du traitement:', error.message);
  // Fournir une solution de secours
}

Liens connexes

Distribué sous licence Apache 2.0.