최소 실행 가능 POSIX C 예
좀 더 구체적으로 설명하기 위해 time
최소한의 C 테스트 프로그램 을 예로 들겠습니다 .
모든 프로그램은 다음을 사용하여 컴파일하고 실행할 수 있습니다.
gcc -ggdb3 -o main.out -pthread -std=c99 -pedantic-errors -Wall -Wextra main.c
time ./main.out
Ubuntu 18.10, GCC 8.2.0, glibc 2.28, Linux 커널 4.18, ThinkPad P51 랩톱, Intel Core i7-7820HQ CPU (4 코어 / 8 스레드), 2x Samsung M471A2K43BB1-CRC RAM (2x 16GiB)에서 테스트되었습니다.
자다
사용 중이 아닌 절전 모드는 user
또는 중 하나 sys
에 만 계산되지 않습니다 real
.
예를 들어, 잠 들어있는 프로그램 :
#define _XOPEN_SOURCE 700
#include <stdlib.h>
#include <unistd.h>
int main(void) {
sleep(1);
return EXIT_SUCCESS;
}
GitHub의 상류 .
다음과 같은 결과가 출력됩니다.
real 0m1.003s
user 0m0.001s
sys 0m0.003s
IO에서 차단 된 프로그램도 사용할 수 있습니다.
예를 들어, 다음 프로그램은 사용자가 문자를 입력 할 때까지 기다린 후 enter를 누릅니다.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("%c\n", getchar());
return EXIT_SUCCESS;
}
GitHub의 상류 .
그리고 약 1 초 정도 기다리면 수면 예제와 같이 다음과 같이 출력됩니다.
real 0m1.003s
user 0m0.001s
sys 0m0.003s
이러한 이유로 time
CPU와 IO 바운드 프로그램을 구별 할 수 있습니다. "CPU 바운드"및 "I / O 바운드"라는 용어는 무엇을 의미합니까?
여러 스레드
다음 예제는 스레드 niters
에서 쓸모없는 순수한 CPU 바운드 작업의 반복을 수행 nthreads
합니다.
#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 업스트림 + 플롯 코드 .
그런 다음 8 하이퍼 스레드 CPU에서 고정 10 ^ 10 반복에 대한 스레드 수의 함수로 wall, user 및 sys를 플로팅합니다.
데이터를 플롯합니다 .
그래프에서 우리는 다음을 볼 수 있습니다.
CPU를 많이 사용하는 단일 코어 애플리케이션의 경우 월과 사용자는 거의 같습니다
2 코어의 경우 사용자는 벽의 약 2 배이므로 모든 스레드에서 사용자 시간이 계산됩니다.
사용자는 기본적으로 두 배가되었고 벽은 동일하게 유지되었습니다.
이것은 최대 8 개의 스레드까지 계속되며, 이는 컴퓨터의 여러 하이퍼 스레드와 일치합니다.
8 시간이 지나면 주어진 시간에 더 많은 작업을 할 CPU가 없기 때문에 벽도 증가하기 시작합니다!
이 시점의 비율은 정점입니다.
이 메모리가 결합 된 경우에서와 같이 메모리 액세스가 병목이 될 것이기 때문에, 우리는 훨씬 이전에 적은 코어의 성능 하락을 얻을 것입니다 : 작업이 순수하게 CPU 바인딩 때문에이 그래프는 매우 명확하고 간단합니다 무엇 "CPU bound"및 "I / O bound"라는 용어는 무엇을 의미합니까?
Sys의 중작 업 sendfile
가장 무거운 SYS 내가를 사용하는 것이었다 가지고 올 수있는 워크로드 sendfile
파일 복사 커널 공간에서 작업을 수행하는 : 복사 온전한에서 파일을 안전하고 효율적인 방법
그래서이 커널 내부 memcpy
가 CPU 집약적 인 작업이 될 것이라고 상상했습니다 .
먼저 다음과 같이 큰 10GiB 임의 파일을 초기화합니다.
dd if=/dev/urandom of=sendfile.in.tmp bs=1K count=10M
그런 다음 코드를 실행하십시오.
#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의 상류 .
기본적으로 예상대로 시스템 시간을 제공합니다.
real 0m2.175s
user 0m0.001s
sys 0m1.476s
또한 time
다른 프로세스의 syscall을 구분할 수 있는지 궁금해서 시도했습니다.
time ./sendfile.out sendfile.in1.tmp sendfile.out1.tmp &
time ./sendfile.out sendfile.in2.tmp sendfile.out2.tmp &
결과는 다음과 같습니다.
real 0m3.651s
user 0m0.000s
sys 0m1.516s
real 0m4.948s
user 0m0.000s
sys 0m1.562s
sys 시간은 단일 프로세스의 경우와 거의 동일하지만 프로세스가 디스크 읽기 액세스를 위해 경쟁하고 있기 때문에 소요 시간이 더 큽니다.
따라서 실제로 어떤 프로세스가 주어진 커널 작업을 시작했는지 설명하는 것 같습니다.
배쉬 소스 코드
time <cmd>
우분투에서 할 때 다음 과 같이 Bash 키워드를 사용합니다.
type time
어떤 출력 :
time is a shell keyword
따라서 출력 문자열에 대한 Bash 4.19 소스 코드에서 소스를 grep하십시오.
git grep '"user\b'
이를 통해 우리는 execute_cmd.c 함수 time_command
를 사용합니다.
gettimeofday()
그리고 getrusage()
두 경우 사용할 수 있습니다
times()
그렇지 않으면
이것들은 모두 리눅스 시스템 호출 과 POSIX 함수 입니다.
GNU Coreutils 소스 코드
우리가 다음과 같이 부르면 :
/usr/bin/time
그런 다음 GNU Coreutils 구현을 사용합니다.
이것은 조금 더 복잡하지만 관련 소스는 resuse.c 에있는 것 같습니다 .
- POSIX가 아닌 BSD
wait3
호출
times
와 gettimeofday
달리