Skip to content

Ví dụ tối ưu hiệu suất

Đây là một số ví dụ và thực hành tốt nhất về tối ưu hiệu suất Sharp.

Tối ưu bộ nhớ

Xử lý luồng tệp lớn

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

// Xử lý luồng, tránh tải toàn bộ tệp vào bộ nhớ
fs.createReadStream('large-image.jpg')
  .pipe(sharp().resize(800, 600))
  .pipe(fs.createWriteStream('output.jpg'));

Sử dụng Buffer thay vì tệp

javascript
// Đối với tệp nhỏ, sử dụng Buffer hiệu quả hơn
const inputBuffer = fs.readFileSync('input.jpg');
const outputBuffer = await sharp(inputBuffer)
  .resize(300, 200)
  .jpeg({ quality: 80 })
  .toBuffer();

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

Giải phóng tài nguyên kịp thời

javascript
// Giải phóng kịp thời sau khi xử lý xong
const image = sharp('input.jpg');
await image.resize(300, 200).toFile('output.jpg');
// Instance image sẽ được tự động thu gom rác

Điều khiển đồng thời

Giới hạn số lượng đồng thời

javascript
// Thiết lập số đồng thời tối đa
sharp.concurrency(4);

// Điều khiển đồng thời khi xử lý hàng loạt
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;
}

Sử dụng hàng đợi xử lý

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();
    }
  }
}

// Ví dụ sử dụng
const processor = new ImageProcessor(4);

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

Tối ưu bộ nhớ đệm

Bộ nhớ đệm kết quả xử lý

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;
}

Xóa bộ nhớ đệm Sharp

javascript
// Xóa bộ nhớ đệm định kỳ để giải phóng bộ nhớ
setInterval(() => {
  sharp.cache(false);
}, 60000); // Xóa mỗi phút một lần

Lựa chọn thuật toán

Chọn thuật toán điều chỉnh phù hợp

javascript
// Đối với thu nhỏ, sử dụng thuật toán nhanh hơn
await sharp('input.jpg')
  .resize(300, 200, { kernel: sharp.kernel.cubic })
  .toFile('output.jpg');

// Đối với phóng to, sử dụng thuật toán chất lượng cao hơn
await sharp('input.jpg')
  .resize(1200, 800, { kernel: sharp.kernel.lanczos3 })
  .toFile('output.jpg');

Tối ưu xử lý hàng loạt

javascript
async function optimizedBatchProcess(files) {
  // Nhóm theo kích thước để xử lý
  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);
    }
  }
  
  // Tệp nhỏ sử dụng thuật toán nhanh
  await Promise.all(smallFiles.map(file =>
    sharp(file)
      .resize(300, 200, { kernel: sharp.kernel.cubic })
      .jpeg({ quality: 80 })
      .toFile(`output_${file}`)
  ));
  
  // Tệp lớn sử dụng thuật toán chất lượng cao
  await Promise.all(largeFiles.map(file =>
    sharp(file)
      .resize(800, 600, { kernel: sharp.kernel.lanczos3 })
      .jpeg({ quality: 90 })
      .toFile(`output_${file}`)
  ));
}

Tối ưu mạng

Phản hồi luồng

javascript
// Ví dụ 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');
  }
});

Xử lý có điều kiện

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');
  }
});

Giám sát và gỡ lỗi

Giám sát hiệu suất

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(`Thời gian xử lý: ${endTime - startTime}ms`);
  } catch (error) {
    console.error('Xử lý thất bại:', error.message);
  }
}

Giám sát sử dụng bộ nhớ

javascript
const process = require('process');

function logMemoryUsage() {
  const usage = process.memoryUsage();
  console.log('Sử dụng bộ nhớ:', {
    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`
  });
}

// Ghi lại sử dụng bộ nhớ trước và sau khi xử lý
logMemoryUsage();
await sharp('input.jpg').resize(800, 600).toFile('output.jpg');
logMemoryUsage();

Xử lý lỗi và thử lại

Cơ chế thử lại

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(`Xử lý thành công, số lần thử: ${attempt}`);
      return;
    } catch (error) {
      console.error(`Thử ${attempt} thất bại:`, error.message);
      
      if (attempt === maxRetries) {
        throw new Error(`Xử lý thất bại, đã thử lại ${maxRetries} lần`);
      }
      
      // Chờ một khoảng thời gian trước khi thử lại
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
    }
  }
}

Xử lý lỗi phân loại

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('Định dạng hình ảnh không được hỗ trợ');
    } else if (error.code === 'VipsForeignLoadLimit') {
      console.error('Hình ảnh quá lớn, thử thu nhỏ');
      // Thử xử lý phiên bản nhỏ hơn
      await sharp(inputPath, { limitInputPixels: 268402689 })
        .resize(400, 300)
        .jpeg({ quality: 80 })
        .toFile(outputPath);
    } else if (error.code === 'ENOSPC') {
      console.error('Không đủ dung lượng đĩa');
    } else {
      console.error('Lỗi không xác định:', error.message);
    }
  }
}

Tóm tắt thực hành tốt nhất

1. Chọn phương thức xử lý phù hợp

javascript
// Tệp nhỏ: Xử lý trực tiếp
if (fileSize < 1024 * 1024) {
  await sharp(file).resize(300, 200).toFile(output);
}

// Tệp lớn: Xử lý luồng
else {
  fs.createReadStream(file)
    .pipe(sharp().resize(300, 200))
    .pipe(fs.createWriteStream(output));
}

2. Tối ưu xử lý hàng loạt

javascript
// Sử dụng Promise.all để xử lý đồng thời
const promises = files.map(file => 
  sharp(file).resize(300, 200).jpeg().toFile(`output_${file}`)
);
await Promise.all(promises);

3. Quản lý bộ nhớ

javascript
// Dọn dẹp bộ nhớ đệm định kỳ
setInterval(() => {
  sharp.cache(false);
}, 300000); // Dọn dẹp mỗi 5 phút một lần

4. Xử lý lỗi

javascript
// Luôn sử dụng try-catch
try {
  await sharp(input).resize(300, 200).toFile(output);
} catch (error) {
  console.error('Xử lý thất bại:', error.message);
  // Cung cấp giải pháp dự phòng
}

Liên kết liên quan

Được phát hành theo Giấy phép Apache 2.0.