🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: pl.comp.lang.c
Article #310010

Optymalizacja kodu

#310010
From: M.M.
Date: Tue, 24 Dec 2019 19:25
360 lines
12534 bytes
Czasami potrzebuję całkowite liczby pseudolosowe z małego 
przedziału, przy czym losowość tychże liczb nie musi być
wysokiej jakości, ale za to generowanie musi być bardzo 
szybkie. Standardowy sposób na uzyskanie takich liczb jest 
zapewne wszystkim czytelnikom tej grupy znany, ale przypomnijmy:

random() % (MAX-MIN+1) + MIN;

Od razu widać zabójczą dla wydajności operację modulo :/ Co
można na to poradzić? Mój pierwszy pomysł był taki, aby zgrać
do tablicy np. milion liczb, a potem z tej tablicy te liczby w
kółko podawać. Cóż może być wydajniejszego, niż podawanie kolejnych
liczb z pamięci RAM? Niestety tablica o rozmiarze miliona elementów
to lekkie marnotrawienie pamięci RAM i nie wiadomo jak procesor
takie tablice będzie buforował w większych programach. Zastosowałem
więc rozwiązanie z użyciem dwóch buforów:

buff1[SIZE1];
buff2[SIZE2];

( albo jednego łączonego buff[SIZE1+SIZE2] )

przy czym SIZE1 i SIZE2 to są liczby pierwsze z przedziału 
około <100,10000>. Można podawać na przemian liczby z bufora1 i 
bufora2, cykl takiego 'generatora' wyniesie size1*size2 (po
wygenerowaniu tylu liczb można bufory zapełnić nowymi liczbami, to
niewiele spowolni generator, bo jest mało generowania w stosunku
do podawanych liczb).

 
No więc teoria jest prosta, pora zabrać się kodowanie. Jednak są niespodzianki.
Generator std::mt19937_64 wygenerował 4mld liczb z przedziału od 11 do 38 w
około 27 sekund. Natomiast ta sztuczka z dwoma tablicami potrzebowała aż
35 sekund! Czyli zamiast przyspieszenia uzyskałem spowolnienie. Implementacja
tej wersji jest w załączonym kodzie na dole w klasie RndBuff1. Nie poddawałem
się, zamiast statycznej tablicy użyłem dynamicznie przydzielonej przez
new - myślę sobie, a może kompilator lepiej da radę z new. Okazało się, że jest
jeszcze gorzej, czas wzrósł do 38s - to jest w klasie RndBuff2. Dopiero jak
dodałem wskaźniki i kilka usprawnień instrukcji warunkowych, czas spadł do...
zaledwie 4 sekund - to jest w klasie RndBuff3. Dla pewności sprawdziłem jeszcze
kombinację z tablicą (zamiast new) i wskaźnikami. Czas z powrotem wzrósł do
11 sekund - kod w RndBuff4. 

Po 34 pomiarach czasu, wyniki wyglądają tak:
seed [911368664458670987] nr.:34
raw mt start   105226567093 872
RndBuff1 start 105232396785 1210
RndBuff2 start 105232396785 1267
RndBuff3 start 105232396785 127
RndBuff4 start 105232396785 397

Widać że czas pomiędzy najlepszą i najgorszą wersją różni się aż 10 razy, a
kod ciągle bardzo podobny i oczywiste optymalizacje spowalniają!

Suma kontrolna z raw mt jest inna, bo też jest inny algorytm generowania - to
nie stanowi błędu.

Niezrozumiałe jest dla mnie, dlaczego są tak duże różnice w czasie przy
tak niewielkich zmianach.

Pouczające jest to, że jeśli już chcemy mieć kawałek wydajnego kodu, to
niewielkie zmiany mogą dawać  10-krotne przyspieszenie. I to, że oczywista
optymalizacja może tak naprawdę spowolnić nasz kod.

Pozdrawiam


Zawsze mogłem popełnić jakieś błędy (choć sumy kontrolne się zgadzają), więc
będę wdzięczny za krytykę:

#include <iostream>
#include <random>
#include <ctime>

using ultyp = unsigned long long;
using utyp  = unsigned int;
using cultyp = const ultyp;
using cutyp  = const utyp;

template<class TRND,cutyp SIZE1, cutyp SIZE2>
class RndBuff1 {
private:
    TRND  &rnd;   // Pseudo random number generator.
    utyp  min;    // Min range.
    utyp  max;    // Max range.
    utyp  buff[SIZE1+SIZE2];   // N-Cyclic buffer to number generator.
    utyp  i1,i2;  // Index to the next given value.
    utyp  select; // Select first or second buffer.

private:
    void reset() {
        for( utyp i=0 ; i<SIZE1+SIZE2 ; i++ ) {
            buff[i] = rnd() % (max-min+1)+min;
        }
    }
public:
    RndBuff1( TRND &rnd, cutyp min=0, cutyp max=0 ) : rnd(rnd) {
        setMinMax(min,max);
    }
    void setMinMax(cutyp min, cutyp max   ) {
        this->min = min;
        this->max = max;
        i1 = SIZE1;
        i2 = SIZE1+SIZE2;
        select = 0;
    }
    utyp operator()() {
        if( i1 == SIZE1 && i2 == SIZE1+SIZE2 ) {
            reset();
        }
        if( 1 & select++ ) {
            if( i1 == SIZE1 ) {
                i1 = 0;
            }
            return buff[i1++];
        }
        if( i2 == SIZE1+SIZE2 ) {
            i2 = SIZE1;
        }
        return buff[i2++];
    }

};

