производительность — C ++ программа работает лучше, когда по трубопроводу

Я не занимался программированием в течение десятилетия. Я хотел вернуться к этому, поэтому я сделал эту маленькую бессмысленную программу на практике.
Самый простой способ описать то, что он делает, с помощью вывода моего кодового блока —help:

./ prng_bench —help

./prng_bench: usage: ./prng_bench $N $B [$T]

This program will generate an N digit base(B) random number until
all N digits are the same.

Once a repeating N digit base(B) number is found, the following statistics are displayed:
-Decimal value of all N digits.
-Time & number of tries taken to randomly find.

Optionally, this process is repeated T times.
When running multiple repititions, averages for all N digit base(B)
numbers are displayed at the end, as well as total time and total tries.

Моя «проблема» заключается в том, что когда проблема «проста», скажем трехзначное основание из 10 чисел, и я выполняю большое количество проходов, «общее время» меньше, когда передается в grep. то есть:

команда; команда | grep взяла:

./prng_bench 3 10 999999 ; ./prng_bench 3 10 999999|grep took

....
Pass# 999999: All 3 base(10) digits =  3 base(10).   Time:    0.00005 secs.   Tries: 23
It took 191.86701 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.
An average of 0.00019 secs & 99 tries was needed to find each one.

It took 159.32355 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.

Если я запускаю одну и ту же команду много раз, время grep всегда ОЧЕНЬ близко.
Сейчас я использую srand (1234) для тестирования. Код между моими вызовами clock_gettime () для запуска и остановки не включает никаких потоковых манипуляций, которые, очевидно, влияют на время. Я понимаю, что это бесполезное упражнение, но я хотел бы знать, почему оно так себя ведет.
Ниже сердце программы. Вот ссылка на полный исходный код в БД, если кто-то хочет скомпилировать и протестировать. https://www.dropbox.com/s/bczggar2pqzp9g1/prng_bench.cpp
clock_gettime () требует -lrt.

for (int pass_num=1; pass_num<=passes; pass_num++) {   //Executes $passes # of times.
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &temp_time);  //get time
start_time = timetodouble(temp_time);                //convert time to double, store as start_time
for(i=1, tries=0; i!=0; tries++) {    //loops until 'comparison for' fully completes. counts reps as 'tries'.  <------------
for (i=0; i<Ndigits; i++)      //Move forward through array.                                                              |
results[i]=(rand()%base);    //assign random num of base to element (digit).                                            |
/*for (i=0; i<Ndigits; i++)     //---Debug Lines---------------                                                           |
std::cout<<" "<<results[i];   //---a LOT of output.----------                                                           |
std::cout << "\n";              //---Comment/decoment to disable/enable.*/   //                                           |
for (i=Ndigits-1; i>0 && results[i]==results[0]; i--); //Move through array, != element breaks & i!=0, new digits drawn. -|
}                                                        //If all are equal i will be 0, nested for condition satisfied.  -|
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &temp_time);  //get time
draw_time = (timetodouble(temp_time) - start_time);  //convert time to dbl, subtract start_time, set draw_time to diff.
total_time += draw_time;    //add time for this pass to total.
total_tries += tries;       //add tries for this pass to total.
/*Formated output for each pass:
Pass# ---: All -- base(--) digits = -- base(10)   Time:   ----.---- secs.    Tries: ----- (LINE) */
std::cout<<"Pass# "<<std::setw(width_pass)<<pass_num<<": All "<<Ndigits<<" base("<<base<<") digits = "<<std::setw(width_base)<<results[0]<<" base(10).   Time: "<<std::setw(width_time)<<draw_time
<<" secs.   Tries: "<<tries<<"\n";
}
if(passes==1) return 0;        //No need for totals and averages of 1 pass.
/* It took ----.---- secs & ------ tries to find --- repeating -- digit base(--) numbers. (LINE)
An average of ---.---- secs & ---- tries was needed to find each one. (LINE)(LINE) */
std::cout<<"It took "<<total_time<<" secs & "<<total_tries<<" tries to find "<<passes<<" repeating "<<Ndigits<<" digit base("<<base<<") numbers.\n"<<"An average of "<<total_time/passes<<" secs & "<<total_tries/passes
<<" tries was needed to find each one. \n\n";
return 0;

3

Решение

Печать на экран очень медленная по сравнению с конвейером или без печати. Трубопровод к grep мешает вам сделать это.

5

Другие решения

Речь идет не о печати на экране; речь идет о выводе, являющемся терминалом (tty).

В соответствии с спецификация POSIX:

