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
- Usare elaborazione streaming per file grandi
- Controllare numero concorrenza
- Cachare risultati elaborazione
- Scegliere algoritmo ridimensionamento appropriato
- Ottimizzare formato output e qualità
- Pre-generare dimensioni comuni
- Monitorare metriche prestazioni
Confronto Prestazioni
| Operazione | Sharp | ImageMagick | GraphicsMagick |
|---|---|---|---|
| Ridimensionamento | 100ms | 450ms | 420ms |
| Conversione formato | 80ms | 380ms | 360ms |
| Applicazione filtro | 120ms | 520ms | 480ms |
| Utilizzo memoria | Basso | Alto | Alto |
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à