Mengukur waktu eksekusi suatu fungsi di C ++

138

Saya ingin mencari tahu berapa banyak waktu yang dibutuhkan fungsi tertentu dalam program C ++ saya untuk dijalankan di Linux . Setelah itu, saya ingin membuat perbandingan kecepatan. Saya melihat beberapa fungsi waktu tetapi akhirnya dengan dorongan ini. Chrono:

process_user_cpu_clock, captures user-CPU time spent by the current process

Sekarang, saya tidak jelas jika saya menggunakan fungsi di atas, apakah saya akan mendapatkan satu-satunya waktu yang dihabiskan CPU untuk fungsi itu?

Kedua, saya tidak dapat menemukan contoh penggunaan fungsi di atas. Adakah yang bisa membantu saya bagaimana menggunakan fungsi di atas?

PS: Saat ini, saya menggunakan std::chrono::system_clock::now()untuk mendapatkan waktu dalam hitungan detik tetapi ini memberi saya hasil yang berbeda karena beban CPU yang berbeda setiap waktu.

Xara
sumber
2
Untuk penggunaan Linux: clock_gettime.. gcc mendefinisikan jam lain sebagai: typedef system_clock steady_clock; typedef system_clock high_resolution_clock;pada Windows, gunakan QueryPerformanceCounter.
Brandon
Bukankah pertanyaan ini merupakan duplikat dari pertanyaan ini atau apakah skenario membuat solusi berbeda?
northerner
Saya memiliki dua implementasi fungsi dan ingin mencari yang berkinerja lebih baik.
northerner
Sangat penting: pastikan Anda mengaktifkan pengoptimalan . Kode yang tidak dioptimalkan memiliki hambatan yang berbeda dari kode optimal yang dioptimalkan, dan tidak memberi tahu Anda sesuatu yang bermakna. Bantuan optimisasi C loop untuk tugas akhir (dengan optimisasi kompiler dinonaktifkan) . Dan secara umum microbenchmarking memiliki banyak jebakan, terutama kegagalan untuk melakukan loop pemanasan terlebih dahulu untuk frekuensi CPU dan kesalahan halaman: Cara evaluasi kinerja yang idiomatis? . Dan jawaban ini
Peter Cordes
Lihat juga Bagaimana Anda mengukur kinerja suatu fungsi? untuk Google Benchmark yang menghindari banyak perangkap dari rolling microbenchmark Anda sendiri. Juga benchmark Simple untuk () loop membutuhkan waktu yang sama dengan setiap loop terikat untuk lebih lanjut tentang bagaimana optimasi berinteraksi dengan loop benchmark, dan apa yang harus dilakukan tentang itu.
Peter Cordes

Jawaban:

264

Ini adalah metode yang sangat mudah digunakan dalam C ++ 11. Anda harus menggunakan std::chrono::high_resolution_clockdari <chrono>header.

Gunakan seperti ini:

#include <iostream>
#include <chrono>

void function()
{
    long long number = 0;

    for( long long i = 0; i != 2000000; ++i )
    {
       number += 5;
    }
}

int main()
{
    auto t1 = std::chrono::high_resolution_clock::now();
    function();
    auto t2 = std::chrono::high_resolution_clock::now();

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>( t2 - t1 ).count();

    std::cout << duration;
    return 0;
}

Ini akan mengukur durasi fungsi.

CATATAN: Anda tidak akan selalu mendapatkan penghitungan waktu yang sama untuk suatu fungsi. Ini karena CPU mesin Anda bisa kurang atau lebih digunakan oleh proses lain yang berjalan di komputer Anda, sama seperti pikiran Anda dapat lebih atau kurang terkonsentrasi ketika Anda menyelesaikan latihan matematika. Dalam pikiran manusia, kita dapat mengingat solusi dari masalah matematika, tetapi untuk komputer proses yang sama akan selalu menjadi sesuatu yang baru; dengan demikian, seperti yang saya katakan, Anda tidak akan selalu mendapatkan hasil yang sama!

