Tối ưu hiệu suất
Sharp đã là một thư viện xử lý hình ảnh hiệu suất cao, nhưng thông qua một số kỹ thuật tối ưu, bạn có thể cải thiện hiệu suất hơn nữa.
Kiểm tra điểm chuẩn
Lợi thế hiệu suất của Sharp so với các thư viện xử lý hình ảnh khác:
- Nhanh hơn ImageMagick 4-5 lần
- Nhanh hơn GraphicsMagick 4-5 lần
- Chiếm dụng bộ nhớ thấp hơn
- Hỗ trợ xử lý luồng
Tối ưu bộ nhớ
Sử dụng xử lý luồng
Đối với tệp lớn, sử dụng xử lý luồng có thể giảm đáng kể chiếm dụng bộ nhớ:
javascript
import fs from 'fs';
// ❌ Không khuyến nghị: Tải toàn bộ tệp vào bộ nhớ
const buffer = fs.readFileSync('large-image.jpg');
await sharp(buffer).resize(800, 600).toFile('output.jpg');
// ✅ Khuyến nghị: Sử dụng xử lý luồng
fs.createReadStream('large-image.jpg')
.pipe(sharp().resize(800, 600).jpeg())
.pipe(fs.createWriteStream('output.jpg'));Xử lý theo khối
Đối với tệp siêu lớn, có thể xử lý theo khối:
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');Giải phóng bộ nhớ kịp thời
javascript
// Dọn dẹp kịp thời sau khi xử lý xong
async function processImage(inputPath, outputPath) {
const sharpInstance = sharp(inputPath);
try {
await sharpInstance
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile(outputPath);
} finally {
// Dọn dẹp thủ công (mặc dù Node.js sẽ tự động thu gom rác)
sharpInstance.destroy();
}
}Tối ưu đồng thời
Điều khiển số lượng đồng thời
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);Sử dụng Worker thread
Đối với tác vụ CPU-intensive, có thể sử dụng Worker thread:
javascript
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';
if (isMainThread) {
// Luồng chính
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');
}Tối ưu bộ nhớ đệm
Bộ nhớ đệm kết quả xử lý
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('Sử dụng kết quả bộ nhớ đệm');
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 });Tối ưu thuật toán
Chọn thuật toán điều chỉnh phù hợp
javascript
// Đối với ảnh, sử dụng kernel lanczos3
await sharp('photo.jpg')
.resize(800, 600, { kernel: sharp.kernel.lanczos3 })
.toFile('photo-resized.jpg');
// Đối với biểu tượng hoặc hình vẽ đường, sử dụng kernel nearest
await sharp('icon.png')
.resize(32, 32, { kernel: sharp.kernel.nearest })
.toFile('icon-resized.png');
// Đối với hình ảnh cần xử lý nhanh, sử dụng kernel cubic
await sharp('image.jpg')
.resize(300, 200, { kernel: sharp.kernel.cubic })
.toFile('image-resized.jpg');Tối ưu chất lượng JPEG
javascript
// Điều chỉnh chất lượng theo nội dung hình ảnh
async function optimizeJPEGQuality(inputPath, outputPath) {
const metadata = await sharp(inputPath).metadata();
// Điều chỉnh chất lượng theo kích thước hình ảnh
let quality = 80;
if (metadata.width > 1920 || metadata.height > 1080) {
quality = 85; // Hình ảnh lớn sử dụng chất lượng cao hơn
} else if (metadata.width < 800 && metadata.height < 600) {
quality = 75; // Hình ảnh nhỏ có thể sử dụng chất lượng thấp hơn
}
await sharp(inputPath)
.jpeg({
quality,
progressive: true, // JPEG lũy tiến
mozjpeg: true // Sử dụng tối ưu mozjpeg
})
.toFile(outputPath);
}Tối ưu mạng
Tạo trước các kích thước khác nhau
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');Sử dụng định dạng hiện đại
javascript
// Tạo nhiều định dạng để hỗ trợ các trình duyệt khác nhau
async function generateModernFormats(inputPath) {
const promises = [
// JPEG làm dự phòng
sharp(inputPath)
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile('output.jpg'),
// WebP cho trình duyệt hiện đại
sharp(inputPath)
.resize(800, 600)
.webp({ quality: 80 })
.toFile('output.webp'),
// AVIF cho trình duyệt mới nhất
sharp(inputPath)
.resize(800, 600)
.avif({ quality: 80 })
.toFile('output.avif')
];
await Promise.all(promises);
}Giám sát và gỡ lỗi
Giám sát hiệu suất
javascript
import { performance } from 'perf_hooks';
async function measurePerformance(fn) {
const start = performance.now();
const result = await fn();
const end = performance.now();
console.log(`Thời gian thực thi: ${end - start}ms`);
return result;
}
await measurePerformance(async () => {
await sharp('input.jpg')
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile('output.jpg');
});Giám sát sử dụng bộ nhớ
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('Sử dụng bộ nhớ trước khi xử lý:', getMemoryUsage());
await sharp('input.jpg')
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile('output.jpg');
console.log('Sử dụng bộ nhớ sau khi xử lý:', getMemoryUsage());Tóm tắt thực hành tốt nhất
- Sử dụng xử lý luồng cho tệp lớn
- Điều khiển số lượng đồng thời
- Bộ nhớ đệm kết quả xử lý
- Chọn thuật toán điều chỉnh phù hợp
- Tối ưu định dạng và chất lượng đầu ra
- Tạo trước các kích thước thường dùng
- Giám sát các chỉ số hiệu suất
So sánh hiệu suất
| Thao tác | Sharp | ImageMagick | GraphicsMagick |
|---|---|---|---|
| Thay đổi kích thước | 100ms | 450ms | 420ms |
| Chuyển đổi định dạng | 80ms | 380ms | 360ms |
| Áp dụng bộ lọc | 120ms | 520ms | 480ms |
| Chiếm dụng bộ nhớ | Thấp | Cao | Cao |
Bước tiếp theo
- Xem Tài liệu API để tìm hiểu tất cả các phương thức có sẵn
- Học Ví dụ để tìm hiểu thêm cách sử dụng
- Xem Nhật ký cập nhật để tìm hiểu các tính năng mới nhất