اختبار أداء كود لينكس مع أمثلة

عندما بدأت تعلم Java ، كانت إحدى المهام الأولى التي حاولت حلها هي تحديد الأرقام الزوجية / الفردية. كنت أعرف عدة طرق للقيام بذلك ، لكنني قررت البحث عن الطريقة "الصحيحة" على الإنترنت. أخبرتني المعلومات الموجودة على جميع الروابط الموجودة عن الحل الصحيح الوحيد للنموذج x٪ 2 ، من أجل الحصول على ما تبقى من القسم. إذا كان الباقي يساوي 0 ، فسيكون الرقم زوجيًا ؛ وإذا كان الباقي يساوي 1 ، فمن الغريب.

منذ وقت ZX Spectrum ، تذكرت طريقة أخرى وهي مرتبطة بتمثيل الأرقام في النظام الثنائي. يمكن كتابة أي رقم في النظام العشري على أنه مجموع سلطات اثنين. على سبيل المثال ، لبايت واحد ، وهذا هو 8 بت ، يمكن تمثيل أي رقم في النظام العشري كمجموع الأرقام 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1.

هذا مجرد سلسلة من السلطات من اثنين. عند ترجمة رقم إلى النظام الثنائي ، إذا احتجنا إلى أخذ الرقم في الاعتبار ، فسيكون الرقم في التمثيل الثنائي ، إذا لم يكن ضروريًا ، سيكون 0.

على سبيل المثال:

10 = 1010 (8 + 0 + 2 + 0)
13 = 1101 (8 + 4 + 0 + 1)
200 = 11001000 (128 + 64 + 0 + 0 + 8 + 0 + 0 + 0)

يمكنك على الفور الانتباه إلى حقيقة أن الرقم الفردي لا يمكن أن يعطي سوى قوة صفرية لاثنين بقيمة 1 ، وجميع القوى الأخرى لاثنين حتى من خلال التعريف. هذا يعني تلقائيًا أنه من وجهة نظر نظام الأرقام الثنائية ، إذا أردنا التحقق من أي رقم من أجل التكافؤ ، فلن نحتاج إلى التحقق من الرقم الصحيح ، بغض النظر عن حجمه. نحتاج إلى التحقق من البت الأول فقط (أقصى اليمين). إذا كان 0 ، فإن الرقم يكون زوجيًا ، نظرًا لأن جميع البتات الأخرى تعطي رقمًا زوجيًا ، والعكس صحيح ، إذا كان الحد الأقصى لليمين واحدًا ، فإن الرقم مضمون ليكون فرديًا ، لأن جميع البتات الأخرى تعطي قيمة زوجية فقط.
للتحقق من البت الصحيح فقط في الرقم ، يمكنك استخدام عدة طرق. واحد منهم ثنائي و.

و


يعمل ثنائي AND (AND) بالقاعدة التالية. إذا تقدمت إلى أي رقم ، فلنطلق عليه الرقم الأصلي والمنطقي AND بالرقم 0 ، فإن نتيجة هذه العملية تكون دائمًا 0. وبهذه الطريقة يمكنك استبعاد البتات التي لا تحتاج إليها. إذا تقدمت إلى الأصل 1 ، فستحصل على الأصل.

في نظام ثنائي ، من السهل كتابة هذا:

0 و 0 = 0 (صفر الأصلي)
1 و 0 = 0 (صفر الأصلي)
0 و 1 = 0 (لا تغير الأصلي)
1 و 1 = 1 (لا تغير الأصلي)

من هنا بعض بسيطة قواعد.

إذا طبقنا عملية AND لجميع الوحدات على جميع الأرقام (جميع البتات قيد التشغيل) ، نحصل على نفس الرقم الأولي.

إذا قمنا بتطبيق AND من جميع الأصفار على أي رقم (جميع البتات متوقفة) ، نحصل على 0.

على سبيل المثال:

إذا قمنا بتطبيق AND 0 على البايت 13 ، فسوف نحصل على 0. في العلامة العشرية يبدو 13 و 0 = 0

إذا طبقنا و 0 على البايت 200 ، فسوف نحصل على 0 ، أو نكتب 200 و 0 = 0 لفترة وجيزة.
نفس الشيء هو العكس ، تنطبق على 13 كل البتات المضمنة ، للبايت سيكون ثماني وحدات ، ونحصل على الأصل. في النظام الثنائي 00001101 و 11111111 = 00001101 أو في النظام العشري 13 و 255 = 13

بالنسبة لـ 200 سيكون هناك 11001000 و 11111111 = 11001000 ، على التوالي ، أو في النظام العشري 200 و 255 = 200

التحقق الثنائي


للتحقق من الرقم من أجل التكافؤ ، نحتاج فقط إلى التحقق من أقصى اليمين. إذا كان الرقم 0 ، فإن الرقم يكون زوجي ؛ إذا كان 1 ، فهو ليس كذلك. مع العلم أنه مع AND يمكننا ترك بعض البتات أصلية ، وبعضها يمكننا إعادة التعيين ، يمكننا فقط إعادة تعيين جميع البتات باستثناء واحدة في أقصى اليمين. على سبيل المثال:

13 في النظام الثنائي هو 1101. دعنا نطبق AND 0001 عليه (نعيد تعيين جميع البتات ، والأخيرة تبقى الأصلية). في عام 1101 ، قمنا بتغيير جميع البتات إلى 0 باستثناء الأخيرة ونحصل على 0001. حصلنا على البت الأخير فقط من رقمنا الأصلي. في النظام العشري ، سيبدو 13 و 1 = 1.

نفس الشيء مع الرقم 200 ، في شكل ثنائي 11001000. نقوم بتطبيقه و 00000001 عليه ، وفقًا للمخطط نفسه ، صفر جميع البتات ، وترك آخر واحد كما هو ، نحصل على 00000000 ، ونعيد تعيين الأصفار السبعة اليسرى مع AND ، وحصلنا على آخر 0 من الرقم الأصلي. في النظام العشري ، يبدو 200 و 1 = 0

وهكذا ، بتطبيق الأمر AND 1 على أي رقم ، نحصل على 0 أو 1. وإذا كانت النتيجة 0 ، فإن الرقم يكون زوجيًا. عندما يكون الرقم 1 ، يكون الرقم فرديًا.

في Java ، يتم كتابة الثنائي AND كـ &. وفقًا لذلك ، 200 & 1 = 0 (زوجي) و 13 & 1 = 1 (فردي).

يتضمن هذا طريقتين على الأقل لتحديد الأرقام الزوجية.

X٪ 2 - خلال باقي القسمة على
X & 1 - من خلال ثنائي و

تتم معالجة العمليات الثنائية مثل OR و AND و XOR بواسطة المعالج في الحد الأدنى من الوقت. لكن عملية التقسيم هي مهمة غير تافهة ، ومن أجل تنفيذها ، يحتاج المعالج إلى معالجة الكثير من الإرشادات ، وتنفيذ البرنامج بالكامل بشكل أساسي. ومع ذلك ، هناك عمليات ثنائية لليسار واليمين تسمح ، على سبيل المثال ، بتقسيم الرقم بسرعة على 2. والسؤال هو ما إذا كان المترجمون يستخدمون هذا التحسين وما إذا كان هناك فرق بين هاتين المقارنتين ، والذي يفعل نفس الشيء في الواقع.

الترميز


سنكتب برنامجًا يعالج 9،000،000،000 رقم في دورة بالترتيب ، ويحدد انتمائهم إلى زوجي / فردي عن طريق تحديد ما تبقى من القسم.

public class OddEvenViaMod {
        public static void main (String[] args) {
                long i=0;
                long odds=0;
                long evens=0;
                do {
                if ((i % 2) == 0) {
                        evens++;
                        }
                else {
                        odds++;
                        }
                i++;
                } while (i<9000000000L);
                System.out.println("Odd " + odds);
                System.out.println("Even " + evens);
        }
}

وسنكتب نفس الشيء تمامًا ، لكننا نغير حرفين حرفياً ، ونتحقق من نفس الشيء من خلال ثنائي AND.

public class OddEvenViaAnd {
        public static void main (String[] args) {
                long i=0;
                long odds=0;
                long evens=0;
                do {
                if ((i & 1) == 0) {
                        evens++;
                        }
                else {
                        odds++;
                        }
                i++;
                } while (i<9000000000L);
                System.out.println("Odd " + odds);
                System.out.println("Even " + evens);

نحن الآن بحاجة إلى مقارنة هذين البرنامجين بطريقة أو بأخرى.

الموارد على لينكس. وحدة المعالجة المركزية


تم إنفاق عدد كبير من الساعات على إنشاء أي نظام تشغيل ، ولا سيما على التوزيع العادل للموارد بين البرامج. من ناحية ، هذا أمر جيد ، نظرًا لأن تشغيل برنامجين ، يمكنك التأكد من أنها ستعمل بالتوازي ، ولكن من ناحية أخرى ، عندما تحتاج إلى التحقق من أداء البرنامج ، فمن الضروري للغاية الحد من التأثير الخارجي على البرنامج أو على الأقل تقليله من الآخرين البرامج ونظام التشغيل.

أول شيء يجب معرفته هو المعالج. يقوم نظام التشغيل Linux لكل عملية بتخزين قناع بت ، والذي يشير إلى أي نواة يمكن استخدامها من قبل التطبيق وأيها لا. يمكنك عرض هذا القناع وتغييره باستخدام أمر مجموعة المهام.

على سبيل المثال ، دعنا نرى عدد النوى في المعالج الخاص بي:

[user@localhost]# grep -c processor /proc/cpuinfo
4

يحتوي جهاز الكمبيوتر الخاص بي على معالج به 4 نوى. هذا أمر جيد ، لأنني سأخصص واحدًا منهم لاحتياجاتي.

دعنا نرى ما إذا كانت جميعها قيد الاستخدام حاليًا مع الأمر العلوي:

[user@localhost]# top

اضغط على "1" لعرض المعلومات عن كل قلب على حدة:

top - 13:44:11 up 1 day, 23:26,  7 users,  load average: 1.48, 2.21, 2.02
Tasks: 321 total,   1 running, 320 sleeping,   0 stopped,   0 zombie
%Cpu0  :  7.7 us,  6.8 sy,  0.0 ni, 85.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  9.2 us,  4.2 sy,  0.0 ni, 86.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  7.6 us,  3.4 sy,  0.0 ni, 89.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  8.4 us,  4.2 sy,  0.0 ni, 87.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 16210820 total,   296972 free, 10072092 used,  5841756 buff/cache
KiB Swap: 16777212 total, 16777212 free,        0 used.  5480568 avail Mem
....

هنا نرى أن جميع النوى تستخدم تقريبًا. (تتشابه مؤشراتنا ومعرفات sy تقريبًا لكل نواة).

الآن دعنا نحاول رؤية الأمر نفسه مع أمر مجموعة المهام.

[user@localhost]# taskset -p 1
pid 1's current affinity mask: f

يعني قناع البت "F" في النظام الست عشري 15 في العشري ، أو 1111 في ثنائي (8 + 4 + 2 + 1). يتم تمكين جميع البتات ، مما يعني أن جميع النوى تستخدم من خلال عملية باستخدام PID 1.
في Linux ، عندما تفرز إحدى العمليات عملية أخرى باستدعاء نظام استنساخ ، يتم نسخ قناع البت من الأصل في وقت الاستنساخ. هذا يعني أنه إذا قمنا بتغيير هذا القناع لعملية التهيئة لدينا (في حالتي هو systemd) ، فعند بدء أي عملية جديدة من خلال systemd ، سيتم إطلاق هذه العملية الجديدة بالفعل باستخدام قناع جديد.

يمكنك تغيير القناع للعملية باستخدام نفس الأمر ، مع سرد عدد نوى وحدة المعالجة المركزية التي نريد تركها مستخدمة للعملية. افترض أننا نريد ترك kernel 0.2.3 للعملية الخاصة بنا ، ونريد تعطيل kernel 1 لعملية systemd. للقيام بذلك ، نحتاج إلى تشغيل الأمر:

[user@localhost]#  taskset -pc 0,2,3 1
pid 1's current affinity list: 0-3
pid 1's new affinity list: 0,2,3

نحن نفحص:

[user@localhost]# taskset -p 1
pid 1's current affinity mask: d

تغير القناع إلى "D" في التدوين السداسي العشري ، وهو 13 في العلامة العشرية و 1101 في ثنائي (8 + 4 + 0 + 1).

من الآن فصاعدًا ، ستحتوي أي عملية سيتم استنساخها بواسطة عملية systemd تلقائيًا على قناع 1101 من استخدام وحدة المعالجة المركزية ، مما يعني أنه لن يتم استخدام رقم 1 للنواة.

نحن نحظر استخدام النواة لجميع العمليات


منع عملية لينكس الرئيسية من استخدام نواة واحدة سيؤثر فقط على العمليات الجديدة التي تم إنشاؤها بواسطة هذه العملية. ولكن في نظامي ، لا توجد بالفعل عملية واحدة ، ولكن هناك العديد من الأشخاص ، مثل crond و sshd و bash وغيرها. إذا كنت بحاجة إلى منع جميع العمليات من استخدام نواة واحدة ، فيجب علي تشغيل أمر مجموعة المهام لكل عملية قيد التشغيل.

للحصول على قائمة بجميع العمليات ، سنستخدم واجهة برمجة التطبيقات التي تقدمها لنا النواة ، أي نظام الملفات / proc.

علاوة على ذلك ، في الحلقة ، ننظر إلى PID لكل عملية تشغيل ونغير القناع الخاص بها وجميع الخيوط:

[user@localhost]# cd /proc; for i in `ls -d [0-9]*`; do taskset -a -pc 0,2,3 $i; done
pid 1's current affinity list: 0,2,3
pid 1's new affinity list: 0,2,3
...

نظرًا لأنه أثناء تنفيذ البرنامج ، قد يكون لدى بعض العمليات وقت لتوليد عمليات أخرى ، فمن الأفضل تشغيل هذا الأمر عدة مرات.

تحقق من نتيجة عملنا مع الأمر العلوي:

[user@localhost]# top
top - 14:20:46 up 2 days, 3 min,  7 users,  load average: 0.19, 0.27, 0.57
Tasks: 324 total,   4 running, 320 sleeping,   0 stopped,   0 zombie
%Cpu0  :  8.9 us,  7.7 sy,  0.0 ni, 83.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  9.5 us,  6.0 sy,  0.0 ni, 84.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  8.4 us,  6.6 sy,  0.0 ni, 85.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 16210820 total,   285724 free, 10142548 used,  5782548 buff/cache
KiB Swap: 16777212 total, 16777212 free,        0 used.  5399648 avail Mem

كما ترى ، تغيرت الصورة قليلاً ، الآن بالنسبة إلى kernel 0.2.3 ، فإن متوسط ​​المعلمات لنا ، sy ، id هي نفسها بالنسبة لنا ، وبالنسبة إلى kernel 1 ، فإن استهلاكنا الأساسي في مساحة المستخدمين و sys هو 0 ، والنواة خاملة بنسبة 100٪ (خمول 100 ) لم تعد تطبيقات Kernel 1 مستخدمة من قبل تطبيقاتنا ، وتستخدم النواة حاليًا نسبة صغيرة جدًا.

الآن يتم تخفيض مهمة اختبار الأداء إلى بدء عمليتنا على جوهر مجاني.

ذاكرة


يمكن أخذ الذاكرة الفعلية المخصصة لعملية ما بسهولة من أي عملية. تسمى هذه الآلية مبادلة. إذا كان لدى Linux مكان للتبديل ، فسيقوم بذلك على أي حال. الطريقة الوحيدة لمنع نظام التشغيل من أخذ الذاكرة من عمليتنا ، مثل أي عملية أخرى ، هي تعطيل قسم المبادلة تمامًا ، والذي سنفعله:

[user@localhost]$ sudo swapoff -a
[user@localhost]$ free -m
              total        used        free      shared  buff/cache   available
Mem:          15830        7294        1894         360        6641        7746
Swap:             0           0           0

لقد خصصنا نواة معالج واحدة ، وهي غير مستخدمة ، وأزلنا أيضًا القدرة على تبديل الذاكرة من نواة Linux.

القرص


من أجل تقليل تأثير القرص على إطلاق عمليتنا ، أنشئ قرصًا في الذاكرة وانسخ جميع الملفات الضرورية إلى هذا القرص.

قم بإنشاء دليل وتركيب نظام الملفات:

[user@localhost]$ sudo mkdir /mnt/ramdisk;
[user@localhost]$ mount -t tmpfs -o size=512m tmpfs /mnt/ramdisk
[user@localhost]$ chown user: /mnt/ramdisk

الآن نحن بحاجة إلى معرفة ما وكيف نخطط لإطلاقه. لتشغيل برنامجنا ، نحتاج أولاً إلى تجميع الكود الخاص بنا:

[user@localhost]$ javac OddEvenViaMod.java

ثم تحتاج إلى تشغيله:

[user@localhost]$ java OddEvenViaMod

ولكن في حالتنا ، نريد تشغيل العملية على قلب المعالج الذي لا تستخدمه أي عملية أخرى. لذلك ، قم بتشغيله من خلال مجموعة المهام:

[user@localhost]# taskset -c 1 time java OddEvenViaMod

في اختباراتنا ، نحتاج إلى قياس الوقت ، بحيث يتحول خط الإطلاق إلى

taskset -c 1 time java OddEvenViaMod

يدعم نظام التشغيل Linux العديد من تنسيقات الملفات القابلة للتنفيذ ، وأكثرها شيوعًا هو تنسيق ELF. يسمح لك تنسيق الملف هذا بإخبار نظام التشغيل بعدم تشغيل الملف الخاص بك ، ولكن تشغيل ملف آخر. للوهلة الأولى ، لا يبدو منطقيًا ومفهومًا للغاية. تخيل أنني أطلقت لعبة كاسحة الألغام ، وبدأت لعبة ماريو بالنسبة لي - تبدو وكأنها فيروس. لكن هذا هو المنطق. إذا كان برنامجي يتطلب أي مكتبة ديناميكية ، على سبيل المثال libc ، أو أي مكتبة أخرى ، فهذا يعني أنه يجب على نظام التشغيل أولاً تحميل هذه المكتبة في الذاكرة ، وبعد ذلك تحميل برنامجي وتشغيله. ويبدو من المنطقي وضع هذه الوظيفة في نظام التشغيل نفسه ، ولكن نظام التشغيل يعمل في منطقة محمية من الذاكرة ويجب أن يحتوي على أقل قدر ممكن من الوظائف والضرورية.لذلك ، يوفر تنسيق ELF الفرصة لإخبار نظام التشغيل أننا نريد تنزيل برنامج آخر ، وسوف يقوم هذا البرنامج "الآخر" بتنزيل جميع المكتبات الضرورية وبرنامجنا وبدء كل شيء.

لذا ، نحتاج إلى تشغيل 3 ملفات ، هذه هي مجموعة المهام والوقت وجافا.

تحقق أولهم:

[user@localhost]$ whereis taskset
taskset: /usr/bin/taskset /usr/share/man/man1/taskset.1.gz

سيقوم Bash بتشغيل الملف / usr / bin / tasket ، والتحقق من ما بداخله:

[user@localhost]$ file /usr/bin/taskset
/usr/bin/taskset: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=7a2fd0779f64aa9047faa00f498042f0f0c5dc60, stripped

هذا هو ملف ELF الذي كتبته أعلاه. في ملف ELF ، بالإضافة إلى البرنامج نفسه ، هناك رؤوس مختلفة. من خلال تشغيل هذا الملف ، يتحقق نظام التشغيل من الرؤوس الخاصة به ، وإذا كان العنوان "طلب مترجم البرنامج" موجودًا في الملف ، فسوف يقوم نظام التشغيل بتشغيل الملف من هذا الرأس ، وسوف يمرر الملف الذي تم تشغيله في البداية كوسيطة.

تحقق من وجود هذا الرأس في ملف ELF الخاص بنا:

[user@localhost]$ readelf -a /usr/bin/taskset  | grep -i interpreter
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

العنوان موجود ، مما يعني أنه من خلال تشغيل ملف / usr / bin / tasket نقوم بالفعل بتشغيل /lib64/ld-linux-x86-64.so.2.

تحقق من ماهية هذا الملف:

[user@localhost]$ ls -lah /lib64/ld-linux-x86-64.so.2
lrwxrwxrwx 1 root root 10 May 21  2019 /lib64/ld-linux-x86-64.so.2 -> ld-2.17.so

هذا رابط سيم للملف / lib64/ld-2.17.so. تحقق من ذلك:

[user@localhost]$ file /lib64/ld-2.17.so
/lib64/ld-2.17.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=a527fe72908703c5972ae384e78d1850d1881ee7, not stripped

كما ترى ، هذا ملف ELF آخر سيتم تشغيل نظام التشغيل. نحن ننظر إلى الرؤوس:

[user@localhost]$ readelf -a /lib64/ld-2.17.so  | grep -i interpreter
[user@localhost]$

نرى أن ملف ELF هذا لا يحتوي على مثل هذا الرأس ، لذلك سيقوم نظام التشغيل بتشغيل هذا الملف ونقل التحكم إليه. وسيفتح هذا الملف بالفعل ملفنا / usr / bin / tasket ، ويقرأ من هناك معلومات عن جميع المكتبات الضرورية. قائمة المكتبات المطلوبة موجودة أيضًا في رؤوس ملف ELF. يمكننا إلقاء نظرة على هذه القائمة باستخدام الأمر ldd أو readelf ، وهو نفس الشيء:

[user@localhost]$ ldd /usr/bin/taskset
	linux-vdso.so.1 =>  (0x00007ffc4c1df000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f4a24c4e000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f4a2501b000)

[user@localhost]$ readelf -a /usr/bin/taskset  | grep NEEDED
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

VDSO هو جزء من الذاكرة المرتبطة لا يرتبط بالمكتبات ، وبالتالي فهو مفقود من ملف ELF كمكتبة ضرورية.

وهذا يوضح أن البرنامج / lib64/ld-2.17.so مسؤول عن تشغيل جميع البرامج التي تتطلبها ، وهذه كلها برامج ذات مكتبات مرتبطة ديناميكيًا.

إذا قمنا بتشغيل / usr / bin / tasket ، فسيكون هذا هو نفسه تمامًا مثل تشغيل /lib64/ld-2.17.so مع الوسيطة / usr / bin / tasket.

نعود إلى مشكلة تأثير القرص على اختباراتنا. الآن نعلم أنه إذا أردنا تحميل برنامجنا من الذاكرة ، فنحن بحاجة إلى نسخ ملف واحد ، وليس عدة ملفات:

[user@localhost]$ cp /lib64/libc-2.17.so /mnt/ramdisk/
[user@localhost]$ cp /lib64/ld-2.17.so /mnt/ramdisk/
[user@localhost]$ cp /usr/bin/taskset /mnt/ramdisk/

نفعل نفس الشيء مع مرور الوقت ، ومتطلبات المكتبة هي نفسها تمامًا (قمنا بالفعل بنسخ ld و libc).

[user@localhost]$ cp /usr/bin/time /mnt/ramdisk/

بالنسبة إلى جافا ، تكون الأشياء أكثر تعقيدًا بعض الشيء ، نظرًا لأن جافا تتطلب العديد من المكتبات المختلفة التي يمكن نسخها لفترة طويلة. لتبسيط حياتي قليلاً ، سأقوم بنسخ الدليل بأكمله من برنامج جافا openjdk الخاص بي إلى قرص في الذاكرة وإنشاء ارتباط sim. بالطبع ، ستظل عمليات الوصول إلى القرص في هذه الحالة ، ولكن سيكون هناك عدد أقل.

[user@localhost]$ cp -R /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64 /mnt/ramdisk/

إعادة تسمية الدليل القديم ، بإضافة النهاية الافتراضية إليه

[user@localhost]$ sudo mv /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64{,.default}

وإنشاء ارتباط رمزي:

[user@localhost]$ sudo ln -s /mnt/ramdisk/java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64 /usr/lib/jvm/

نحن نعرف بالفعل كيفية تشغيل ملف ثنائي من خلال الوسيطة إلى ملف /lib64/ld-2.17.so ، الذي يبدأ بالفعل. لكن كيف تجعل البرنامج / lib64/ld-2.17.so يقوم بتحميل المكتبات المحملة من الدليل الذي حددناه؟ man ld لمساعدتنا ، والذي نتعلم منه أنه إذا أعلنت عن متغير البيئة LD_LIBRARY_PATH ، فإن برنامج ld سيحمّل المكتبات من الأدلة التي نحددها. الآن لدينا جميع البيانات لإعداد خط إطلاق تطبيق Java.

نبدأ عدة مرات متتالية ونتحقق من:

[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so java OddEvenViaMod
Odd 4500000000
Even 4500000000
10.66user 0.01system 0:10.67elapsed 99%CPU (0avgtext+0avgdata 20344maxresident)k
0inputs+64outputs (0major+5229minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so java OddEvenViaMod
Odd 4500000000
Even 4500000000
10.65user 0.01system 0:10.67elapsed 99%CPU (0avgtext+0avgdata 20348maxresident)k
0inputs+64outputs (0major+5229minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so java OddEvenViaMod
Odd 4500000000
Even 4500000000
10.66user 0.01system 0:10.68elapsed 99%CPU (0avgtext+0avgdata 20348maxresident)k
0inputs+64outputs (0major+5229minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so java OddEvenViaMod
Odd 4500000000
Even 4500000000
10.65user 0.01system 0:10.67elapsed 99%CPU (0avgtext+0avgdata 20348maxresident)k
0inputs+96outputs (0major+5234minor)pagefaults 0swaps
[user@localhost ramdisk]$

أثناء تنفيذ البرنامج ، يمكننا تشغيل أعلى وتأكد من أن البرنامج يعمل على وحدة المعالجة المركزية الأساسية الصحيحة.

[user@localhost ramdisk]$ top
...
%Cpu0  : 19.7 us, 11.7 sy,  0.0 ni, 68.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  9.8 us,  9.1 sy,  0.0 ni, 81.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 14.0 us,  9.0 sy,  0.0 ni, 77.1 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 s
...

كما ترون ، فإن النتائج في معظم الحالات متشابهة. لسوء الحظ ، لا يمكننا إزالة تأثير نظام التشغيل تمامًا على وحدة المعالجة المركزية الأساسية ، لذلك لا تزال النتيجة تعتمد على المهام المحددة داخل نواة Linux في وقت الإطلاق. لذلك ، من الأفضل استخدام متوسط ​​قيم عدة بدايات.

في حالتنا ، نرى أن برنامج جافا يعالج 9،000،000،000 مع التكافؤ خلال الفترة المتبقية من القسم في 10.65 ثانية على وحدة معالجة مركزية واحدة.

دعونا نقوم بنفس الاختبار مع برنامجنا الثاني ، الذي يفعل نفس الشيء من خلال ثنائي و.

[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so java OddEvenViaAnd
Odd 4500000000
Even 4500000000
4.02user 0.00system 0:04.03elapsed 99%CPU (0avgtext+0avgdata 20224maxresident)k
0inputs+64outputs (0major+5197minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so java OddEvenViaAnd
Odd 4500000000
Even 4500000000
4.01user 0.00system 0:04.03elapsed 99%CPU (0avgtext+0avgdata 20228maxresident)k
0inputs+64outputs (0major+5199minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so java OddEvenViaAnd
Odd 4500000000
Even 4500000000
4.01user 0.01system 0:04.03elapsed 99%CPU (0avgtext+0avgdata 20224maxresident)k
0inputs+64outputs (0major+5198minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so java OddEvenViaAnd
Odd 4500000000
Even 4500000000
4.02user 0.00system 0:04.03elapsed 99%CPU (0avgtext+0avgdata 20224maxresident)k
0inputs+64outputs (0major+5198minor)pagefaults 0swaps

يمكننا الآن القول بثقة أن مقارنة التكافؤ من خلال ثنائي و تستغرق 4.02 ثانية ، مما يعني أنه بالمقارنة مع التحقق من خلال ما تبقى من القسمة ، فإنها تعمل بشكل أسرع 2.6 مرة ، على الأقل في إصدار openjdk 1.8.0.

أوراكل جافا مقابل Openjdk


أنا تحميل وتفكيك جدك جافا من موقع أوراكل إلى الدليل /mnt/ramdisk/jdk-13.0.2.

ترجمة:

[user@localhost ramdisk]$ /mnt/ramdisk/jdk-13.0.2/bin/javac OddEvenViaAnd.java

أطلقنا:

[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/jdk-13.0.2/bin/java OddEvenViaAnd
Odd 4500000000
Even 4500000000
10.39user 0.01system 0:10.41elapsed 99%CPU (0avgtext+0avgdata 24260maxresident)k
0inputs+64outputs (0major+6979minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/jdk-13.0.2/bin/java OddEvenViaAnd
Odd 4500000000
Even 4500000000
10.40user 0.01system 0:10.42elapsed 99%CPU (0avgtext+0avgdata 24268maxresident)k
0inputs+64outputs (0major+6985minor)pagefaults 0swaps

نقوم بتجميع البرنامج الثاني:

[user@localhost ramdisk]$ /mnt/ramdisk/jdk-13.0.2/bin/javac OddEvenViaMod.java

أطلقنا:

[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/jdk-13.0.2/bin/java OddEvenViaMod
Odd 4500000000
Even 4500000000
10.39user 0.01system 0:10.40elapsed 99%CPU (0avgtext+0avgdata 24324maxresident)k
0inputs+96outputs (0major+7003minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/jdk-13.0.2/bin/java OddEvenViaMod
Odd 4500000000
Even 4500000000
10.40user 0.00system 0:10.41elapsed 99%CPU (0avgtext+0avgdata 24316maxresident)k
0inputs+64outputs (0major+6992minor)pagefaults 0swaps

وقت التنفيذ لنفس المصادر في oracle jdk هو نفسه لبقية القسم والثنائي AND ، والذي يبدو طبيعيًا ، لكن هذه المرة سيئة بنفس الدرجة ، والتي تم عرضها في openjdk على بقية القسم.

بيثون


دعونا نحاول مقارنة الشيء نفسه في Python. أولاً ، الخيار مع الباقي من القسمة على 2:

odd=0
even=0
for i in xrange(100000000):
	if i % 2 == 0:
		even += 1
	else:
		odd += 1
print "even", even
print "odd", odd

أطلقنا:

[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/python2.7 odd_mod.py
even 50000000
odd 50000000
11.69user 0.00system 0:11.69elapsed 99%CPU (0avgtext+0avgdata 4584maxresident)k
0inputs+0outputs (0major+1220minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/python2.7 odd_mod.py
even 50000000
odd 50000000
11.67user 0.00system 0:11.67elapsed 99%CPU (0avgtext+0avgdata 4584maxresident)k
0inputs+0outputs (0major+1220minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/python2.7 odd_mod.py
even 50000000
odd 50000000
11.66user 0.00system 0:11.66elapsed 99%CPU (0avgtext+0avgdata 4584maxresident)k
0inputs+0outputs (0major+1220minor)pagefaults 0swaps

الآن نفس الشيء مع ثنائي و:

odd=0
even=0
for i in xrange(100000000):
	if i & 1 == 0:
		even += 1
	else:
		odd += 1
print "even", even
print "odd", odd

أطلقنا:

[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/python2.7 odd_and.py
even 50000000
odd 50000000
10.41user 0.00system 0:10.41elapsed 99%CPU (0avgtext+0avgdata 4588maxresident)k
0inputs+0outputs (0major+1221minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/python2.7 odd_and.py
even 50000000
odd 50000000
10.43user 0.00system 0:10.43elapsed 99%CPU (0avgtext+0avgdata 4588maxresident)k
0inputs+0outputs (0major+1222minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/python2.7 odd_and.py
even 50000000
odd 50000000
10.43user 0.00system 0:10.43elapsed 99%CPU (0avgtext+0avgdata 4584maxresident)k
0inputs+0outputs (0major+1222minor)pagefaults 0swaps

أظهرت النتائج أن AND أسرع.

على الإنترنت ، كتب مرات عديدة أن المتغيرات العالمية في Python أبطأ. قررت مقارنة وقت تنفيذ البرنامج الأخير بـ AND وبالضبط نفسه ، ولكن ملفوفة في دالة:

def main():
	odd=0
	even=0
	for i in xrange(100000000):
		if i & 1 == 0:
			even += 1
		else:
			odd += 1
	print "even", even
	print "odd", odd

main()

تعمل في الوظيفة:

[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/python2.7 odd_and_func.py
even 50000000
odd 50000000
5.08user 0.00system 0:05.08elapsed 99%CPU (0avgtext+0avgdata 4592maxresident)k
0inputs+0outputs (0major+1222minor)pagefaults 0swaps
[user@localhost ramdisk]$
[user@localhost ramdisk]$ export LD_LIBRARY_PATH=/mnt/ramdisk ; /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/taskset -c 1 /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/time /mnt/ramdisk/ld-2.17.so /mnt/ramdisk/python2.7 odd_and_func.py
even 50000000
odd 50000000
5.08user 0.00system 0:05.09elapsed 99%CPU (0avgtext+0avgdata 4592maxresident)k
0inputs+0outputs (0major+1223minor)pagefaults 0swaps

كما ترون ، فإن نفس مقارنة التكافؤ في Python من خلال ثنائي و في دالة تعالج 100000000 أرقام على نواة وحدة معالجة مركزية واحدة في 5 ثوانٍ تقريبًا ، نفس المقارنة عبر و بدون وظيفة تستغرق 10 ثوانٍ تقريبًا ، والمقارنة بدون وظيفة من خلال ما تبقى من القسمة تستغرق ~ 11 ثانية

لماذا يعمل برنامج Python في دالة بشكل أسرع من بدونه وقد تم وصفه بالفعل أكثر من مرة ويرتبط بنطاق المتغيرات.

تمتلك Python القدرة على تفكيك برنامج إلى وظائف داخلية يستخدمها Python عند تفسير البرنامج. دعنا نرى الوظائف التي تستخدمها Python للمتغير مع وظيفة odd_and_func.py:

[user@localhost ramdisk]# python
Python 2.7.5 (default, Jun 20 2019, 20:27:34)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def main():
...     odd=0
...     even=0
...     for i in xrange(100000000):
...             if i & 1 == 0:
...                     even += 1
...             else:
...                     odd += 1
...     print "even", even
...     print "odd", odd
...
>>> import dis
>>> dis.dis(main)
  2           0 LOAD_CONST               1 (0)
              3 STORE_FAST               0 (odd)

  3           6 LOAD_CONST               1 (0)
              9 STORE_FAST               1 (even)

  4          12 SETUP_LOOP              59 (to 74)
             15 LOAD_GLOBAL              0 (xrange)
             18 LOAD_CONST               2 (100000000)
             21 CALL_FUNCTION            1
             24 GET_ITER
        >>   25 FOR_ITER                45 (to 73)
             28 STORE_FAST               2 (i)

  5          31 LOAD_FAST                2 (i)
             34 LOAD_CONST               3 (1)
             37 BINARY_AND
             38 LOAD_CONST               1 (0)
             41 COMPARE_OP               2 (==)
             44 POP_JUMP_IF_FALSE       60

  6          47 LOAD_FAST                1 (even)
             50 LOAD_CONST               3 (1)
             53 INPLACE_ADD
             54 STORE_FAST               1 (even)
             57 JUMP_ABSOLUTE           25

  8     >>   60 LOAD_FAST                0 (odd)
             63 LOAD_CONST               3 (1)
             66 INPLACE_ADD
             67 STORE_FAST               0 (odd)
             70 JUMP_ABSOLUTE           25
        >>   73 POP_BLOCK

  9     >>   74 LOAD_CONST               4 ('even')
             77 PRINT_ITEM
             78 LOAD_FAST                1 (even)
             81 PRINT_ITEM
             82 PRINT_NEWLINE

 10          83 LOAD_CONST               5 ('odd')
             86 PRINT_ITEM
             87 LOAD_FAST                0 (odd)
             90 PRINT_ITEM
             91 PRINT_NEWLINE
             92 LOAD_CONST               0 (None)
             95 RETURN_VALUE

وتحقق من ذلك دون استخدام الوظيفة في الكود الخاص بنا:

>>> f=open("odd_and.py","r")
>>> l=f.read()
>>>
>>> l
'odd=0\neven=0\nfor i in xrange(100000000):\n\tif i & 1 == 0:\n\t\teven += 1\n\telse:\n\t\todd += 1\nprint "even", even\nprint "odd", odd\n'
>>> k=compile(l,'l','exec')
>>> k
<code object <module> at 0x7f2bdf39ecb0, file "l", line 1>
>>> dis.dis(k)
  1           0 LOAD_CONST               0 (0)
              3 STORE_NAME               0 (odd)

  2           6 LOAD_CONST               0 (0)
              9 STORE_NAME               1 (even)

  3          12 SETUP_LOOP              59 (to 74)
             15 LOAD_NAME                2 (xrange)
             18 LOAD_CONST               1 (100000000)
             21 CALL_FUNCTION            1
             24 GET_ITER
        >>   25 FOR_ITER                45 (to 73)
             28 STORE_NAME               3 (i)

  4          31 LOAD_NAME                3 (i)
             34 LOAD_CONST               2 (1)
             37 BINARY_AND
             38 LOAD_CONST               0 (0)
             41 COMPARE_OP               2 (==)
             44 POP_JUMP_IF_FALSE       60

  5          47 LOAD_NAME                1 (even)
             50 LOAD_CONST               2 (1)
             53 INPLACE_ADD
             54 STORE_NAME               1 (even)
             57 JUMP_ABSOLUTE           25

  7     >>   60 LOAD_NAME                0 (odd)
             63 LOAD_CONST               2 (1)
             66 INPLACE_ADD
             67 STORE_NAME               0 (odd)
             70 JUMP_ABSOLUTE           25
        >>   73 POP_BLOCK

  8     >>   74 LOAD_CONST               3 ('even')
             77 PRINT_ITEM
             78 LOAD_NAME                1 (even)
             81 PRINT_ITEM
             82 PRINT_NEWLINE

  9          83 LOAD_CONST               4 ('odd')
             86 PRINT_ITEM
             87 LOAD_NAME                0 (odd)
             90 PRINT_ITEM
             91 PRINT_NEWLINE
             92 LOAD_CONST               5 (None)
             95 RETURN_VALUE

كما ترى ، في المتغير مع الوظيفة المعلنة ، تستخدم Python الوظائف الداخلية مع postfix FAST ، على سبيل المثال ، STORE_FAST ، LOAD_FAST ، وفي المتغير بدون إعلان الوظيفة ، تستخدم Python الوظائف الداخلية STORE_NAME و LOAD_NAME.

هذه المقالة لها القليل من المعنى العملي وتهدف إلى فهم بعض ميزات لينكس والمترجمين.

جيد للجميع!

All Articles