Pemenang
sumber
Ketika saya menggunakan fungsi ini, pada jalankan pertama itu memberi saya 118440535 mikrodetik dan pada jalankan kedua dari fungsi yang sama itu memberi saya 83221031 mikrodetik. Bukankah dua pengukuran waktu harus sama ketika saya mengukur durasi fungsi itu saja?
Xara
1
Tidak. Prosesor komputer Anda dapat digunakan kurang atau lebih. Ini high_resolution_clockakan memberi Anda waktu fisik dan waktu nyata yang diperlukan fungsi Anda untuk dijalankan. Jadi, dalam menjalankan pertama Anda, CPU Anda digunakan kurang dari pada menjalankan berikutnya. Maksud "bekas" yang saya maksud adalah pekerjaan aplikasi lain menggunakan CPU.
Victor
1
Ya, jika Anda membutuhkan rata-rata waktu, itu cara yang baik untuk mendapatkannya. ambil tiga langkah, dan hitung rata-rata.
Victor
3
Bisakah Anda memposting kode tanpa "menggunakan namespace" secara umum. Itu membuatnya lebih mudah untuk melihat apa yang datang dari mana.
Snowman
1
Bukankah ini seharusnya steady_clock? Mungkinkah high_resolution_clockjam non-monoton?
Gillespie
15

Berikut adalah fungsi yang akan mengukur waktu eksekusi dari setiap fungsi yang dilewatkan sebagai argumen:

#include <chrono>
#include <utility>

typedef std::chrono::high_resolution_clock::time_point TimeVar;

#define duration(a) std::chrono::duration_cast<std::chrono::nanoseconds>(a).count()
#define timeNow() std::chrono::high_resolution_clock::now()

template<typename F, typename... Args>
double funcTime(F func, Args&&... args){
    TimeVar t1=timeNow();
    func(std::forward<Args>(args)...);
    return duration(timeNow()-t1);
}

Contoh penggunaan:

#include <iostream>
#include <algorithm>

typedef std::string String;

//first test function doing something
int countCharInString(String s, char delim){
    int count=0;
    String::size_type pos = s.find_first_of(delim);
    while ((pos = s.find_first_of(delim, pos)) != String::npos){
        count++;pos++;
    }
    return count;
}

//second test function doing the same thing in different way
int countWithAlgorithm(String s, char delim){
    return std::count(s.begin(),s.end(),delim);
}


int main(){
    std::cout<<"norm: "<<funcTime(countCharInString,"precision=10",'=')<<"\n";
    std::cout<<"algo: "<<funcTime(countWithAlgorithm,"precision=10",'=');
    return 0;
}

Keluaran:

norm: 15555
algo: 2976
Jahid
sumber
2
@ RestlessC0bra: Implementaion didefinisikan, high_resolution_clockmungkin alias system_clock(jam dinding), steady_clockatau jam independen ketiga. Lihat detailnya di sini . Untuk jam cpu, std::clockdapat digunakan
Jahid
2
Dua makro dan satu typedef global - tidak ada yang aman satu keytroke - tentu tidak ada yang saya sebut elegan. Juga melewati objek fungsi dan sempurna meneruskan argumen secara terpisah adalah sedikit berlebihan (dan dalam kasus fungsi kelebihan beban bahkan tidak nyaman), ketika Anda hanya bisa meminta kode waktunya dimasukkan ke dalam lambda. Tapi yah, selama melewati argumen adalah opsional.
MikeMB
2
Dan ini adalah pembenaran untuk melanggar masing-masing dan setiap pedoman tentang penamaan makro? Anda tidak mengawali mereka, Anda tidak menggunakan huruf kapital, Anda memilih nama yang sangat umum yang memiliki kemungkinan besar bertabrakan dengan beberapa simbol lokal dan yang terpenting: Mengapa Anda menggunakan makro sama sekali (alih-alih fungsi) )? Dan sementara kita berada di dalamnya: Mengapa Anda mengembalikan durasi sebagai nanodetik ganda mewakili di tempat pertama? Kita mungkin harus setuju bahwa kita tidak setuju. Pendapat asli saya berdiri: "Ini bukan apa yang saya sebut kode elegan".
MikeMB
1
Masalahnya adalah mereka tidak dicopot. Yang saya khawatirkan adalah bahwa makro seperti itu berakhir di file header yang (mungkin secara tidak langsung sebagai bagian dari perpustakaan) termasuk dalam kode saya. Jika Anda ingin merasakan apa yang terjadi jika nama-nama umum digunakan untuk makro, termasuk windows.hdalam proyek c ++ non-sepele. Mengenai assertpertama-tama: "quod licet iovi non licet bovi";). Kedua, tidak semua keputusan di perpustakaan standar (kadang-kadang sejak dekade kembali) sebenarnya dianggap ide yang baik oleh standar modern. Ada alasannya, mengapa perancang modul c ++ berusaha sangat keras untuk tidak mengekspor makro secara default.
MikeMB
2
@ Rahid: Terima kasih. Kalau begitu anggap komentar saya batal dan nol.
MikeMB
9

