تجنب حذف الشيفرة غير المستخدمة
Blackhole والحالة
تجنب حذف الشيفرة غير المستخدمة درس مجاني في Java Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Java Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Java Academy 4 دروس في المجموع.
تجنّب إزالة الشيفرة الميتة
أكبر فخ في اختبارات الأداء الدقيق هو إزالة الشيفرة الميتة (DCE): فإذا أثبت JIT أن نتيجة ما لن تُستخدم، فإنه يحذف العملية الحسابية. وعندها لا يقيس الاختبار شيئًا. يوفّر JMH أداتين لمنع ذلك: إعادة القيم وBlackhole.
فخ إزالة الشيفرة الميتة
تحسب هذه الحلقة الجذر التربيعي مليون مرة، لكنها لا تستخدم النتيجة قط. ويمكن لمترجم ذكي حذف الحلقة بأكملها.
public class Main {
public static void main(String[] args) {
long t = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
double ignored = Math.sqrt(i); // result thrown away
}
System.out.println("ns: " + (System.nanoTime() - t));
}
}الإصلاح الأول: إعادة النتيجة
أسهل طريقة للدفاع هي إعادة القيمة المحسوبة من دالة @Benchmark. يستهلك JMH كل قيمة مُعادة، ولذلك لا تستطيع JVM إثبات أنها غير مستخدمة.
import org.openjdk.jmh.annotations.Benchmark;
public class Bench {
@Benchmark
public double sqrt() {
return Math.sqrt(42.0); // returned -> consumed by JMH
}
}الإصلاح الثاني: Blackhole
عندما ينتج الاختبار المعياري عدة قيم أو يتعذر عليكم إعادة قيمة، مرّروا كائنًا من نوع Blackhole واستدعوا consume(...). يقنع Blackhole JIT بأن كل قيمة مستخدمة، دون تكلفة تنفيذ عمل حقيقي.
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;
public class Bench {
@Benchmark
public void manyValues(Blackhole bh) {
for (int i = 0; i < 100; i++) {
bh.consume(Math.sqrt(i));
}
}
}كائنات @State
تحتفظ فئة @State بمدخلات غير مكشوفة أمام المُحسِّن، مما يمنع طيّ الثوابت. ينشئها JMH ويمرّرها إلى الاختبار المعياري. ويمكن أن يكون النطاق Thread أو Benchmark (مشتركًا) أو Group.
import org.openjdk.jmh.annotations.*;
public class Bench {
@State(Scope.Thread)
public static class Data {
public int x = 21;
}
@Benchmark
public int multiply(Data d) {
return d.x * 2; // d.x is not a compile-time constant
}
}@Setup و@TearDown
داخل فئة @State، تعمل الدوال التي تحمل التعليق التوضيحي @Setup قبل القياس، وتعمل الدوال التي تحمل @TearDown بعده. ويحدّد Level معدل التكرار: Trial (مرة واحدة) أو Iteration أو Invocation.
import org.openjdk.jmh.annotations.*;
@State(Scope.Thread)
public class Data {
int[] arr;
@Setup(Level.Trial)
public void init() {
arr = new int[1000];
for (int i = 0; i < arr.length; i++) arr[i] = i;
}
}تجنّب المدخلات الثابتة
لا تمرّر أبدًا ثوابت حرفية إلى الدالة قيد الاختبار. قد تُختزل Math.sqrt(2.0) إلى ثابت، بينما لا يمكن اختزال Math.sqrt(state.value). احرص دائمًا على أخذ المدخلات من حقل @State.
إرجاع قيم متعددة
إذا كان اختبار الأداء ينتج نتيجتين بطبيعته، فيمكنك إرجاع إحداهما واستهلاك الأخرى عبر Blackhole، أو دمجهما. إن تركت أيًّا منهما دون استهلاك، فأنت تعيد فتح ثغرة إزالة الشيفرة الميتة.
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;
public class Bench {
@Benchmark
public int twoResults(Blackhole bh) {
int a = 3 * 7;
int b = 5 + 9;
bh.consume(b);
return a;
}
}كلفة Blackhole
صُمّم Blackhole ليكون منخفض الكلفة للغاية ومقاومًا بدوره للتحسينات، لكنه ليس مجانيًا تمامًا. بالنسبة إلى العمليات فائقة السرعة، يُفضّل إرجاع قيمة واحدة؛ واحتفظ باستخدام Blackhole للحلقات والحالات التي تتضمن قيمًا متعددة.
تحقّق من منطقية أرقامك
إذا أبلغ اختبار الأداء عن زمن منخفض إلى حد غير معقول، مثل أقل من نانوثانية لعمل حقيقي، فاشتبِه في إزالة الشيفرة الميتة أو طيّ الثوابت. تحقّق مرة أخرى من إرجاع النتائج أو استهلاكها، ومن أن المدخلات مصدرها @State.
جمع العناصر معًا
يقرأ اختبار الأداء المتين مدخلاته من كائن @State، وينفّذ العمل، ثم إما return النتيجة أو يستهلكها عبر Blackhole.consume. باتباع هذه العادات الثلاث، لا يمكن لإزالة الشيفرة الميتة أو طيّ الثوابت إفساد قياساتك.
تحقّق سريعًا
اختبر مدى فهمك لكيفية منع إزالة الشيفرة الميتة.
مراجعة
تعلّمت كيف تحافظ على نزاهة اختبارات الأداء:
- تحذف إزالة الشيفرة الميتة العمليات الحسابية غير المستخدمة.
- أعِد return نتيجة واحدة لكي يستهلكها JMH.
- استخدم Blackhole.consume للحلقات والقيم المتعددة.
- تحافظ كائنات @State على غموض المدخلات، مما يمنع طيّ الثوابت.
- تجهّز @Setup / @TearDown الحالة وتنظفها عند مستوى Level تختاره.
الأسئلة الشائعة
هل درس «تجنب حذف الشيفرة غير المستخدمة» مجاني؟
نعم — نص درس «تجنب حذف الشيفرة غير المستخدمة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Java Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Java Academy 4 دروس في المجموع.
ماذا ستتعلم في «تجنب حذف الشيفرة غير المستخدمة»؟
Blackhole والحالة تتمرن على Java Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Java Academy؟
لا تُشترط خبرة سابقة. Java Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «تجنب حذف الشيفرة غير المستخدمة»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Java Academy هذا؟
نعم. كل درس في Java Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- لماذا JMH
- كتابة اختبار أداء
- الإحماء والتكرارات
- تجنب حذف الشيفرة غير المستخدمة