Minimum çalıştırılabilir POSIX C örnekleri
İşleri daha somut hale getirmek için time
, bazı minimum C test programlarıyla ilgili birkaç uç durumu örneklendirmek istiyorum .
Tüm programlar aşağıdakilerle derlenebilir ve çalıştırılabilir:
gcc -ggdb3 -o main.out -pthread -std=c99 -pedantic-errors -Wall -Wextra main.c
time ./main.out
ve Ubuntu 18.10, GCC 8.2.0, glibc 2.28, Linux çekirdeği 4.18, ThinkPad P51 dizüstü bilgisayar, Intel Core i7-7820HQ CPU (4 çekirdek / 8 iş parçacığı), 2x Samsung M471A2K43BB1-CRC RAM (2x 16GiB) ile test edilmiştir.
uyku
Sigara meşgul uyku saymak ya gelmez user
ya sys
sadece real
.
Örneğin, bir saniyeliğine uyuyan bir program:
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <unistd.h>
int main(void) {
sleep(1);
return EXIT_SUCCESS;
}
GitHub akış yukarı .
gibi bir şey çıktılar:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
Aynı şey ES'nin kullanıma sunulmasıyla engellenen programlar için de geçerlidir.
Örneğin, aşağıdaki program kullanıcının bir karakter girmesini ve enter tuşuna basmasını bekler:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("%c\n", getchar());
return EXIT_SUCCESS;
}
GitHub akış yukarı .
Ve yaklaşık bir saniye beklerseniz, uyku örneğinde olduğu gibi çıktı:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
Bu nedenle time
CPU ve IO bağlı programları ayırt etmenize yardımcı olabilir: "CPU bağlı" ve "I / O bağlı" terimleri ne anlama gelir?
Birden çok iş parçacığı
Aşağıdaki örnek, niters
iş nthreads
parçacığı üzerinde tamamen CPU'ya bağlı yararsız yinelemeler yapar :
#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 akış yukarı + çizim kodu .
Sonra 8 hiper-iplik işlemcimdeki sabit 10 ^ 10 yinelemeleri için iş parçacığı sayısının bir fonksiyonu olarak wall, user ve sys'yi çiziyoruz:
Verileri çizin .
Grafikten şunu görüyoruz:
CPU yoğunluklu tek çekirdekli bir uygulama için duvar ve kullanıcı hemen hemen aynıdır
2 çekirdek için, kullanıcı yaklaşık 2x duvardır, yani kullanıcı süresi bütün dişlerde sayılır.
kullanıcı temelde iki katına çıktı ve duvar aynı kaldı.
Bu, bilgisayarımdaki hyperthreads sayımla eşleşen 8 iş parçacığına kadar devam eder.
8'den sonra, duvar da artmaya başlar, çünkü belirli bir sürede daha fazla iş koyacak ekstra CPU'larımız yok!
Bu noktada yaylalar oranı.
Hafıza bağlanmış olsaydı tarafta gösterildiği gibi hafıza erişimler bir darboğaz olacaktır çünkü, o zaman çok daha erken az çekirdekli performansında bir düşüş alacağı: iş tamamen CPU bağımlı olduğundan bu grafik yalnızca bu kadar açık ve basit olduğunu Not Ne CPU bağlı "ve" I / O bağlı "terimleri ne anlama geliyor?
Sys ağır iş ile sendfile
Gelebildiğim en ağır sys iş yükü sendfile
, çekirdek alanında bir dosya kopyalama işlemi yapan: Dosyayı aklı başında, güvenli ve verimli bir şekilde kopyalamaktı.
Bu yüzden, bu çekirdek içi memcpy
CPU yoğunluklu bir işlem olacağını hayal ettim .
İlk önce büyük bir 10GiB rastgele dosya ile başlat:
dd if=/dev/urandom of=sendfile.in.tmp bs=1K count=10M
Sonra kodu çalıştırın:
#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 akış yukarı .
temelde çoğunlukla beklendiği gibi sistem zamanı verir:
real 0m2.175s
user 0m0.001s
sys 0m1.476s
Ayrıca time
farklı süreçlerin sistem çağrılarını ayırt edip etmeyeceğini merak ettim, bu yüzden denedim:
time ./sendfile.out sendfile.in1.tmp sendfile.out1.tmp &
time ./sendfile.out sendfile.in2.tmp sendfile.out2.tmp &
Ve sonuç şuydu:
real 0m3.651s
user 0m0.000s
sys 0m1.516s
real 0m4.948s
user 0m0.000s
sys 0m1.562s
Sys süresi her ikisi için de tek bir işlemle aynıdır, ancak işlemler disk okuma erişimi için rekabet edebileceğinden duvar süresi daha büyüktür.
Öyle görünüyor ki aslında hangi sürecin belirli bir çekirdek çalışmasını başlattığını açıklıyor.
Bash kaynak kodu
Yalnızca time <cmd>
Ubuntu'da yaptığınızda, Bash anahtar sözcüğünü şu şekilde görülebileceği gibi kullanır:
type time
hangi çıktılar:
time is a shell keyword
Bu yüzden çıkış dizesi için Bash 4.19 kaynak kodunda grep kaynağı:
git grep '"user\b'
hangi potansiyel müşteriler bize execute_cmd.c fonksiyonu time_command
kullanır:
gettimeofday()
ve getrusage()
eğer ikisi de mevcutsa
times()
aksi takdirde
bunların hepsi Linux sistem çağrıları ve POSIX işlevleridir .
GNU Coreutils kaynak kodu
Biz şöyle diyoruz:
/usr/bin/time
daha sonra GNU Coreutils uygulamasını kullanır.
Bu biraz daha karmaşık, ancak ilgili kaynak yeniden kullanımda gibi görünüyor. C ve öyle:
wait3
varsa POSIX olmayan bir BSD çağrısı
times
ve gettimeofday
başka türlü