program sederhana untuk menemukan waktu pelaksanaan fungsi yang diambil.

#include <iostream>
#include <ctime> // time_t
#include <cstdio>

void function()
{
     for(long int i=0;i<1000000000;i++)
     {
        // do nothing
     }
}

int main()
{

time_t begin,end; // time_t is a datatype to store time values.

time (&begin); // note time before execution
function();
time (&end); // note time after execution

double difference = difftime (end,begin);
printf ("time taken for function() %.2lf seconds.\n", difference );

return 0;
}
Abdullah Farweez
sumber
6
ini sangat tidak akurat, hanya menunjukkan detik, tetapi tidak ada milidetik
user25
7

Dalam buku Scott Meyers saya menemukan contoh ekspresi lambda generik universal yang dapat digunakan untuk mengukur waktu eksekusi fungsi. (C ++ 14)

auto timeFuncInvocation = 
    [](auto&& func, auto&&... params) {
        // get time before function invocation
        const auto& start = std::chrono::high_resolution_clock::now();
        // function invocation using perfect forwarding
        std::forward<decltype(func)>(func)(std::forward<decltype(params)>(params)...);
        // get time after function invocation
        const auto& stop = std::chrono::high_resolution_clock::now();
        return stop - start;
     };

Masalahnya adalah Anda hanya mengukur satu eksekusi sehingga hasilnya bisa sangat berbeda. Untuk mendapatkan hasil yang andal, Anda harus mengukur sejumlah besar eksekusi. Menurut Andrei Alexandrescu kuliah di code :: dive 2015 conference - Writing Fast Code I:

Waktu yang diukur: tm = t + tq + tn + hingga

dimana:

tm - waktu diukur (diamati)

t - waktu aktual bunga

tq - waktu ditambahkan oleh noise kuantisasi

Waktu ditambahkan oleh berbagai sumber kebisingan

ke - waktu overhead (fungsi pengukuran, perulangan, panggilan)

Menurut apa yang dia katakan nanti dalam perkuliahan, Anda harus mengambil minimal eksekusi dalam jumlah besar ini sebagai hasilnya. Saya mendorong Anda untuk melihat ceramah di mana dia menjelaskan mengapa.

Juga ada perpustakaan yang sangat bagus dari google - https://github.com/google/benchmark . Perpustakaan ini sangat mudah digunakan dan kuat. Anda dapat checkout beberapa ceramah Chandler Carruth di youtube di mana dia menggunakan perpustakaan ini dalam praktek. Misalnya CppCon 2017: Chandler Carruth “Going Nowhere Faster”;

Contoh penggunaan:

#include <iostream>
#include <chrono>
#include <vector>
auto timeFuncInvocation = 
    [](auto&& func, auto&&... params) {
        // get time before function invocation
        const auto& start = high_resolution_clock::now();
        // function invocation using perfect forwarding
        for(auto i = 0; i < 100000/*largeNumber*/; ++i) {
            std::forward<decltype(func)>(func)(std::forward<decltype(params)>(params)...);
        }
        // get time after function invocation
        const auto& stop = high_resolution_clock::now();
        return (stop - start)/100000/*largeNumber*/;
     };

void f(std::vector<int>& vec) {
    vec.push_back(1);
}

void f2(std::vector<int>& vec) {
    vec.emplace_back(1);
}
int main()
{
    std::vector<int> vec;
    std::vector<int> vec2;
    std::cout << timeFuncInvocation(f, vec).count() << std::endl;
    std::cout << timeFuncInvocation(f2, vec2).count() << std::endl;
    std::vector<int> vec3;
    vec3.reserve(100000);
    std::vector<int> vec4;
    vec4.reserve(100000);
    std::cout << timeFuncInvocation(f, vec3).count() << std::endl;
    std::cout << timeFuncInvocation(f2, vec4).count() << std::endl;
    return 0;
}

