IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.
Avatar
Nositelka Změny:24.7.2020 12:19

Na stránce https://www.sallyx.org/sally/c/c26.php se píše o tom, že "používáním funkce puts se zoptimalizuje program", což zní logicky, protože puts textový řetězec rovnou vypíše, zatímco printf si ho musí nejdříve vytvořit. Nicméně v jednom programu jsem si všimla toho, že verze s puts byla pomalejší než s printf.

Zkusil jsem: Poté jsem si vytvořila jednoduchý testovací program, který jenom pořád dokola vypisoval "abc\n" a skutečně je printf rychlejší než puts i v tomto případě. Něco málo jsem si hledala na internetu, ale jinak jsem už nic na tohle téma nenašla.

Chci docílit: Tak jak je to tedy správně? Ten článek je už poměrně starý, takže je možné, že dnes je již printf natolik optimalizovaná, že se spíše vyplatí používat jednu funkci na všechno a puts slouží jenom pro okrasu. Otázka se samozřejmě týká i podobných funkcí, jako je putc, fputs nebo gets (oproti scanf) :-), ale předpokládám, že u nich je to stejné... ;-)

Odpovědět
24.7.2020 12:19
j.k.j
Avatar
Martin Dráb
Tvůrce
Avatar
Odpovídá na Nositelka Změny
Martin Dráb:25.7.2020 0:42

Jakým způsobem jsi měřila? Jak vypadal tvůj testovací program (nebo oba dva)? Klidně sem dej binárku, ať se můžeme podívat, co z toho překladač vytvořil.

Co jsem se v rychlosti dočetl, někteé překladače se mohou samy rozhodnout, že nahradí výskyty printf za puts, pokud je to ekvivalentní operace. Podobné věci dělají i při jiných příležitostech (například dokáží for cykly nahrazovat voláním memset či memcpy, pokud usoudí, že se jedná o zapisování jedné hodnoty do bloku paměti či o jeho kopírování).

Také záleží, jak moc složitý je ten formátovací řetězec předávaný do printf. Pokud neobsahuje žádné formátovací znaky (%), volání printf by mohlo mít rychlost srovnatelnou s puts.

Tady by mohlo být zajímavé přeložit testovací programy v režimu Debug, takže překladač nebude provádět žádné optimalizace. Takový výsledek pro praxi neřekne vůbec nic, ale můžeš si pak takové programy ručně optimalizovat a dívat se, jak velký efekt takové optimalizace mají.

Co se týče dalších funkcí, jako třeba fputs či fprintf, záleží mj. i na vlastnostech streamu, do kterého zapisuješ. Např. zda si má sám řešit řádkování (Windows) či zda se jedná prostě o binární stream... nebo zda-li dochází k bufferování dat (tzn. volání výpisu se neprojeví hned, ale až poté, co se standardní knihovna rozhodne, že apíše nabufferovaná data). Myslím, že stderr (standardní chybový výstup) obvykle není bufferovaný (data se objeví hned), kdežto stdout (standartní výstup) být může.

Nahoru Odpovědět
25.7.2020 0:42
2 + 2 = 5 for extremely large values of 2
Avatar
Nositelka Změny:25.7.2020 10:49

Bylo to něco takového:

#include <stdio.h>
#include <limits.h>

int main()
{
    for (int f = 0; f < SHRT_MAX; f += 1)
    {
          printf("abc\n");  /* puts("abc"); */
    }
}

Ještě jsem si to párkrát spustila a pokaždé to běželo jinak rychle, ale i tak to vypadá, že printf běžel o trochu rychleji. Ale pokud to vypisovalo dvakrát \n (

printf("abc\n\n"); puts("abc\n");

), tak obě funkce běžely takřka stejně rychle. Ještě si s tím trochu pohraju. Jo a překládám to pouze jako debug, optimalizace u takovýchle programů nepoužívám...

Nahoru Odpovědět
25.7.2020 10:49
j.k.j
Avatar
DarkCoder
Člen
Avatar
Odpovídá na Nositelka Změny
DarkCoder:26.7.2020 7:20

