Ví dụ POSIX C có thể chạy tối thiểu
Để làm cho mọi thứ cụ thể hơn, tôi muốn làm gương một vài trường hợp cực đoan time
với một số chương trình thử nghiệm C tối thiểu.
Tất cả các chương trình có thể được biên dịch và chạy với:
gcc -ggdb3 -o main.out -pthread -std=c99 -pedantic-errors -Wall -Wextra main.c
time ./main.out
và đã được thử nghiệm trong Ubuntu 18.10, GCC 8.2.0, glibc 2.28, Linux kernel 4.18, máy tính xách tay ThinkPad P51, CPU Intel Core i7-7820HQ (4 lõi / 8 luồng), 2 lần RAM Samsung M471A2K43BB1-CRC (2x 16GiB).
ngủ
Giấc ngủ không bận rộn không được tính vào một trong hai user
hoặc sys
, chỉ real
.
Ví dụ, một chương trình ngủ trong một giây:
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <unistd.h>
int main(void) {
sleep(1);
return EXIT_SUCCESS;
}
GitHub ngược dòng .
xuất ra một cái gì đó như:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
Điều tương tự giữ cho các chương trình bị chặn trên IO trở nên có sẵn.
Ví dụ, chương trình sau đây chờ người dùng nhập một ký tự và nhấn enter:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("%c\n", getchar());
return EXIT_SUCCESS;
}
GitHub ngược dòng .
Và nếu bạn đợi khoảng một giây, nó sẽ xuất ra giống như ví dụ về giấc ngủ giống như:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
Vì lý do này time
có thể giúp bạn phân biệt giữa các chương trình bị ràng buộc CPU và IO: Các thuật ngữ "ràng buộc CPU" và "ràng buộc I / O" nghĩa là gì?
Nhiều chủ đề
Ví dụ sau đây niters
lặp đi lặp lại các công việc hoàn toàn vô dụng của CPU trên các nthreads
luồng:
#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 ngược dòng + mã cốt truyện .
Sau đó, chúng ta vẽ sơ đồ tường, người dùng và hệ thống như là một hàm số lượng luồng cho một lần lặp 10 ^ 10 cố định trên CPU 8 siêu phân luồng của tôi:
Lô dữ liệu .
Từ biểu đồ, chúng ta thấy rằng:
Đối với một ứng dụng lõi đơn chuyên sâu về CPU, tường và người dùng giống nhau
đối với 2 lõi, người dùng có khoảng 2 lần tường, có nghĩa là thời gian của người dùng được tính trên tất cả các luồng.
người dùng về cơ bản tăng gấp đôi, và trong khi tường vẫn như cũ.
điều này tiếp tục lên đến 8 luồng, phù hợp với số lượng siêu phân luồng trong máy tính của tôi.
Sau 8, tường cũng bắt đầu tăng, vì chúng tôi không có thêm CPU để đặt thêm công việc trong một khoảng thời gian nhất định!
Các cao nguyên tỷ lệ tại điểm này.
Lưu ý rằng biểu đồ này chỉ rõ ràng và đơn giản vì công việc hoàn toàn bị ràng buộc bởi CPU: nếu nó bị ràng buộc về bộ nhớ, thì chúng ta sẽ bị giảm hiệu năng sớm hơn với ít lõi hơn vì các truy cập bộ nhớ sẽ bị nghẽn cổ chai như trong Điều gì các thuật ngữ "ràng buộc CPU" và "ràng buộc I / O" có nghĩa là gì?
Sys làm việc nặng với sendfile
Khối lượng công việc sys nặng nhất tôi có thể sử dụng là sử dụng sendfile
thao tác sao chép tệp trên không gian kernel: Sao chép tệp theo cách lành mạnh, an toàn và hiệu quả
Vì vậy, tôi tưởng tượng rằng trong nhân memcpy
này sẽ là một hoạt động chuyên sâu của CPU.
Đầu tiên tôi khởi tạo một tệp ngẫu nhiên 10GiB lớn với:
dd if=/dev/urandom of=sendfile.in.tmp bs=1K count=10M
Sau đó chạy mã:
#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 ngược dòng .
về cơ bản là thời gian hệ thống như mong đợi:
real 0m2.175s
user 0m0.001s
sys 0m1.476s
Tôi cũng tò mò muốn xem liệu time
có phân biệt được các tòa nhà của các quy trình khác nhau không, vì vậy tôi đã thử:
time ./sendfile.out sendfile.in1.tmp sendfile.out1.tmp &
time ./sendfile.out sendfile.in2.tmp sendfile.out2.tmp &
Và kết quả là:
real 0m3.651s
user 0m0.000s
sys 0m1.516s
real 0m4.948s
user 0m0.000s
sys 0m1.562s
Thời gian sys gần giống nhau cho cả hai quy trình, nhưng thời gian treo tường lớn hơn vì các quy trình đang cạnh tranh để truy cập đọc đĩa.
Vì vậy, có vẻ như trên thực tế nó giải thích cho quá trình nào đã bắt đầu một công việc kernel đã cho.
Mã nguồn Bash
Khi bạn chỉ làm time <cmd>
trên Ubuntu, nó sử dụng từ khóa Bash như có thể thấy từ:
type time
đầu ra nào:
time is a shell keyword
Vì vậy, chúng tôi grep nguồn trong mã nguồn Bash 4.19 cho chuỗi đầu ra:
git grep '"user\b'
dẫn chúng ta đến hàm exec_cmd.ctime_command
, sử dụng:
gettimeofday()
và getrusage()
nếu cả hai đều có sẵn
times()
nếu không thì
tất cả đều là các cuộc gọi hệ thống Linux và các chức năng POSIX .
Mã nguồn GNU Coreutils
Nếu chúng ta gọi nó là:
/usr/bin/time
sau đó nó sử dụng triển khai GNU Coreutils.
Cái này phức tạp hơn một chút, nhưng nguồn có liên quan dường như ở resuse.c và nó có:
- một
wait3
cuộc gọi BSD không phải POSIX nếu có sẵn
times
và gettimeofday
nếu không