EDIT: Tentu saja Anda selalu perlu mengingat bahwa kompiler Anda dapat mengoptimalkan sesuatu atau tidak. Alat seperti perf dapat berguna dalam kasus seperti itu.

Krzysztof Sommerfeld
sumber
Menarik - apa manfaat menggunakan lambda di sini di atas templat fungsi?
user48956
1
Perbedaan utama adalah bahwa itu adalah objek yang dapat dipanggil tetapi memang Anda bisa mendapatkan sesuatu yang sangat mirip dengan template variadic dan std :: result_of_t.
Krzysztof Sommerfeld
@ KrzysztofSommerfeld Bagaimana melakukan ini untuk metode fungsi, ketika saya melewati waktu (Object.Method1) itu mengembalikan kesalahan "sintaksis non-standar; gunakan '&' untuk membuat pointer ke anggota"
RobinAtTech
timeFuncInvocation ([& objectName] (auto && ... args) {objectName.methodName (std :: forward <decltype (args)> (args) ...);}, arg1, arg2, ...); atau ommit & masuk sebelum objectName (maka Anda akan memiliki salinan objek)
Krzysztof Sommerfeld
4

Cara mudah untuk C ++ yang lebih lama, atau C:

#include <time.h> // includes clock_t and CLOCKS_PER_SEC

int main() {

    clock_t start, end;

    start = clock();
    // ...code to measure...
    end = clock();

    double duration_sec = double(end-start)/CLOCKS_PER_SEC;
    return 0;
}

Ketepatan waktu dalam hitungan detik adalah 1.0/CLOCKS_PER_SEC

v.chaplin
sumber
1
Ini tidak portabel. Ini mengukur waktu prosesor di Linux, dan waktu jam di Windows.
BugSquasher
2
  • Ini adalah metode yang sangat mudah digunakan dalam C ++ 11.
  • Kita dapat menggunakan std :: chrono :: high_resolution_clock dari header
  • Kita dapat menulis metode untuk mencetak waktu eksekusi metode dalam bentuk yang mudah dibaca.

Misalnya, untuk menemukan semua bilangan prima antara 1 dan 100 juta, dibutuhkan sekitar 1 menit dan 40 detik. Jadi waktu eksekusi dapat dicetak sebagai:

Execution Time: 1 Minutes, 40 Seconds, 715 MicroSeconds, 715000 NanoSeconds

Kode di sini:

#include <iostream>
#include <chrono>

using namespace std;
using namespace std::chrono;

typedef high_resolution_clock Clock;
typedef Clock::time_point ClockTime;

void findPrime(long n, string file);
void printExecutionTime(ClockTime start_time, ClockTime end_time);

int main()
{
    long n = long(1E+8);  // N = 100 million

    ClockTime start_time = Clock::now();

    // Write all the prime numbers from 1 to N to the file "prime.txt"
    findPrime(n, "C:\\prime.txt"); 

    ClockTime end_time = Clock::now();

    printExecutionTime(start_time, end_time);
}

void printExecutionTime(ClockTime start_time, ClockTime end_time)
{
    auto execution_time_ns = duration_cast<nanoseconds>(end_time - start_time).count();
    auto execution_time_ms = duration_cast<microseconds>(end_time - start_time).count();
    auto execution_time_sec = duration_cast<seconds>(end_time - start_time).count();
    auto execution_time_min = duration_cast<minutes>(end_time - start_time).count();
    auto execution_time_hour = duration_cast<hours>(end_time - start_time).count();

    cout << "\nExecution Time: ";
    if(execution_time_hour > 0)
    cout << "" << execution_time_hour << " Hours, ";
    if(execution_time_min > 0)
    cout << "" << execution_time_min % 60 << " Minutes, ";
    if(execution_time_sec > 0)
    cout << "" << execution_time_sec % 60 << " Seconds, ";
    if(execution_time_ms > 0)
    cout << "" << execution_time_ms % long(1E+3) << " MicroSeconds, ";
    if(execution_time_ns > 0)
    cout << "" << execution_time_ns % long(1E+6) << " NanoSeconds, ";
}
Pratik Patil
sumber
0

Berikut adalah templat kelas hanya header yang sangat baik untuk mengukur waktu yang berlalu dari suatu fungsi atau blok kode apa pun:

