Optimisation des performances
Sharp est déjà une bibliothèque de traitement d'image haute performance, mais avec quelques techniques d'optimisation, vous pouvez améliorer encore plus les performances.
Tests de performance
Avantages de performance de Sharp par rapport à d'autres bibliothèques de traitement d'image :
- 4 à 5 fois plus rapide qu'ImageMagick
- 4 à 5 fois plus rapide que GraphicsMagick
- Consommation mémoire plus faible
- Support du traitement en flux
Optimisation mémoire
Utiliser le traitement en flux
Pour les gros fichiers, utiliser le traitement en flux peut réduire considérablement la consommation mémoire :
javascript
import fs from 'fs';
// ❌ Non recommandé : charger tout le fichier en mémoire
const buffer = fs.readFileSync('large-image.jpg');
await sharp(buffer).resize(800, 600).toFile('output.jpg');
// ✅ Recommandé : utiliser le traitement en flux
fs.createReadStream('large-image.jpg')
.pipe(sharp().resize(800, 600).jpeg())
.pipe(fs.createWriteStream('output.jpg'));Traitement par blocs
Pour les très gros fichiers, vous pouvez traiter par blocs :
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');Libérer la mémoire en temps opportun
javascript
// Nettoyer en temps opportun après le traitement
async function processImage(inputPath, outputPath) {
const sharpInstance = sharp(inputPath);
try {
await sharpInstance
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile(outputPath);
} finally {
// Nettoyage manuel (bien que Node.js fasse automatiquement le garbage collection)
sharpInstance.destroy();
}
}Optimisation de concurrence
Contrôler le nombre de concurrences
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);Utiliser les threads Worker
Pour les tâches intensives en CPU, vous pouvez utiliser les threads Worker :
javascript
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
if (isMainThread) {
// Thread principal
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 {
// Thread Worker
const { files } = workerData;
for (const file of files) {
await sharp(file)
.resize(300, 200)
.jpeg({ quality: 80 })
.toFile(`processed-${file}`);
}
parentPort.postMessage('done');
}Optimisation du cache
Mettre en cache les résultats de traitement
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('Utiliser le résultat en 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 });Optimisation d'algorithmes
Choisir l'algorithme de redimensionnement approprié
javascript
// Pour les photos, utiliser le noyau lanczos3
await sharp('photo.jpg')
.resize(800, 600, { kernel: sharp.kernel.lanczos3 })
.toFile('photo-resized.jpg');
// Pour les icônes ou les images de ligne, utiliser le noyau nearest
await sharp('icon.png')
.resize(32, 32, { kernel: sharp.kernel.nearest })
.toFile('icon-resized.png');
// Pour les images nécessitant un traitement rapide, utiliser le noyau cubic
await sharp('image.jpg')
.resize(300, 200, { kernel: sharp.kernel.cubic })
.toFile('image-resized.jpg');Optimiser la qualité JPEG
javascript
// Ajuster la qualité selon le contenu de l'image
async function optimizeJPEGQuality(inputPath, outputPath) {
const metadata = await sharp(inputPath).metadata();
// Ajuster la qualité selon les dimensions de l'image
let quality = 80;
if (metadata.width > 1920 || metadata.height > 1080) {
quality = 85; // Utiliser une qualité plus élevée pour les grandes images
} else if (metadata.width < 800 && metadata.height < 600) {
quality = 75; // Utiliser une qualité plus faible pour les petites images
}
await sharp(inputPath)
.jpeg({
quality,
progressive: true, // JPEG progressif
mozjpeg: true // Utiliser l'optimisation mozjpeg
})
.toFile(outputPath);
}Optimisation réseau
Pré-générer différentes tailles
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');Utiliser des formats modernes
javascript
// Générer plusieurs formats pour supporter différents navigateurs
async function generateModernFormats(inputPath) {
const promises = [
// JPEG comme solution de secours
sharp(inputPath)
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile('output.jpg'),
// WebP pour les navigateurs modernes
sharp(inputPath)
.resize(800, 600)
.webp({ quality: 80 })
.toFile('output.webp'),
// AVIF pour les navigateurs les plus récents
sharp(inputPath)
.resize(800, 600)
.avif({ quality: 80 })
.toFile('output.avif')
];
await Promise.all(promises);
}Surveillance et débogage
Surveillance des performances
javascript
import { performance } from 'perf_hooks';
async function measurePerformance(fn) {
const start = performance.now();
const result = await fn();
const end = performance.now();
console.log(`Temps d'exécution: ${end - start}ms`);
return result;
}
await measurePerformance(async () => {
await sharp('input.jpg')
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile('output.jpg');
});Surveillance de l'utilisation mémoire
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('Utilisation mémoire avant traitement:', getMemoryUsage());
await sharp('input.jpg')
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile('output.jpg');
console.log('Utilisation mémoire après traitement:', getMemoryUsage());Résumé des meilleures pratiques
- Utiliser le traitement en flux pour les gros fichiers
- Contrôler le nombre de concurrences
- Mettre en cache les résultats de traitement
- Choisir l'algorithme de redimensionnement approprié
- Optimiser le format et la qualité de sortie
- Pré-générer les tailles couramment utilisées
- Surveiller les indicateurs de performance
Comparaison des performances
| Opération | Sharp | ImageMagick | GraphicsMagick |
|---|---|---|---|
| Redimensionnement | 100ms | 450ms | 420ms |
| Conversion de format | 80ms | 380ms | 360ms |
| Application de filtre | 120ms | 520ms | 480ms |
| Consommation mémoire | Faible | Élevée | Élevée |
Prochaines étapes
- Consulter la Documentation API pour connaître toutes les méthodes disponibles
- Apprendre les exemples pour plus d'utilisations
- Consulter le Journal des modifications pour connaître les dernières fonctionnalités