Skip to content

تحسين الأداء

Sharp هي بالفعل مكتبة معالجة صور عالية الأداء، ولكن من خلال بعض تقنيات التحسين، يمكنك تحسين الأداء بشكل أكبر.

اختبار الأداء

مزايا أداء Sharp مقارنة بمكتبات معالجة الصور الأخرى:

  • أسرع بـ 4-5 مرات من ImageMagick
  • أسرع بـ 4-5 مرات من GraphicsMagick
  • استهلاك ذاكرة أقل
  • يدعم المعالجة المتدفقة

تحسين الذاكرة

استخدام المعالجة المتدفقة

للملفات الكبيرة، يمكن للمعالجة المتدفقة أن تقلل بشكل كبير من استهلاك الذاكرة:

javascript
import fs from 'fs';

// ❌ غير موصى به: تحميل الملف بالكامل في الذاكرة
const buffer = fs.readFileSync('large-image.jpg');
await sharp(buffer).resize(800, 600).toFile('output.jpg');

// ✅ موصى به: استخدام المعالجة المتدفقة
fs.createReadStream('large-image.jpg')
  .pipe(sharp().resize(800, 600).jpeg())
  .pipe(fs.createWriteStream('output.jpg'));

المعالجة المجزأة

للملفات الكبيرة جدًا، يمكن المعالجة المجزأة:

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

تحرير الذاكرة في الوقت المناسب

javascript
// التنظيف في الوقت المناسب بعد اكتمال المعالجة
async function processImage(inputPath, outputPath) {
  const sharpInstance = sharp(inputPath);
  
  try {
    await sharpInstance
      .resize(800, 600)
      .jpeg({ quality: 80 })
      .toFile(outputPath);
  } finally {
    // التنظيف اليدوي (على الرغم من أن Node.js سيقوم بجمع المهملات تلقائيًا)
    sharpInstance.destroy();
  }
}

تحسين التزامن

التحكم في عدد التزامن

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

استخدام خيوط Worker

للمهام المكثفة في وحدة المعالجة المركزية، يمكن استخدام خيوط Worker:

javascript
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';

if (isMainThread) {
  // الخيط الرئيسي
  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
  const { files } = workerData;
  
  for (const file of files) {
    await sharp(file)
      .resize(300, 200)
      .jpeg({ quality: 80 })
      .toFile(`processed-${file}`);
  }
  
  parentPort.postMessage('done');
}

تحسين التخزين المؤقت

تخزين نتائج المعالجة مؤقتًا

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('استخدام نتيجة التخزين المؤقت');
      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 });

تحسين الخوارزمية

اختيار خوارزمية التغيير المناسبة

javascript
// للصور الفوتوغرافية، استخدام نواة lanczos3
await sharp('photo.jpg')
  .resize(800, 600, { kernel: sharp.kernel.lanczos3 })
  .toFile('photo-resized.jpg');

// للأيقونات أو الرسوم الخطية، استخدام نواة nearest
await sharp('icon.png')
  .resize(32, 32, { kernel: sharp.kernel.nearest })
  .toFile('icon-resized.png');

// للصور التي تحتاج معالجة سريعة، استخدام نواة cubic
await sharp('image.jpg')
  .resize(300, 200, { kernel: sharp.kernel.cubic })
  .toFile('image-resized.jpg');

تحسين جودة JPEG

javascript
// تعديل الجودة بناءً على محتوى الصورة
async function optimizeJPEGQuality(inputPath, outputPath) {
  const metadata = await sharp(inputPath).metadata();
  
  // تعديل الجودة بناءً على حجم الصورة
  let quality = 80;
  if (metadata.width > 1920 || metadata.height > 1080) {
    quality = 85; // الصور الكبيرة تستخدم جودة أعلى
  } else if (metadata.width < 800 && metadata.height < 600) {
    quality = 75; // الصور الصغيرة يمكنها استخدام جودة أقل
  }
  
  await sharp(inputPath)
    .jpeg({ 
      quality,
      progressive: true, // JPEG تدريجي
      mozjpeg: true      // استخدام تحسين mozjpeg
    })
    .toFile(outputPath);
}

تحسين الشبكة

إنشاء أحجام مختلفة مسبقًا

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

استخدام التنسيقات الحديثة

javascript
// إنشاء تنسيقات متعددة لدعم متصفحات مختلفة
async function generateModernFormats(inputPath) {
  const promises = [
    // JPEG كنسخة احتياطية
    sharp(inputPath)
      .resize(800, 600)
      .jpeg({ quality: 80 })
      .toFile('output.jpg'),
    
    // WebP للمتصفحات الحديثة
    sharp(inputPath)
      .resize(800, 600)
      .webp({ quality: 80 })
      .toFile('output.webp'),
    
    // AVIF لأحدث المتصفحات
    sharp(inputPath)
      .resize(800, 600)
      .avif({ quality: 80 })
      .toFile('output.avif')
  ];
  
  await Promise.all(promises);
}

المراقبة والتصحيح

مراقبة الأداء

javascript
import { performance } from 'perf_hooks';

async function measurePerformance(fn) {
  const start = performance.now();
  const result = await fn();
  const end = performance.now();
  
  console.log(`وقت التنفيذ: ${end - start}ms`);
  return result;
}

await measurePerformance(async () => {
  await sharp('input.jpg')
    .resize(800, 600)
    .jpeg({ quality: 80 })
    .toFile('output.jpg');
});

مراقبة استخدام الذاكرة

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('استخدام الذاكرة قبل المعالجة:', getMemoryUsage());

await sharp('input.jpg')
  .resize(800, 600)
  .jpeg({ quality: 80 })
  .toFile('output.jpg');

console.log('استخدام الذاكرة بعد المعالجة:', getMemoryUsage());

ملخص أفضل الممارسات

  1. استخدام المعالجة المتدفقة للملفات الكبيرة
  2. التحكم في عدد التزامن
  3. تخزين نتائج المعالجة مؤقتًا
  4. اختيار خوارزمية التغيير المناسبة
  5. تحسين تنسيق الإخراج والجودة
  6. إنشاء الأحجام الشائعة مسبقًا
  7. مراقبة مؤشرات الأداء

مقارنة الأداء

العمليةSharpImageMagickGraphicsMagick
تغيير الحجم100ms450ms420ms
تحويل التنسيق80ms380ms360ms
تطبيق الفلتر120ms520ms480ms
استهلاك الذاكرةمنخفضعاليعالي

الخطوات التالية

تم الإصدار بموجب رخصة Apache 2.0.