#ifndef EXECUTION_TIMER_H
#define EXECUTION_TIMER_H

template<class Resolution = std::chrono::milliseconds>
class ExecutionTimer {
public:
    using Clock = std::conditional_t<std::chrono::high_resolution_clock::is_steady,
                                     std::chrono::high_resolution_clock,
                                     std::chrono::steady_clock>;
private:
    const Clock::time_point mStart = Clock::now();

public:
    ExecutionTimer() = default;
    ~ExecutionTimer() {
        const auto end = Clock::now();
        std::ostringstream strStream;
        strStream << "Destructor Elapsed: "
                  << std::chrono::duration_cast<Resolution>( end - mStart ).count()
                  << std::endl;
        std::cout << strStream.str() << std::endl;
    }    

    inline void stop() {
        const auto end = Clock::now();
        std::ostringstream strStream;
        strStream << "Stop Elapsed: "
                  << std::chrono::duration_cast<Resolution>(end - mStart).count()
                  << std::endl;
        std::cout << strStream.str() << std::endl;
    }

}; // ExecutionTimer

#endif // EXECUTION_TIMER_H

Berikut beberapa kegunaannya:

int main() {
    { // empty scope to display ExecutionTimer's destructor's message
         // displayed in milliseconds
         ExecutionTimer<std::chrono::milliseconds> timer;

         // function or code block here

         timer.stop();

    } 

    { // same as above
        ExecutionTimer<std::chrono::microseconds> timer;

        // code block here...

        timer.stop();
    }

    {  // same as above
       ExecutionTimer<std::chrono::nanoseconds> timer;

       // code block here...

       timer.stop();

    }

    {  // same as above
       ExecutionTimer<std::chrono::seconds> timer;

       // code block here...

       timer.stop();

    }              

    return 0;
}

Karena kelas adalah templat, kami dapat menentukan dengan mudah bagaimana kami ingin waktu kami diukur & ditampilkan. Ini adalah templat kelas utilitas yang sangat berguna untuk melakukan penandaan bangku dan sangat mudah digunakan.

Francis Cugler
sumber
Secara pribadi, stop()fungsi anggota tidak diperlukan karena destruktor menghentikan timer untuk Anda.
Casey
@Casey Desain kelas tidak perlu berhenti fungsi, tetapi ada karena alasan tertentu. Konstruk default saat membuat objek sebelum Anda test codememulai timer. Kemudian setelah Anda, test codeAnda secara eksplisit menggunakan objek timer dan memanggil metode berhenti. Anda harus menjalankannya secara manual ketika Anda ingin stoptimer. Kelas tidak mengambil parameter apa pun. Juga jika Anda menggunakan kelas ini seperti yang saya tunjukkan, Anda akan melihat bahwa ada sedikit waktu berlalu antara panggilan ke obj.stopdan destructor.
Francis Cugler
@ Casey ... Ini juga memungkinkan untuk memiliki beberapa objek timer dalam lingkup yang sama, bukan berarti orang benar-benar membutuhkannya, tetapi hanya opsi lain yang layak.
Francis Cugler
Contoh ini tidak dapat dikompilasi dalam bentuk yang disajikan. Kesalahan terkait dengan "tidak cocok untuk operator << ..."!
Celdor
@Eldor yang harus Anda masukkan meliputi; seperti <chrono>?
Francis Cugler
0

Saya sarankan menggunakan steady_clockyang dijamin monoton, tidak seperti high_resolution_clock.

#include <iostream>
#include <chrono>

using namespace std;

unsigned int stopwatch()
{
    static auto start_time = chrono::steady_clock::now();

    auto end_time = chrono::steady_clock::now();
    auto delta    = chrono::duration_cast<chrono::microseconds>(end_time - start_time);

    start_time = end_time;

    return delta.count();
}

int main() {
  stopwatch(); //Start stopwatch
  std::cout << "Hello World!\n";
  cout << stopwatch() << endl; //Time to execute last line
  for (int i=0; i<1000000; i++)
      string s = "ASDFAD";
  cout << stopwatch() << endl; //Time to execute for loop
}

Keluaran:

Hello World!
62
163514
Gillespie
sumber
0

Anda dapat memiliki kelas sederhana yang dapat digunakan untuk pengukuran semacam ini.

