न्यूनतम रन योग्य POSIX C उदाहरण
चीजों को और अधिक ठोस बनाने के लिए, मैं time
कुछ न्यूनतम सी परीक्षण कार्यक्रमों के साथ कुछ चरम मामलों को उदाहरण देना चाहता हूं ।
सभी कार्यक्रमों को संकलित किया जा सकता है और उनके साथ चलाया जा सकता है:
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, लिनक्स कर्नेल 4.18, थिंकपैड P51 लैपटॉप, इंटेल कोर i7-7820HQ CPU (4 कोर / 8 धागे), 2x सैमसंग M471.2K43BB1-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;
}
गिटहब ऊपर ।
आउटपुट कुछ इस तरह है:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
उपलब्ध होने वाले IO पर अवरुद्ध कार्यक्रमों के लिए भी यही स्थिति है।
उदाहरण के लिए, निम्न प्रोग्राम उपयोगकर्ता को एक चरित्र दर्ज करने और एंटर प्रेस करने के लिए इंतजार करता है:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("%c\n", getchar());
return EXIT_SUCCESS;
}
गिटहब ऊपर ।
और यदि आप लगभग एक सेकंड का इंतजार करते हैं, तो यह नींद के उदाहरण की तरह ही आउटपुट करता है:
real 0m1.003s
user 0m0.001s
sys 0m0.003s
इस कारण time
से आप सीपीयू और आईओ बाध्य कार्यक्रमों के बीच अंतर करने में मदद कर सकते हैं: "सीपीयू बाध्य" और "आई / ओ बाध्य" शब्द का क्या अर्थ है?
कई सूत्र
निम्न उदाहरण थ्रेड niters
पर बेकार शुद्ध सीपीयू-बाउंड कार्य का पुनरावृत्तियों करता है 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 हाइपरथ्रेड सीपीयू पर एक निश्चित 10 ^ 10 पुनरावृत्तियों के लिए थ्रेड्स की संख्या के एक फ़ंक्शन के रूप में दीवार, उपयोगकर्ता और sys प्लॉट करते हैं:
प्लॉट डेटा ।
ग्राफ से, हम देखते हैं कि:
सीपीयू इंटेंसिव सिंगल कोर एप्लिकेशन के लिए, दीवार और उपयोगकर्ता लगभग एक ही हैं
2 कोर के लिए, उपयोगकर्ता 2x दीवार के बारे में है, जिसका अर्थ है कि उपयोगकर्ता का समय सभी थ्रेड्स में गिना जाता है।
उपयोगकर्ता मूल रूप से दोगुना हो गया, और जबकि दीवार एक ही रही।
यह 8 थ्रेड तक जारी रहता है, जो मेरे कंप्यूटर में मेरे हाइपरथ्रेड्स की संख्या से मेल खाता है।
8 के बाद, दीवार के रूप में अच्छी तरह से वृद्धि शुरू होती है, क्योंकि हमारे पास किसी भी अतिरिक्त सीपीयू को एक निश्चित समय में अधिक काम नहीं करना पड़ता है!
इस बिंदु पर अनुपात पठार।
ध्यान दें कि यह ग्राफ केवल इतना स्पष्ट और सरल है क्योंकि काम विशुद्ध रूप से सीपीयू बाध्य है: अगर यह स्मृति बाध्य कर रहे थे, तो हम बहुत पहले ही कम कोर के साथ प्रदर्शन में गिरावट मिलेगा क्योंकि स्मृति पहुंच अड़चन के रूप में दिखाया गया होगा क्या क्या "सीपीयू बाउंड" और "आई / ओ बाउंड" शब्द का अर्थ है?
के साथ भारी काम करता है sendfile
सबसे भारी एसईएस कार्यभार जिसके साथ मैं आ सकता था, का उपयोग करना था sendfile
, जो कर्नेल स्थान पर फ़ाइल कॉपी ऑपरेशन करता है: किसी फ़ाइल को किसी सुरक्षित, सुरक्षित और कुशल तरीके से कॉपी करें
तो मैंने सोचा कि यह इन-कर्नेल memcpy
एक सीपीयू गहन ऑपरेशन होगा।
पहले मैं एक बड़ी 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;
}
गिटहब ऊपर ।
जो मूल रूप से उम्मीद के मुताबिक ज्यादातर सिस्टम समय देता है:
real 0m2.175s
user 0m0.001s
sys 0m1.476s
मैं यह देखने के लिए भी उत्सुक था कि time
क्या विभिन्न प्रक्रियाओं के syscalls के बीच अंतर होगा, इसलिए मैंने कोशिश की:
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>
उबंटू पर करते हैं , तो यह बैश कीवर्ड का उपयोग करता है जैसा कि देखा जा सकता है:
type time
कौन से आउटपुट:
time is a shell keyword
इसलिए हम आउटपुट स्ट्रिंग के लिए बैश 4.19 सोर्स कोड में grep स्रोत:
git grep '"user\b'
जो हमें execute_cmd.c फ़ंक्शन की ओर ले जाता है time_command
, जो उपयोग करता है:
gettimeofday()
और getrusage()
अगर दोनों उपलब्ध हैं
times()
अन्यथा
जिनमें से सभी Linux सिस्टम कॉल और POSIX फ़ंक्शन हैं ।
GNU Coreutils स्रोत कोड
अगर हम इसे कहते हैं:
/usr/bin/time
इसके बाद यह GNU Coreutils कार्यान्वयन का उपयोग करता है।
यह थोड़ा अधिक जटिल है, लेकिन प्रासंगिक स्रोत resuse.c पर लगता है और यह करता है:
wait3
यदि उपलब्ध हो तो एक गैर-पॉसिक्स बीएसडी कॉल
times
और gettimeofday
अन्यथा