При открытии стандартный поток ошибок не полностью буферизуется;
стандартные входные и стандартные выходные потоки полностью буферизируются, если и
только если поток может быть определен как не относящийся к интерактивному
устройство.

Linux интерпретирует это, чтобы сделать FILE * (то есть, stdio) stdout буферизуется линией, когда вывод является tty (например, окном вашего терминала), и буферизуется блоком иначе (например, ваш канал).

Причина sync_with_stdio разница в том, что когда он включен, C ++ cout поток наследует это поведение. Когда вы установите его false, он больше не связан этим поведением и, таким образом, становится блочным буфером.

Буферизация блоков выполняется быстрее, поскольку позволяет избежать затрат на очистку буфера на каждой новой строке.

Вы можете дополнительно проверить это, отправив cat вместо grep, Разница в самой трубе, а не в экране как таковом.

2

Спасибо Коллин & Nemo. Я был уверен, что потому что я не звонил std :: cout между началом работы & остановите время, чтобы это не имело эффекта. Не так. Я думаю, что это связано с оптимизацией, которую компилятор выполняет даже с -O0 или «значениями по умолчанию».

Что я думаю, что происходит …? Я думаю, что, как предположил Коллин, компилятор пытается проявить смекалку, когда пишет в TTY. И, как указывал Немо, cout наследует линейные буферизованные свойства stdio.

Я могу уменьшить эффект, но не устранить, используя:

std::cout.sync_with_stdio(false);

Из моего ограниченного прочтения, он должен быть вызван перед выполнением любых операций вывода.
Вот источник для версии no_sync: https://www.dropbox.com/s/wugo7hxvu9ao8i3/prng_bench_no_sync.cpp

./ no_sync 3 10 999999; ./ no_sync 3 10 999999 | grep взял

Скомпилировано с -O0

999999: All 3 base(10) digits =  3 base(10)  Time:    0.00004 secs.  Tries: 23
It took 166.30801 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.
An average of 0.00017 secs & 99 tries was needed to find each one.

It took 163.72914 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.

Соблюдается с -O3

999999: All 3 base(10) digits =  3 base(10)  Time:    0.00003 secs.  Tries: 23
It took 143.23234 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.
An average of 0.00014 secs & 99 tries was needed to find each one.

It took 140.36195 secs & 99947208 tries to find 999999 repeating 3 digit base(10) numbers.

Указание не синхронизировать со stdio изменило мою дельту между каналом и каналом от более 30 секунд до менее 3. Смотреть оригинальный вопрос для оригинальной дельты это было ~ 191 — ~ 160

Для дальнейшего тестирования я создал другую версию, используя структуру для хранения статистики о каждом проходе. Этот метод выводит все данные после завершения всех проходов. Я хочу подчеркнуть, что это, вероятно, ужасная идея. Я разрешаю аргумент командной строки для определения размера динамически размещаемого массива структур, содержащих int, double и unsigned long. Я даже не могу запустить эту версию с 999 999 проходами. Я получаю ошибку сегментации. https://www.dropbox.com/s/785ntsm622q9mwd/prng_bench_struct.cpp

./ struct_prng 3 10 99999; ./ struct_prng 3 10 99999 | grep взял

Pass# 99999: All 3 base(10) digits =  6 base(10)  Time:    0.00025 secs.  Tries: 193
It took 13.10071 secs & 9970298 tries to find 99999 repeating 3 digit base(10) numbers.
An average of 0.00013 secs & 99 tries was needed to find each one.

It took 13.12466 secs & 9970298 tries to find 99999 repeating 3 digit base(10) numbers.

Из этого я узнал, что вы не можете рассчитывать на порядок, в котором вы кодировали вещи, в порядке, в котором они выполняются. В будущих программах я, вероятно, реализую getopt вместо того, чтобы писать собственную функцию parse_args. Это позволило бы мне исключить посторонний вывод в циклах с высоким повторением, потребовав от пользователей использования ключа -v, если они хотят его видеть.

Я надеюсь, что дальнейшее тестирование окажется полезным для всех, кто интересуется трубопроводами и выводом в циклах. Все результаты, которые я опубликовал, были получены на RasPi. Все исходные коды связаны с GPL, просто потому, что это первая лицензия, о которой я мог подумать … У меня действительно нет нужды в самоусилении положений GPL с авторским левом, я просто хотел прояснить, что она бесплатная, но без гарантия или ответственность.

Обратите внимание, что у всех связанных источников есть комментарий к srand (…), поэтому все ваши псевдослучайные результаты будут точно такими же.

0
А ты уже прошел курс программирования? Супер скидка!
Прокачать скилл $$$
×