template<class TRND,cutyp SIZE1, cutyp SIZE2>
class RndBuff2 {
private:
    TRND  &rnd;   // Pseudo random number generator.
    utyp  min;    // Min range.
    utyp  max;    // Max range.
    utyp  *const buff;   // N-Cyclic buffer to number generator.
    utyp  i1,i2;  // Index to the next given value.
    utyp  select; // Select first or second buffer.

private:
    void reset() {
        for( utyp i=0 ; i<SIZE1+SIZE2 ; i++ ) {
            buff[i] = rnd() % (max-min+1)+min;
        }
    }
public:
    RndBuff2( TRND &rnd, cutyp min=0, cutyp max=0 ) : rnd(rnd), buff(new utyp[SIZE1+SIZE2]) {
        setMinMax(min,max);
    }
    ~RndBuff2() {
        delete[]buff;
    }
    void setMinMax(cutyp min, cutyp max) {
        this->min = min;
        this->max = max;
        i1 = SIZE1;
        i2 = SIZE1+SIZE2;
        select = 0;
    }
    utyp operator()() {
        if( i1 == SIZE1 && i2 == SIZE1+SIZE2 ) {
            reset();
        }
        if( 1 & select++ ) {
            if( i1 == SIZE1 ) {
                i1 = 0;
            }
            return buff[i1++];
        }
        if( i2 == SIZE1+SIZE2 ) {
            i2 = SIZE1;
        }
        return buff[i2++];
    }

};


template<class TRND,cutyp SIZE1, cutyp SIZE2>
class RndBuff3 {
private:
    TRND  &rnd;   // Pseudo random number generator.
    utyp  min;    // Min range.
    utyp  max;    // Max range.
    utyp  *const buff;  // N-Cyclic buffer to number generator.
    cutyp *i1;          // Index to the next given value.
    cutyp *i2;
    utyp  select; // Select first or second buffer.

private:
    void reset() {
        for( utyp i=0 ; i<SIZE1+SIZE2 ; i++ ) {
            buff[i] = rnd() % (max-min+1)+min;
        }
    }
public:
    RndBuff3( TRND &rnd, cutyp min=0, cutyp max=0 ) : rnd(rnd), buff(new utyp[SIZE1+SIZE2]) {
        setMinMax(min,max);
    }
    ~RndBuff3() {
        delete[] buff;
    }
    void setMinMax(cutyp min, cutyp max) {
        this->min = min;
        this->max = max;
        i1 = buff+SIZE1;
        i2 = buff+SIZE1+SIZE2;
        select = 0;
    }
    utyp operator()() {
        if( 1 & select++ ) {
            if( i1 == buff+SIZE1 ) {
                if( i2 == buff+SIZE1+SIZE2 ) {
                    reset();
                }
                i1 = buff;
            }
            return *i1++;
        } else {
            if( i2 == buff+SIZE1+SIZE2 ) {
                if(i1 == buff+SIZE1) {
                    reset();
                }
                i2 = buff+SIZE1;
            }
            return *i2++;
        }
    }

};


template<class TRND,cutyp SIZE1, cutyp SIZE2>
class RndBuff4 {
private:
    TRND  &rnd;   // Pseudo random number generator.
    utyp  min;    // Min range.
    utyp  max;    // Max range.
    utyp  buff[SIZE1+SIZE2];  // N-Cyclic buffer to number generator.
    cutyp *i1;          // Index to the next given value.
    cutyp *i2;
    utyp  select; // Select first or second buffer.

private:
    void reset() {
        for( utyp i=0 ; i<SIZE1+SIZE2 ; i++ ) {
            buff[i] = rnd() % (max-min+1)+min;
        }
    }
public:
    RndBuff4( TRND &rnd, cutyp min=0, cutyp max=0 ) : rnd(rnd) {
        setMinMax(min,max);
    }
    void setMinMax(cutyp min, cutyp max) {
        this->min = min;
        this->max = max;
        i1 = buff+SIZE1;
        i2 = buff+SIZE1+SIZE2;
        select = 0;
    }
    utyp operator()() {
        if( 1 & select++ ) {
            if( i1 == buff+SIZE1 ) {
                if( i2 == buff+SIZE1+SIZE2 ) {
                    reset();
                }
                i1 = buff;
            }
            return *i1++;
        } else {
            if( i2 == buff+SIZE1+SIZE2 ) {
                if(i1 == buff+SIZE1) {
                    reset();
                }
                i2 = buff+SIZE1;
            }
            return *i2++;
        }
    }

};