class duration_printer {
public:
    duration_printer() : __start(std::chrono::high_resolution_clock::now()) {}
    ~duration_printer() {
        using namespace std::chrono;
        high_resolution_clock::time_point end = high_resolution_clock::now();
        duration<double> dur = duration_cast<duration<double>>(end - __start);
        std::cout << dur.count() << " seconds" << std::endl;
    }
private:
    std::chrono::high_resolution_clock::time_point __start;
};

Satu-satunya hal yang perlu dilakukan adalah membuat objek di fungsi Anda di awal fungsi itu

void veryLongExecutingFunction() {
    duration_calculator dc;
    for(int i = 0; i < 100000; ++i) std::cout << "Hello world" << std::endl;
}

int main() {
    veryLongExecutingFunction();
    return 0;
}

dan hanya itu. Kelas dapat dimodifikasi agar sesuai dengan kebutuhan Anda.

arsdever
sumber
0

Karena tidak ada jawaban yang diberikan yang sangat akurat atau memberikan hasil yang dapat direproduksi, saya memutuskan untuk menambahkan tautan ke kode saya yang memiliki ketepatan sub-nanosecond dan statistik ilmiah.

Perhatikan bahwa ini hanya akan berfungsi untuk mengukur kode yang membutuhkan waktu (sangat) singkat untuk dijalankan (alias, beberapa siklus clock hingga beberapa ribu): jika mereka berjalan sangat lama sehingga mereka kemungkinan akan terganggu oleh beberapa -heh- interupsi , maka jelas tidak mungkin untuk memberikan hasil yang dapat direproduksi dan akurat; konsekuensi yang adalah bahwa pengukuran tidak pernah selesai: yaitu, ia terus mengukur sampai secara statistik 99,9% yakin itu memiliki jawaban yang benar yang tidak pernah terjadi pada mesin yang memiliki proses lain yang berjalan ketika kode terlalu lama.

https://github.com/CarloWood/cwds/blob/master/benchmark.h#L40

Carlo Wood
sumber
0

Jika Anda ingin menghemat waktu dan baris kode, Anda dapat mengukur waktu pelaksanaan fungsi makro satu baris:

a) Menerapkan kelas pengukur waktu seperti yang telah disarankan di atas (berikut ini adalah implementasi saya untuk android):

class MeasureExecutionTime{
private:
    const std::chrono::steady_clock::time_point begin;
    const std::string caller;
public:
    MeasureExecutionTime(const std::string& caller):caller(caller),begin(std::chrono::steady_clock::now()){}
    ~MeasureExecutionTime(){
        const auto duration=std::chrono::steady_clock::now()-begin;
        LOGD("ExecutionTime")<<"For "<<caller<<" is "<<std::chrono::duration_cast<std::chrono::milliseconds>(duration).count()<<"ms";
    }
};

b) Tambahkan makro nyaman yang menggunakan nama fungsi saat ini sebagai TAG (menggunakan makro di sini adalah penting, yang lain __FUNCTION__akan mengevaluasi untuk MeasureExecutionTimebukannya fungsi yang Anda ingin ukur

#ifndef MEASURE_FUNCTION_EXECUTION_TIME
#define MEASURE_FUNCTION_EXECUTION_TIME const MeasureExecutionTime measureExecutionTime(__FUNCTION__);
#endif

c) Tulis makro Anda di awal fungsi yang ingin Anda ukur. Contoh:

 void DecodeMJPEGtoANativeWindowBuffer(uvc_frame_t* frame_mjpeg,const ANativeWindow_Buffer& nativeWindowBuffer){
        MEASURE_FUNCTION_EXECUTION_TIME
        // Do some time-critical stuff 
}

Yang akan menghasilkan int berikut output:

ExecutionTime: For DecodeMJPEGtoANativeWindowBuffer is 54ms

Perhatikan bahwa ini (seperti semua solusi yang disarankan lainnya) akan mengukur waktu antara kapan fungsi Anda dipanggil dan ketika itu kembali, tidak perlu waktu CPU Anda menjalankan fungsi. Namun, jika Anda tidak memberikan perubahan pada penjadwal untuk menangguhkan kode berjalan Anda dengan memanggil sleep () atau yang serupa, tidak ada perbedaan di antara keduanya.

Constantin Geier
sumber