Vysvětlení celého tohoto chování je daleko komplikovanější než se na první pohled zdá. Je zde mnoho aspektů, které jsou před uživatelem potažmo programátorem skryty.

Aby bylo srovnání funkcí puts() vs printf() alespoň trochu konkrétní, je třeba, aby testovací program měl patřičnou podobu vracející adekvátní výsledky. Výsledky budou a jsou si velmi podobné, rozdíly minimální (ale jsou).

Začnu tím, jak by mohl vypadat testovací program.

#include <stdio.h>
#include <time.h>

#define N 100000

int main(void) {
        clock_t start, end;

        start = clock();
        for (int i = 0; i < N; i++) {
                //puts("abc");
                printf("abc\n");
        }
        end = clock();

        printf("%ld\n", (long)(end - start));

        getchar();

        return(0);
}

Program umožňuje zadat proměnlivý počet opakování a na konci vypíše počet tiků systémových hodin, které vyjadřují čas, po jakou dobu se prováděl výpis zadaného řetězce pomocí funkcí puts() potažmo printf(). Výsledkem je tedy konkrétní něco říkající hodnota.

To že výsledek je pokaždé odlišný je pochopitelné a dáno mnoha aspekty o kterých jsem na začátku psal.

Tou první věcí je optimalizace, kde skutečně po analýze může docházet k záměně jedné funkce za jinou, popřípadě dokonce vynechání některých nepotřebných částí v dané funkci. Mnoho optimalizací se provádí na základě analýzy formátovacího řetězce. Escape sekvence jsou stále znakové konstanty, které jsou tvořeny více znaky. Mohou tedy být součástí jak funkce puts() tak funkce printf(). Pokud jej formátovací řetězec nemá, může dojít k vyjmutí testování na tyto hodnoty. Pokud u funkce printf() se při analýze zjistí, že funkce neobsahuje specifikace formátu, může být uvnitř funkce definováno interní makro, které zajistí, že celý rozhodovací blok pro specifikace formátu bude vynechán. A to je pouze interní optimalizace funkce nikoli překladače. Tedy i bez stanovení optimalizace pro překladač může docházet k navýšení výkonu funkce. Rychlost funkce printf() se tak v některých případech může velmi blížit rychlosti funkce puts(). Nicméně pořád jsou tam drobná nastavení nutná pro univerzální chování funkce printf(), která zpomalují výpis na obrazovku. Tyto interní manipulace jsou skryty, některé z nich lze vypnout tím, že se zakáží optimalizace v nastavení pro překladač.

Ta druhá věc, která způsobuje odlišné chování, je ještě záhadnější. A je spojena s vícevláknovým programováním. Výsledky jsou odlišné právě díky tomuto aspektu. Výsledky budou různé spustíme-li program z IDE, spustíme-li program jako exe soubor a dokonce spustíme-li program samostatně se spuštěným IDE na pozadí a mnoho dalších.

Toto celé je spojeno s plánováním procesů (scheduling), zejména v krátkodobém plánování (short-term), u něhož dochází k výběru procesu kterému bude přidělen procesor. Jelikož procesů je obvykle více než procesorů, dochází k přepínání kontextu který se řídí mnoha pravidly (spravedlnost, efektivita, čas odezvy, průchodnost, atd.).

Jak je vidět, je toho dost co ovlivňuje výsledek programu. Každopádně platí, že pro konkrétní případ by měla být použita vhodná funkce. Funkce printf() je univerzální, ale nenahrazuje specializované funkce, i když z výsledků některých operací to tak vypadá. Tyto funkce opravdu neslouží pro okrasu, ale mají svá pevná opodstatnění.

Ještě jedna drobnost na závěr. Informace, které si se na dané stránce dočetla, jsou vesměs platná stále. Jen funkce gets() je vyřazena již od C11 a je nahrazena z důvodu bezpečnosti funkcí fgets(), která umožňuje stanovit limit počtu načtených znaků.

Nahoru Odpovědět
26.7.2020 7:20
"I ta nejlepší poučka postrádá na významu, není-li patřičně předána." - DarkCoder
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 4 zpráv z 4.