Pengujian kinerja kode Linux dengan contoh-contoh

Ketika saya mulai belajar Java, salah satu tugas pertama yang saya coba selesaikan adalah untuk menentukan genap / ganjil. Saya tahu beberapa cara untuk melakukan ini, tetapi memutuskan untuk mencari cara yang "benar" di Internet. Informasi tentang semua tautan yang ditemukan memberi tahu saya tentang satu-satunya solusi yang tepat dari formulir x% 2, untuk mendapatkan sisa dari divisi tersebut. Jika sisanya adalah 0, angkanya genap, jika sisanya adalah 1, itu ganjil.

Sejak zaman ZX Spectrum, saya ingat cara lain dan itu terkait dengan representasi angka dalam sistem biner. Angka apa pun dalam sistem desimal dapat ditulis sebagai jumlah dari kekuatan dua. Misalnya, untuk satu byte, dan ini adalah 8 bit, angka apa pun dalam sistem desimal dapat direpresentasikan sebagai jumlah dari angka 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1.

Ini hanya urutan kekuatan dua. Ketika menerjemahkan angka ke dalam sistem biner, jika kita perlu memperhitungkan angka tersebut, dalam representasi biner itu akan menjadi satu, jika tidak perlu, itu akan menjadi 0.

Misalnya:

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

Anda dapat segera memperhatikan fakta bahwa angka ganjil hanya dapat memberikan nol kekuatan dua dengan nilai 1, semua kekuatan dua lainnya akan genap bahkan dengan definisi. Ini secara otomatis berarti bahwa dari sudut pandang sistem bilangan biner, jika kita ingin memeriksa angka untuk paritas, kita tidak perlu memeriksa seluruh bilangan, tidak peduli seberapa besar itu. Kita hanya perlu memeriksa bit pertama (paling kanan). Jika 0, maka angkanya genap, karena semua bit lainnya memberikan angka genap, dan sebaliknya, jika bit tersebut berada di bit paling kanan, maka jumlahnya dijamin ganjil, karena semua bit lainnya hanya memberikan nilai genap.
Untuk memeriksa hanya bit yang tepat dalam suatu angka, Anda dapat menggunakan beberapa metode. Salah satunya adalah biner DAN.

DAN


Biner DAN (DAN) bekerja dengan aturan berikut. Jika Anda berlaku untuk nomor apa pun, sebut saja itu orisinal, logis, dan dengan angka 0, maka hasil dari operasi semacam itu selalu 0. Dengan demikian, Anda dapat nol bit yang tidak Anda butuhkan. Jika Anda berlaku untuk yang asli 1, maka Anda mendapatkan yang asli.

Dalam sistem biner, mudah untuk menulis ini:

0 DAN 0 = 0 (nol aslinya)
1 DAN 0 = 0 (nol asli)
0 DAN 1 = 0 (jangan ganti yang asli)
1 DAN 1 = 1 (jangan ganti yang asli)

Dari sini beberapa sederhana aturan

Jika kita menerapkan operasi DAN dari semua unit ke semua angka (semua bit aktif), kita mendapatkan nomor awal yang sama.

Jika kita menerapkan DAN dari semua nol ke nomor berapa pun (semua bit tidak aktif), kita mendapatkan 0.

Misalnya:

Jika kita menerapkan AND 0 ke byte 13, maka kita mendapatkan 0. Dalam desimal itu terlihat seperti 13 AND 0 = 0

Jika kita menerapkan AND 0 ke byte 200, kita mendapatkan 0, atau menuliskan 200 DAN 0 = 0 secara singkat.
Hal yang sama adalah kebalikannya, berlaku untuk 13 semua bit yang disertakan, untuk byte itu akan menjadi delapan unit, dan kami mendapatkan yang asli. Dalam sistem biner 00001101 DAN 11111111 = 00001101 atau dalam sistem desimal 13 AND 255 = 13

Untuk 200 akan ada 11001000 DAN 11111111 = 11001000, masing-masing, atau dalam sistem desimal 200 DAN 255 = 200

Verifikasi biner


Untuk memeriksa jumlah paritas, kita hanya perlu memeriksa bit paling kanan. Jika 0, maka angkanya genap; jika 1, maka tidak genap. Mengetahui bahwa dengan DAN kita dapat meninggalkan beberapa bit asli, dan beberapa kita dapat mengatur ulang, kita hanya dapat mengatur ulang semua bit kecuali yang paling kanan. Sebagai contoh:

13 dalam sistem biner adalah 1101. Mari kita terapkan DAN 0001 untuk itu (kita mereset semua bit, yang terakhir tetap yang asli). Pada 1101, kami mengubah semua bit menjadi 0 kecuali yang terakhir dan mendapatkan 0001. Kami hanya mendapatkan bit terakhir dari nomor asli kami. Dalam sistem desimal, akan terlihat seperti 13 DAN 1 = 1.

Hal yang sama dengan angka 200, dalam bentuk biner 11001000. Kami menerapkan DAN 00000001 untuk itu, menurut skema yang sama, nol semua bit, biarkan yang terakhir seperti apa adanya, kami mendapatkan 00000000, dan kami mereset 7 nol kiri dengan AND, dan kami mendapat 0 terakhir dari nomor aslinya. Dalam sistem desimal, sepertinya 200 AND 1 = 0

Jadi, menerapkan perintah AND 1 ke angka apa pun, kita mendapatkan 0 atau 1. Dan jika hasilnya 0, maka angkanya genap. Ketika 1, jumlahnya aneh.

Di Jawa, biner DAN ditulis sebagai &. Dengan demikian, 200 & 1 = 0 (datar) dan 13 & 1 = 1 (tidak datar).

Ini menyiratkan setidaknya dua metode untuk menentukan angka genap.

X% 2 - melalui sisa pembagian oleh dua
X & 1 - melalui biner DAN

Operasi biner seperti OR, AND, XOR diproses oleh prosesor dalam jumlah waktu minimum. Tetapi operasi divisi adalah tugas yang tidak sepele, dan untuk menjalankannya, prosesor perlu memproses banyak instruksi, pada dasarnya menjalankan seluruh program. Namun, ada operasi biner kiri dan kanan yang memungkinkan, misalnya, dengan cepat membagi angka dengan 2. Pertanyaannya adalah apakah kompiler menggunakan optimasi ini dan apakah ada perbedaan antara dua perbandingan ini, yang sebenarnya melakukan hal yang sama.

Coding


Kami akan menulis sebuah program yang akan memproses angka 9.000.000.000 dalam satu siklus secara berurutan, dan menentukan milik mereka genap / ganjil dengan menentukan sisa dari divisi tersebut.

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

Dan kami akan menulis persis sama, tetapi secara harfiah mengubah dua karakter, memeriksa hal yang sama melalui biner DAN.

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

Sekarang kita perlu membandingkan kedua program ini.

Sumberdaya di Linux. CPU


Sejumlah besar jam telah dihabiskan untuk menciptakan sistem operasi apa pun, khususnya distribusi sumber daya yang adil antar program. Di satu sisi, ini bagus, karena menjalankan dua program, Anda dapat yakin bahwa mereka akan bekerja secara paralel, tetapi di sisi lain, ketika Anda perlu memeriksa kinerja suatu program, sangat penting untuk membatasi atau setidaknya mengurangi pengaruh eksternal pada program dari orang lain. program dan sistem operasi.

Hal pertama yang harus diketahui adalah prosesor. OS Linux untuk setiap proses menyimpan bitmask, yang menunjukkan kernel mana yang dapat digunakan oleh aplikasi dan mana yang tidak. Anda dapat melihat dan mengubah topeng ini dengan perintah tasket.

Sebagai contoh, mari kita lihat jumlah core di prosesor saya:

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

Komputer saya memiliki prosesor dengan 4 core. Ini bagus, karena saya akan mengalokasikan salah satunya untuk kebutuhan saya.

Mari kita lihat apakah semuanya sedang digunakan dengan perintah teratas:

[user@localhost]# top

Tekan "1" untuk melihat informasi pada setiap inti secara terpisah:

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
....

Di sini kita melihat bahwa semua core digunakan kira-kira sama. (indikator kami dan sy dan id kira-kira sama untuk setiap inti).

Sekarang mari kita coba untuk melihat hal yang sama dengan perintah tasket.

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

Bitmask "F" dalam sistem heksadesimal berarti 15 dalam desimal, atau 1111 dalam biner (8 + 4 + 2 + 1). Semua bit diaktifkan, yang berarti bahwa semua core digunakan oleh proses dengan PID 1.
Di Linux, ketika satu proses memunculkan yang lain dengan panggilan sistem klon, topeng bit disalin dari induk pada saat kloning. Ini berarti bahwa jika kita mengubah topeng ini untuk proses init kita (dalam kasus saya itu adalah systemd), maka ketika memulai proses baru melalui systemd proses baru ini sudah akan diluncurkan dengan topeng baru.

Anda dapat mengubah topeng untuk proses menggunakan perintah yang sama, daftar nomor core CPU yang ingin kita tinggalkan digunakan untuk proses. Misalkan kita ingin meninggalkan kernel 0.2.3 untuk proses kami, dan kami ingin menonaktifkan kernel 1 untuk proses systemd kami. Untuk melakukan ini, kita perlu menjalankan perintah:

[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

Kami memeriksa:

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

Topeng berubah menjadi "D" dalam notasi heksadesimal, yaitu 13 dalam desimal dan 1101 dalam biner (8 + 4 + 0 + 1).

Mulai sekarang, setiap proses yang akan dikloning oleh proses systemd akan secara otomatis memiliki mask 1101 penggunaan CPU, yang berarti bahwa kernel nomor 1 tidak akan digunakan.

Kami melarang penggunaan kernel untuk semua proses


Mencegah proses utama Linux menggunakan kernel tunggal hanya akan memengaruhi proses baru yang dibuat oleh proses ini. Tetapi dalam sistem saya sudah tidak ada satu proses, tetapi banyak sekali, seperti crond, sshd, bash dan lainnya. Jika saya perlu melarang semua proses menggunakan satu inti, maka saya harus menjalankan perintah tasket untuk setiap proses yang berjalan.

Untuk mendapatkan daftar semua proses, kami akan menggunakan API yang diberikan kernel kepada kami, yaitu sistem file / proc.

Lebih lanjut dalam loop, kita melihat PID dari setiap proses yang berjalan dan mengubah topeng untuk itu dan semua utas:

[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
...

Karena selama pelaksanaan program, beberapa proses dapat memiliki waktu untuk menelurkan proses lain, lebih baik untuk menjalankan perintah ini beberapa kali.

Periksa hasil pekerjaan kami dengan perintah atas:

[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

Seperti yang Anda lihat, gambar telah berubah sedikit, sekarang untuk kernel 0.2.3 parameter rata-rata kita, sy, id adalah sama untuk kita, dan untuk kernel 1 konsumsi inti kita di userspace dan sys adalah 0, dan kernel idle di 100% (idle 100 ) Kernel 1 tidak lagi digunakan oleh aplikasi kami dan saat ini persentase yang sangat kecil digunakan oleh kernel.

Sekarang tugas menguji kinerja dikurangi untuk memulai proses kami pada inti gratis.

Penyimpanan


Memori fisik yang dialokasikan untuk suatu proses dapat dengan mudah diambil dari proses apa pun. Mekanisme ini disebut swap. Jika Linux memiliki tempat untuk swap, ia tetap akan melakukannya. Satu-satunya cara untuk mencegah OS mengambil memori dari proses kami, seperti proses lainnya, adalah menonaktifkan sepenuhnya bagian swap, yang akan kami lakukan:

[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

Kami mengalokasikan 1 inti prosesor, yang tidak digunakan, dan juga menghapus kemampuan untuk menukar memori dari kernel Linux.

Disk


Untuk mengurangi dampak disk pada peluncuran proses kami, buat disk dalam memori dan salin semua file yang diperlukan ke disk ini.

Buat direktori dan pasang sistem file:

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

Sekarang kita perlu mencari tahu apa dan bagaimana kita berencana untuk meluncurkannya. Untuk menjalankan program kami, pertama-tama kita perlu mengkompilasi kode kita:

[user@localhost]$ javac OddEvenViaMod.java

Maka Anda perlu menjalankannya:

[user@localhost]$ java OddEvenViaMod

Namun dalam kasus kami, kami ingin menjalankan proses pada inti prosesor yang tidak digunakan oleh proses lain. Oleh karena itu, jalankan melalui tasket:

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

Dalam pengujian kami, kami perlu mengukur waktu, jadi jalur peluncuran kami berubah menjadi

taskset -c 1 time java OddEvenViaMod

OS Linux mendukung beberapa format file yang dapat dieksekusi, dan yang paling umum adalah format ELF. Format file ini memungkinkan Anda untuk memberitahu OS untuk tidak menjalankan file Anda, tetapi untuk menjalankan file lain. Pada pandangan pertama, itu tidak terdengar sangat logis dan dapat dimengerti. Bayangkan saya meluncurkan game Minesweeper, dan game Mario dimulai untuk saya - sepertinya virus. Tapi ini logikanya. Jika program saya memerlukan pustaka dinamis, misalnya libc, atau lainnya, ini berarti bahwa OS harus memuat pustaka ini terlebih dahulu ke dalam memori, dan setelah itu memuat dan menjalankan program saya. Dan tampaknya logis untuk menempatkan fungsionalitas seperti itu di sistem operasi itu sendiri, tetapi sistem operasi itu bekerja di area memori yang dilindungi dan harus memuat fungsionalitas sesedikit mungkin dan perlu.Oleh karena itu, format ELF memberikan kesempatan untuk memberi tahu OS bahwa kami ingin mengunduh beberapa program lain, dan program "lain" ini akan mengunduh semua perpustakaan dan program kami yang diperlukan dan memulai semuanya.

Jadi, kita perlu menjalankan 3 file, ini tasket, waktu, java.

Periksa yang pertama:

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

Bash akan menjalankan file / usr / bin / tasket, periksa apa yang ada di dalamnya:

[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

Ini adalah file ELF yang saya tulis di atas. Dalam file ELF, selain program itu sendiri, ada berbagai tajuk. Dengan meluncurkan file ini, OS akan memeriksa tajuknya, dan jika tajuk “Requesting program interpreter” ada dalam file, OS akan meluncurkan file dari tajuk ini, dan ia akan meneruskan file yang awalnya diluncurkan sebagai argumen.

Periksa apakah header ini ada di file ELF kami:

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

Header ada, yang berarti bahwa dengan meluncurkan file / usr / bin / tasket kita benar-benar menjalankan /lib64/ld-linux-x86-64.so.2.

Periksa apa file ini:

[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

Ini adalah tautan sim ke file /lib64/ld-2.17.so. Coba lihat:

[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

Seperti yang Anda lihat, ini adalah file ELF lain yang akan dijalankan oleh OS. Kami melihat header:

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

Kami melihat bahwa file ELF ini tidak memiliki header seperti itu, sehingga OS akan menjalankan file ini dan mentransfer kontrol ke sana. Dan sudah file ini akan membuka file kita / usr / bin / tasket, baca dari sana informasi tentang semua perpustakaan yang diperlukan. Daftar pustaka yang diperlukan juga ada di header file ELF. Kita dapat melihat daftar ini dengan perintah ldd atau readelf, yang merupakan hal yang sama:

[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 adalah bagian memori yang ditautkan yang tidak terkait dengan perpustakaan, oleh karena itu hilang dari file ELF sebagai perpustakaan yang diperlukan.

Dari sini jelas bahwa program /lib64/ld-2.17.so bertanggung jawab untuk menjalankan semua program yang membutuhkannya, dan ini semua adalah program dengan pustaka yang terhubung secara dinamis.

Jika kita menjalankan / usr / bin / tasket, ini persis sama dengan kita menjalankan /lib64/ld-2.17.so dengan argumen / usr / bin / tasket.

Kami kembali ke masalah pengaruh disk pada pengujian kami. Sekarang kita tahu bahwa jika kita ingin memuat program kita dari memori, maka kita perlu menyalin bukan satu file, tetapi beberapa:

[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/

Kami melakukan hal yang sama untuk waktu, persyaratan perpustakaan yang persis sama (kami sudah menyalin ld dan libc).

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

Untuk java, masalahnya sedikit lebih rumit, karena java membutuhkan banyak pustaka yang berbeda yang dapat disalin untuk waktu yang lama. Untuk menyederhanakan hidup saya, saya akan menyalin seluruh direktori dari java openjdk ke disk di memori dan membuat tautan sim. Tentu saja, akses disk akan tetap dalam kasus ini, tetapi akan ada lebih sedikit.

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

Ganti nama direktori lama, tambahkan akhiran .default ke dalamnya

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

Dan buat symlink:

[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/

Kita sudah tahu bagaimana menjalankan file biner melalui argumen ke file /lib64/ld-2.17.so, yang sebenarnya dimulai. Tetapi bagaimana cara membuat program /lib64/ld-2.17.so memuat pustaka yang dimuat dari direktori yang kita tentukan? man ld untuk membantu kami, dari mana kami mengetahui bahwa jika Anda mendeklarasikan variabel lingkungan LD_LIBRARY_PATH, program ld akan memuat pustaka dari direktori yang kami tentukan. Sekarang kita memiliki semua data untuk mempersiapkan jalur peluncuran aplikasi Java.

Kami memulai beberapa kali berturut-turut dan memeriksa:

[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]$

Selama eksekusi program, kita dapat menjalankan teratas dan memastikan bahwa program berjalan pada inti CPU yang benar.

[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
...

Seperti yang Anda lihat, hasilnya dalam kebanyakan kasus serupa. Sayangnya, kami tidak dapat sepenuhnya menghapus pengaruh OS pada inti CPU, sehingga hasilnya masih tergantung pada tugas-tugas khusus di dalam kernel Linux pada saat peluncuran. Oleh karena itu, lebih baik menggunakan median nilai beberapa permulaan.

Dalam kasus kami, kami melihat bahwa program java memproses 9.000.000.000 dengan paritas melalui sisa divisi dalam 10,65 detik pada satu inti CPU.

Mari kita lakukan tes yang sama dengan program kedua kami, yang melakukan hal yang sama melalui biner DAN.

[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

Sekarang kita dapat mengatakan dengan yakin bahwa perbandingan untuk paritas melalui biner DAN membutuhkan waktu 4,02 detik, yang berarti dibandingkan dengan memeriksa sisa pembagian, ia bekerja 2,6 kali lebih cepat, setidaknya pada openjdk versi 1.8.0.

Oracle Java vs Openjdk


Saya download dan membongkar java jdk dari situs oracle ke direktori /mnt/ramdisk/jdk-13.0.2.

Kompilasi:

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

Kami meluncurkan:

[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

Kami mengkompilasi program kedua:

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

Kami meluncurkan:

[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

Waktu eksekusi dari sumber yang sama di oracle jdk adalah sama untuk sisa divisi dan biner AND, yang terlihat normal, tetapi kali ini sama buruknya, yang ditunjukkan dalam openjdk pada sisa pembagian.

Python


Mari kita coba bandingkan dengan Python. Pertama, opsi dengan sisa pembagian dengan 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

Kami meluncurkan:

[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

Sekarang hal yang sama dengan biner DAN:

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

Kami meluncurkan:

[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

Hasilnya menunjukkan bahwa DAN lebih cepat.

Di Internet, telah ditulis berkali-kali bahwa variabel global dengan Python lebih lambat. Saya memutuskan untuk membandingkan waktu pelaksanaan program terakhir dengan AND dan persis sama, tetapi dibungkus dengan fungsi:

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

Jalankan dalam fungsi:

[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

Seperti yang Anda lihat, perbandingan paritas yang sama dalam Python melalui biner DAN dalam suatu fungsi memproses angka 100000000 pada satu inti CPU dalam ~ 5 detik, perbandingan yang sama melalui DAN tanpa fungsi membutuhkan ~ 10 detik, dan perbandingan tanpa fungsi melalui sisa pembagian membutuhkan ~ 11 detik

Mengapa program Python dalam suatu fungsi bekerja lebih cepat daripada tanpa itu telah dijelaskan lebih dari satu kali dan terkait dengan ruang lingkup variabel.

Python memiliki kemampuan untuk membongkar suatu program menjadi fungsi internal yang digunakan Python ketika menginterpretasikan suatu program. Mari kita lihat fungsi apa yang digunakan Python untuk varian dengan fungsi 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

Dan periksa sama tanpa menggunakan fungsi dalam kode kami:

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

Seperti yang Anda lihat, dalam varian dengan fungsi yang dideklarasikan, Python menggunakan fungsi internal dengan postfix FAST, misalnya, STORE_FAST, LOAD_FAST, dan dalam varian tanpa deklarasi fungsi, Python menggunakan fungsi internal STORE_NAME dan LOAD_NAME.

Artikel ini memiliki sedikit arti praktis dan lebih ditujukan untuk memahami beberapa fitur Linux, dan kompiler.

Baik untuk semua!

All Articles