constexpr ultyp LOOPS = 1ull << 32;
constexpr utyp  MIN = 11;
constexpr utyp  MAX = 38;
constexpr utyp  SIZE1 = 1249;
constexpr utyp  SIZE2 = 1699;


int main() {
    cultyp mainSeed = 1234567890ull;
    time_t times[5] = {0,0,0,0,0};
    std::mt19937_64 rnd, rndSeed(mainSeed);

    for( utyp test=0 ; true ; test++ ) {
        cultyp seed = rndSeed();
        std::cout << "seed [" << seed << "] nr.:" << (test+1) << std::endl;

        {
            std::cout << "raw mt start   ";
            ultyp sum = 0;
            rnd.seed( seed );
            std::uniform_int_distribution<int> dist(MIN,MAX);
            const time_t start = time(NULL);
            for( ultyp loop=0 ; loop<LOOPS ; loop++ ) {
                sum += dist( rnd );
            }
            times[0] += time(NULL) - start;
            std::cout << sum << " " << times[0] << std::endl;
        }
        {
            std::cout << "RndBuff1 start ";
            ultyp sum = 0;
            rnd.seed( seed );
            RndBuff1<std::mt19937_64,SIZE1,SIZE2> rndBuf(rnd,MIN,MAX);
            const time_t start = time(NULL);
            for( ultyp loop=0 ; loop<LOOPS ; loop++ ) {
                sum += rndBuf();
            }
            times[1] += time(NULL) - start;
            std::cout << sum << " " << times[1] << std::endl;
        }
        {
            std::cout << "RndBuff2 start ";
            ultyp sum = 0;
            rnd.seed( seed );
            RndBuff2<std::mt19937_64,SIZE1,SIZE2> rndBuf(rnd,MIN,MAX);
            const time_t start = time(NULL);
            for( ultyp loop=0 ; loop<LOOPS ; loop++ ) {
                sum += rndBuf();
            }
            times[2] += time(NULL) - start;
            std::cout << sum << " " << times[2] << std::endl;
        }
        {
            std::cout << "RndBuff3 start ";
            ultyp sum = 0;
            rnd.seed( seed );
            RndBuff3<std::mt19937_64,SIZE1,SIZE2> rndBuf(rnd,MIN,MAX);
            const time_t start = time(NULL);
            for( ultyp loop=0 ; loop<LOOPS ; loop++ ) {
                sum += rndBuf();
            }
            times[3] += time(NULL) - start;
            std::cout << sum << " " << times[3] << std::endl;
        }
        {
            std::cout << "RndBuff4 start ";
            ultyp sum = 0;
            rnd.seed( seed );
            RndBuff4<std::mt19937_64,SIZE1,SIZE2> rndBuf(rnd,MIN,MAX);
            const time_t start = time(NULL);
            for( ultyp loop=0 ; loop<LOOPS ; loop++ ) {
                sum += rndBuf();
            }
            times[4] += time(NULL) - start;
            std::cout << sum << " " << times[4] << std::endl;
        }

    }


    return 0;
}
Technical Details
Message-ID: <ed3041b7-c05f-43f3-887a-9a12a9633741@googlegroups.com>
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.icm.edu.pl!newsfeed.pionier.net.pl!3.eu.feeder.erje.net!feeder.erje.net!weretis.net!feeder7.news.weretis.net!proxad.net!feeder1-2.proxad.net!209.85.160.216.MISMATCH!g89no4938939qtd.0!news-out.google.com!w29ni785qtc.0!nntp.google.com!g89no4938931qtd.0!postnews.google.com!google-groups.googlegroups.com!not-for-mail

Show Full Headers
Complete Article Headers
X-Received: by 2002:a37:e408:: with SMTP id y8mr32123017qkf.39.1577215532531;        Tue, 24 Dec 2019 11:25:32 -0800 (PST)
X-Received: by 2002:a81:6344:: with SMTP id x65mr25660335ywb.271.1577215532252; Tue, 24 Dec 2019 11:25:32 -0800 (PST)
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.icm.edu.pl!newsfeed.pionier.net.pl!3.eu.feeder.erje.net!feeder.erje.net!weretis.net!feeder7.news.weretis.net!proxad.net!feeder1-2.proxad.net!209.85.160.216.MISMATCH!g89no4938939qtd.0!news-out.google.com!w29ni785qtc.0!nntp.google.com!g89no4938931qtd.0!postnews.google.com!google-groups.googlegroups.com!not-for-mail
Newsgroups: pl.comp.lang.c
Date: Tue, 24 Dec 2019 11:25:31 -0800 (PST)
Complaints-To: groups-abuse@google.com
Injection-Info: google-groups.googlegroups.com; posting-host=77.254.46.242; posting-account=xjvq9QoAAAATMPC2X3btlHd_LkaJo_rj
NNTP-Posting-Host: 77.254.46.242
User-Agent: G2/1.0
MIME-Version: 1.0
Message-ID: <ed3041b7-c05f-43f3-887a-9a12a9633741@googlegroups.com>
Subject: Optymalizacja kodu
From: "M.M." <mmarszik@gmail.com>
Injection-Date: Tue, 24 Dec 2019 19:25:32 +0000
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit