Contoh POSIX C runnable minimal
Untuk membuat segalanya lebih konkret, saya ingin memberikan contoh beberapa kasus ekstrem time
dengan beberapa program uji C minimal.
Semua program dapat dikompilasi dan dijalankan dengan:
gcc -ggdb3 -o main.out -pthread -std=c99 -pedantic-errors -Wall -Wextra main.c
time ./main.out
dan telah diuji di Ubuntu 18.10, GCC 8.2.0, glibc 2.28, kernel Linux 4.18, laptop ThinkPad P51, CPU Intel Core i7-7820HQ (4 core / 8 thread), 2x Samsung M471A2K43BB1-CRC RAM (2x 16GiB).
tidur
Tidur yang tidak sibuk tidak dihitung dalam salah satu user
atau sys
, hanya real
.
Misalnya, program yang tidur sebentar:
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <unistd.h>
int main(void) {
sleep(1);
return EXIT_SUCCESS;
}
GitHub hulu .
menghasilkan sesuatu seperti:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
Hal yang sama berlaku untuk program yang diblokir pada IO menjadi tersedia.
Misalnya, program berikut menunggu pengguna untuk memasukkan karakter dan tekan enter:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("%c\n", getchar());
return EXIT_SUCCESS;
}
GitHub hulu .
Dan jika Anda menunggu sekitar satu detik, itu output seperti contoh tidur seperti:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
Untuk alasan ini time
dapat membantu Anda membedakan antara program terikat CPU dan IO: Apa arti istilah "terikat CPU" dan "I / O terikat"?
Beberapa utas
Contoh berikut melakukan niters
iterasi dari pekerjaan CPU-murni yang tidak berguna pada nthreads
utas:
#define _XOPEN_SOURCE 700
#include <assert.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
uint64_t niters;
void* my_thread(void *arg) {
uint64_t *argument, i, result;
argument = (uint64_t *)arg;
result = *argument;
for (i = 0; i < niters; ++i) {
result = (result * result) - (3 * result) + 1;
}
*argument = result;
return NULL;
}
int main(int argc, char **argv) {
size_t nthreads;
pthread_t *threads;
uint64_t rc, i, *thread_args;
/* CLI args. */
if (argc > 1) {
niters = strtoll(argv[1], NULL, 0);
} else {
niters = 1000000000;
}
if (argc > 2) {
nthreads = strtoll(argv[2], NULL, 0);
} else {
nthreads = 1;
}
threads = malloc(nthreads * sizeof(*threads));
thread_args = malloc(nthreads * sizeof(*thread_args));
/* Create all threads */
for (i = 0; i < nthreads; ++i) {
thread_args[i] = i;
rc = pthread_create(
&threads[i],
NULL,
my_thread,
(void*)&thread_args[i]
);
assert(rc == 0);
}
/* Wait for all threads to complete */
for (i = 0; i < nthreads; ++i) {
rc = pthread_join(threads[i], NULL);
assert(rc == 0);
printf("%" PRIu64 " %" PRIu64 "\n", i, thread_args[i]);
}
free(threads);
free(thread_args);
return EXIT_SUCCESS;
}
GitHub kode hulu + plot .
Kemudian kami memplot wall, user dan sys sebagai fungsi dari jumlah utas untuk iterasi 10 ^ 10 yang diperbaiki pada CPU 8 hyperthread saya:
Plot data .
Dari grafik, kita melihat bahwa:
untuk aplikasi inti tunggal CPU intensif, dinding dan pengguna hampir sama
untuk 2 core, pengguna sekitar 2x dinding, yang berarti bahwa waktu pengguna dihitung di semua utas.
pengguna pada dasarnya dua kali lipat, dan sementara dinding tetap sama.
ini berlanjut hingga 8 utas, yang cocok dengan jumlah hyperthreads saya di komputer saya.
Setelah 8, dinding mulai meningkat juga, karena kami tidak memiliki CPU tambahan untuk membuat lebih banyak pekerjaan dalam jumlah waktu tertentu!
Rasio dataran tinggi pada titik ini.
Perhatikan bahwa grafik ini hanya sangat jelas dan sederhana karena pekerjaan itu murni terikat CPU: jika itu terikat memori, maka kita akan mendapatkan penurunan kinerja jauh lebih awal dengan lebih sedikit core karena akses memori akan menjadi hambatan seperti yang ditunjukkan pada Apa arti dari istilah "CPU terikat" dan "I / O terikat"?
Sys kerja berat dengan sendfile
Beban kerja sys terberat yang dapat saya lakukan adalah menggunakan sendfile
, yang melakukan operasi penyalinan file pada ruang kernel: Menyalin file dengan cara yang waras, aman dan efisien
Jadi saya membayangkan bahwa in-kernel ini memcpy
akan menjadi operasi intensif CPU.
Pertama saya menginisialisasi file acak 10GiB besar dengan:
dd if=/dev/urandom of=sendfile.in.tmp bs=1K count=10M
Kemudian jalankan kodenya:
#define _GNU_SOURCE
#include <assert.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv) {
char *source_path, *dest_path;
int source, dest;
struct stat stat_source;
if (argc > 1) {
source_path = argv[1];
} else {
source_path = "sendfile.in.tmp";
}
if (argc > 2) {
dest_path = argv[2];
} else {
dest_path = "sendfile.out.tmp";
}
source = open(source_path, O_RDONLY);
assert(source != -1);
dest = open(dest_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
assert(dest != -1);
assert(fstat(source, &stat_source) != -1);
assert(sendfile(dest, source, 0, stat_source.st_size) != -1);
assert(close(source) != -1);
assert(close(dest) != -1);
return EXIT_SUCCESS;
}
GitHub hulu .
yang pada dasarnya memberi sebagian besar waktu sistem seperti yang diharapkan:
real 0m2.175s
user 0m0.001s
sys 0m1.476s
Saya juga ingin tahu apakah time
akan membedakan antara syscall dari proses yang berbeda, jadi saya mencoba:
time ./sendfile.out sendfile.in1.tmp sendfile.out1.tmp &
time ./sendfile.out sendfile.in2.tmp sendfile.out2.tmp &
Dan hasilnya adalah:
real 0m3.651s
user 0m0.000s
sys 0m1.516s
real 0m4.948s
user 0m0.000s
sys 0m1.562s
Waktu sistem hampir sama untuk keduanya seperti untuk satu proses tunggal, tetapi waktu dinding lebih besar karena proses bersaing kemungkinan untuk akses baca disk.
Jadi tampaknya itu memang menjelaskan proses yang memulai kerja kernel yang diberikan.
Kode sumber bash
Ketika Anda melakukannya hanya time <cmd>
di Ubuntu, ia menggunakan kata kunci Bash seperti yang dapat dilihat dari:
type time
yang keluaran:
time is a shell keyword
Jadi kami mengambil sumber dalam kode sumber Bash 4.19 untuk string keluaran:
git grep '"user\b'
yang mengarahkan kita ke fungsi execute_cmd.ctime_command
, yang menggunakan:
gettimeofday()
dan getrusage()
jika keduanya tersedia
times()
jika tidak
semuanya adalah panggilan sistem Linux dan fungsi POSIX .
Kode sumber GNU Coreutils
Jika kita menyebutnya sebagai:
/usr/bin/time
kemudian menggunakan implementasi GNU Coreutils.
Yang ini sedikit lebih rumit, tetapi sumber yang relevan tampaknya berada di resuse.c dan memang:
wait3
panggilan BSD non-POSIX jika tersedia
times
dan gettimeofday
sebaliknya