C++: تحسين الأداء
آخر تحديث: 2026-08-26
في الدرس 48، تعلمنا عن ميزات C++17/20 الجديدة.
الآن، سنتعلم عن تحسين الأداء — جعل برامج C++ تعمل بشكل أسرع.
ميزة C++ هي الأداء، لكن استخدامها بشكل جيد ليس سهلاً.
1. نظرة عامة على تحسين الأداء
(1) 1.1 لماذا نحسّن؟
المبادئ:
- لا تُحسّن مبكرًا (Premature Optimization)
- قِس أولاً، ثم حسّن
- حسّن الخوارزمية أولاً، ثم الكود
(2) 1.2 اختناقات الأداء
| الاختناق | النسبة |
|---|---|
| الخوارزمية | 70% |
| الوصول للذاكرة | 20% |
| أخرى | 10% |
الاستنتاج: تحسين الخوارزمية أهم من تحسين الكود.
2. محاذاة الذاكرة
(1) 2.1 ما هي محاذاة الذاكرة؟
محاذاة الذاكرة تعني تخزين البيانات في عناوين ذاكرة هي مضاعفات لقيمة معينة (عادة قوة 2).
لماذا هي مهمة؟
- الوصول للذاكرة غير المحاذاة أبطأ (أو قد يتحطم)
- وحدة المعالجة المركزية تقرأ الذاكرة المحاذاة أسرع
(2) 2.2 مثال: تأثير محاذاة الذاكرة (الصعوبة ⭐⭐)
▶ مثال 1: عرض البرمجة كائنية التوجه (الصعوبة ⭐)
#include <iostream>
struct BadAlignment {
char c; // 1 بايت
int i; // 4 بايت (قد تُمحى إلى الإزاحة 4)
};
struct GoodAlignment {
int i; // 4 بايت
char c; // 1 بايت
};
int main() {
std::cout << "Bad: " << sizeof(BadAlignment) << " bytes" << std::endl;
std::cout << "Good: " << sizeof(GoodAlignment) << " bytes" << std::endl;
return 0;
}
الخرج:
Bad: 8 bytes
Good: 8 bytes
نتيجة تشغيل محتملة:
Bad: 8 bytes
Good: 8 bytes
💡 نصيحة:
- وضع الأعضاء الأكبر أولاً يمكن أن يقلل من الحشو
3. وداقة التخزين المؤقت
(1) 3.1 تخزين وحدة المعالجة المركزية المؤقت
تخزين وحدة المعالجة المركزية المؤقت أسرع بـ 100 مرة من الذاكرة الرئيسية.
مبادئ التحسين:
- مبدأ الموضعية: الوصول إلى ذاكرة متجاورة
- الوصول المتسلسل: أسرع من الوصول العشوائي
- تجنب أخطاء التخزين المؤقت: تقليل الوصول للذاكرة غير المتجاورة
(2) 3.2 مثال: الترتيب حسب الصف مقابل الترتيب حسب العمود (الصعوبة ⭐⭐⭐)
#include <iostream>
### ▶ مثال 2: استخدام حاويات STL (الصعوبة ⭐)
#include <vector>
#include <chrono>
int main() {
const int N = 1000;
std::vector<std::vector<int>> matrix(N, std::vector<int>(N));
// الترتيب حسب الصف (وديق للتخزين المؤقت)
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
matrix[i][j] = 1;
}
}
auto end = std::chrono::high_resolution_clock::now();
auto row_time = std::chrono::duration<double>(end - start).count();
// الترتيب حسب العمود (غير وديق للتخزين المؤقت)
start = std::chrono::high_resolution_clock::now();
for (int j = 0; j < N; j++) {
for (int i = 0; i < N; i++) {
matrix[i][j] = 1;
}
}
end = std::chrono::high_resolution_clock::now();
auto col_time = std::chrono::duration<double>(end - start).count();
std::cout << "Row-major time: " << row_time << " seconds" << std::endl;
std::cout << "Column-major time: " << col_time << " seconds" << std::endl;
return 0;
}
الخرج:
Row-major time: 0.001 seconds
Column-major time: 0.003 seconds
4. تحسين المترجم
(1) 4.1 مستويات التحسين
| المستوى | العلامة | الوصف |
|---|---|---|
| O0 | لا شيء | بدون تحسين (للتصحيح) |
| O1 | -O1 |
تحسين أساسي |
| O2 | -O2 |
موصى به (متوازن) |
| O3 | -O3 |
تحسين عدواني |
| Os | -Os |
تحسين للحجم |
(2) 4.2 مثال: تأثير تحسين المترجم (الصعوبة ⭐)
#include <iostream>
int main() {
int sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i;
}
std::cout << sum << std::endl;
return 0;
}
الخرج:
499999500000
التجميع:
g++ -O0 main.cpp # بطيء
g++ -O2 main.cpp # سريع (المترجم قد يحسب النتيجة مباشرة)
5. أدوات تحليل الأداء
(1) 5.1 الأدوات الشائعة
| الأداة | المنصة | الوصف |
|---|---|---|
| gprof | Linux | محلل أداء GCC |
| perf | Linux | أداة تحليل أداء Linux |
| Valgrind | متعدد المنصات | تحليل الذاكرة |
| Visual Studio Profiler | ويندوز | مدمج في VS |
(2) 5.2 مثال: قياس الوقت بـ chrono (الصعوبة ⭐)
#include <iostream>
#include <chrono>
int main() {
auto start = std::chrono::high_resolution_clock::now();
// الكود المراد قياسه
long sum = 0;
for (int i = 0; i < 100000000; i++) {
sum += i;
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration<double>(end - start).count();
std::cout << "Time elapsed: " << duration << " seconds" << std::endl;
return 0;
}
الخرج:
Time elapsed: 0.05 seconds
6. ملخص تقنيات التحسين
(1) 6.1 على مستوى الكود
| التقنية | الوصف |
|---|---|
| استخدم دلالات النقل | تقليل النسخ |
| استخدم emplace_back | تجنب الكائنات المؤقتة |
| استخدم reserve | تقليل إعادة تخصيص vector |
| استخدم unordered_map | جدول تجزئة، بحث O(1) |
(2) 6.2 على مستوى الخوارزمية
| التقنية | الوصف |
|---|---|
| اختر بنية البيانات المناسبة | vector مقابل list مقابل map |
| استخدم خوارزميات مناسبة | sort مقابل partial_sort |
| تجنب النسخ غير الضروري | استخدم المراجع، النقل |
▶ مثال 3: مقارنة أداء التمرير بالقيمة مقابل التمرير بالمرجع (الصعوبة ⭐)
#include <iostream>
#include <vector>
#include <chrono>
struct BigData {
std::vector<int> data;
BigData() : data(10000, 0) {}
};
// التمرير بالقيمة (نسخ)
void processByValue(BigData d) {
(void)d;
}
// التمرير بالمرجع (بدون نسخ)
void processByRef(const BigData& d) {
(void)d;
}
int main() {
BigData big;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; i++) {
processByValue(big);
}
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Pass by value time: " << std::chrono::duration<double>(end - start).count() << " seconds" << std::endl;
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 10000; i++) {
processByRef(big);
}
end = std::chrono::high_resolution_clock::now();
std::cout << "Pass by reference time: " << std::chrono::duration<double>(end - start).count() << " seconds" << std::endl;
return 0;
}
الخرج:
Pass by value time: 0.15 seconds
Pass by reference time: 0.001 seconds
❓ أسئلة شائعة
📖 ملخص
| النقطة الرئيسية | الملخص |
|---|---|
| محاذاة الذاكرة | تقليل الحشو |
| وداقة التخزين المؤقت | وصول متسلسل، موضعية |
| تحسين المترجم | -O2 موصى به |
| تحليل الأداء | قِس أولاً، ثم حسّن |
| تقنيات التحسين | دلالات النقل، emplace_back |
📝 تمارين
-
أساسي (الصعوبة ⭐): خزّن 100,000 عدد صحيح باستخدام كل من
vectorوlist، وقارن فارق الوقت للإدراج في النهاية والوصول العشوائي. -
متوسط (الصعوبة ⭐⭐): اختبر فرق الأداء بين التمرير بالقيمة والتمرير بالمرجع. اكتب دالة تعالج بنية كبيرة (تحتوي
vector<int>(10000))، مع قياس الوقت لكل من التمرير بالقيمة والتمرير بالمرجع الثابت. -
متقدم (الصعوبة ⭐⭐⭐): استخدم توقيت عالي الدقة
std::chronoلمقارنة فروق الأداء بين حلقةfor، وfor_eachمن STL، وforالمدعوم بنطاق عند الاجتياز. حلل النتائج.
- علامات تحسين المترجم: -O0/-O1/-O2/-O3/-Os
- تقليل النسخ: التمرير بالمرجع، دلالات النقل
- اختيار الحاويات: vector لها ذاكرة متجاورة، وديقة للتخزين المؤقت
- الدوال المضمنة تقلل عبء استدعاء الدوال
- التحسين الموجه بالملف الشخصي: قِس الاختناقات أولاً، ثم حسّن
الدرس التالي: اختبار الوحدات (#50)