Evet yazılımperver dostlarım, std::chrone kütüphanesi maceramıza devam ediyoruz. İlk yazımı okumayan yazılımperver dostlarımızın, öncelikle o yazıma bakmalarında fayda var. Aşağıya ilgili yazının bağlantısını ekliyorum:
Haftalık C++ 39 – std::chrono – 1
İlk yazımızda, std::chrono tarafından sunulan üç önemli kavram olan, süre (“duration”), saat (“clock“) ve zaman noktası (“time_point“)’a değinmiş ve süre kavramını ve kabiliyetlerini detaylı bir şekilde incelemiştik. Bu yazımızın konusu ise saat ve zaman noktaları olacak. O zaman saatler ile başlayalım.
Saatler (“clock”)
std::chrono, saati, bir başlangıç zamanı (“epoch”) ve bu zamandan itibaren olan süre için kullanılacak olan ilerleme periyot (“tick period”) bilgileri ile tanımlar. Hemen hızlı bir örnek ile netleştirmeye çalışayım. Örneğin, bir saat Unix Epoch’u (1 Ocak 1970) başlangıç ve periyodu da milisaniye cinsinden ifade ediyor olabilir. Benzer şekilde başka saat, saniye cinsinden de tutuyor olabilir. Bunlar farklı saatleri temsil eder. Bu saatlerin başlangıç zamanları da farklı olabilir.
Peki bu farklı saatler konusunda neye dikkat etmemiz gerekiyor? Her ne kadar daha bahsetmemiş olsak da, zaman noktaları (“time point”) muhakkak bir saat ile ilişkilendirilirler. Farklı saatler ile ilişkilendirilmiş, zaman noktalarını bir diğeri ile kullanamazsınız. Şimdi biraz daha detaylara inelim.
std::clock tipleri <chrono> başlık dosyası ve std::chrono adres uzayı içerisinde tanımlanmaktadır. Uygulamaların ihtiyaçlarına göre birçok saat kullanılıyor olabilir. En son std::chrono referans sayfasına baktığımda aşağıdaki saatlerin tanımlanmış olduklarını gördüm. Bunların bir kısmı C++ 20 ile sunuluyor:
system_clock (C++11)
|
Gerçek zamanlı sistem saati sınıfı |
steady_clock (C++11)
|
Değiştirilemeyen monoton saat sınıfı |
high_resolution_clock (C++11)
|
Sistem tarafından sunulan en kısa periyoda sahip saat sınıfı |
utc_clock (C++20)
|
Coordinated Universal Time (UTC) sınıfı |
tai_clock (C++20)
|
International Atomic Time (TAI) sınıfı |
gps_clock (C++20)
|
GPS zaman sınıfı |
file_clock (C++20)
|
Dosya zaman snıfı |
local_t (C++20)
|
Yerel zamanı ifade eden “pseudo-clock” saat sınıfı |
Ayrıca ilgili tipin, saat olup/olmadığını kontrol etmek için C++ 20 ile birlikte is_clock ve is_clock_v API’leri de sunulmakta.
Gelelim, bu saatlere ilişkin, her bir saat sınıfı ile sunulması gereken tiplere ve statik üyelere. Bu üyeler aracılığı ile ilgili saatlere ilişkin daha detaylı bilgi edinebiliyoruz:
İlgili Üye | Açıklama |
clock::duration | Saat’in kullandığı süre (“duration”) tipini tanımlar (std::chrono::duration<rep, period>) |
clock::rep | Saat’in kullandığı süreyi ifade etmek için kullandığı ilerlemelerin (“tick”) aritmetik tipini ifade eder (std::chrono::duration::rep) |
clock::period | Saat’in kullandığı periyodun tipini ifade eder (std::chrono::duration::period) |
clock::time_point | Saat’in kullandığı zaman noktasının tipini ifade eder |
clock::is_steady | İlgili saatin sürekli/istikrarlı olup olmamasını ifade eder. Buna aşağıda biraz daha değineceğim |
clock::now() | Şu anı ifade eden zaman noktasını (time_point) döner |
Kodsal olarak gösterecek olsak, şu şekilde ifade edilebilir:
1 2 3 4 5 6 7 8 9 |
struct some_clock { using duration = chrono::duration<int64_t, microseconds>; using rep = duration::rep; using period = duration::period; using time_point = chrono::time_point<some_clock>; static constexpr bool is_steady = false; static time_point now() noexcept; }; |
Yukarıdaki tiplerin çoğu kendini ifade etse de, bir ikisine burada değinmekte fayda var:
- Bunlardan ilki: “is_steady” özellikliği. bu özellik ilgili saatin hiç bir zaman değiştirilip/değiştirelemeyeceğine ilişkin bilgiyi verir (windows üzerinden saati bir şekilde değiştirme olarak da düşünebilirsiniz) . Kısaca, bu saatin sunduğu zaman noktaları hiç bir zaman geriye gitmez. Bu özelliğe saat tiplerinde de değineceğiz,
- Bir diğeri ise: “now()” fonksiyonu. Bu fonksiyon ile, ilgili saati kullanan mevcut anı, zaman noktası nesnesi olarak alabiliyoruz. Tahmin edebileceğiniz üzere, saatler için en önemli fonksiyonlardan birisi budur.
Yazımın başında, STL tarafından sunulan birtakım saatlerin listesini sizler ile paylaştım. Bu yazımda, STL ile gelen üç temel saate bakıyor olacağız. Bunlar: system_clock, steady_clock ve high_resolution_clock.
- system_clock:
- Mevcut sistemde bulunan gerçek zamanlı saat alt yapısı ile sunulan zaman noktalarını almanıza olanak sağlar. Bu saat ayrıca, to_time_t() ve from_time_t() fonksiyonları aracılığıyla C tarafından sunulan time_t ile std::chrono tarafından sunulan time_point tipleri arasında dönüşüm yapmanıza olanak sağlar.
- Bu iki API sayesinde, saatleri de kullanarak, takvimsel saat metinlerini oluşturabilirsiniz (ör. Mon May 23 13:44:00 2011). Nasıl mı? Hemen bakalım:
-
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647#include <chrono>#include <ctime>#include <string>#include <iostream>// Bundan sonra kullanacağımız bazı faydalı fonksiyonları toplayacağımız sınıftırclass ChronoUtil {public:// timepoint nesnelerimizi time_t tipine, onu da takvimsel saat gösterimine (ör. Mon May 23 13:44:00 2011), çevirmek için kullanacağımız fonksiyondurdurstatic std::string ToString (const std::chrono::system_clock::time_point& tp) {// sistem zamanına dönüştürelim:std::time_t t = std::chrono::system_clock::to_time_t(tp);// takvimsel zamana dönüştürelimstd::string ts = ctime(&t);// bunun sonuna eklenen yeni satır karakterini silelimts.resize(ts.size()-1);return ts;}// Takvimsel zaman bilgilerinden, sistem saatinin time_point nesnesine çevirmek için kullanabileceğimi fonksiyondurstatic std::chrono::system_clock::time_point MakeTimePoint (int32_t year, int32_t mon, int32_t day,int32_t hour, int32_t min, int32_t sec = 0) {struct std::tm t;t.tm_sec = sec; // Saniye (0 .. 59)t.tm_min = min; // Dakika (0 .. 59)t.tm_hour = hour; // Saat (0 .. 23)t.tm_mday = day; // Gün (0 .. 31)t.tm_mon = mon-1; // Ay (0 .. 11)t.tm_year = year-1900; // 1900'den geçen senet.tm_isdst = -1; // Gün ışığını koruma var mıstd::time_t tt = std::mktime(&t);if (tt == -1) {throw "Geçerli sistem zamanı yok!";}return std::chrono::system_clock::from_time_t(tt);}};int main() {auto tp1 = ChronoUtil::MakeTimePoint(2021, 15, 8, 21, 15, 40);std::cout << ChronoUtil::ToString(tp1) << std::endl;return 0;}
- Kısaca takvimsel operasyonlar için bu saat tipini kullanabilirsiniz.
- C++ 20 ile birlikte, formatlama ile ilgili bir takım ek kabiliyetler de eklenmiştir. İlgili referans sayfasında bu konuda daha net bilgi edinebilirsiniz.
- steady_clock:
- Bu hiçbir zaman değişmeyeceği garantisi verilen bir saattir. Fiziksel zaman ilerlerken, bu saate ilişkin anlık zamanlar hiçbir zaman azalmazlar ve orantılı şekilde/oranda ilerlerler. Bu ne demek?
- Bu saat tipi de, özelllikle belirli bir süre bekleme veya bir süreye kadar bekletme için tercih edilmektedir. “Stopwatch” benzeri özellikler için de bu saat kullanılmalıdır. Örneğin, bu amaçla sistem saati kullandığınız durumda, windows üzerinde kullanıcı eğer saati değiştirir ise, sonuç değişir. Fakat, steady_clock’ta böyle bir durum söz konusu değil.
- Bu saati kullanarak da, günün saati veya günü, ayı öğrenemezsiniz!
- Bu saatin başlangıç zamanı genelde PC’lerde boot zamanı olabiliyor 😉
- high_resolution_clock:
- Bu saat, sistem üzerinde en kısa ilerleme periyodunu veren saati temsil eder. Bir diğer ifade ile en hassas saati ve zaman anlarını sağlar.
- Çoğu sistemde genelde yukarıda bahsettiğimiz iki saat tipini kullanır ya da farklı bir saat de olabilir.
- Her ne kadar süre ve benzeri ölçümlerde, bu saat tipini insanın kullanası gelse de, referans sayfasında da ifade edildiği üzere, steady_clock, bu anlamda daha doğru bir seçim olacaktır.
C++ standardı ve STL kütüphaneleri, bu saatlere ilişkin, başlangıç zamanı, minimum/maksimum zaman anı ve çözünürlüğüne dair bir garanti vermez. Bu sebeple, katı isterleri bulunan uygulamalar için, mevcut saatlerin sunduğu kabiliyetler sorgulanmalıdır. Aşağıda buna ilişkin örnek bir kod parçasını görebilirsiniz. Bu kod parçası, yukarıdaki üç saat tipi için ilgili bilgileri sizlere sunar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// clockProperties.cpp #include <chrono> #include <iomanip> #include <iostream> class ChronoUtil { public: // Saati şablon parametresi olarak geçirelim template <typename T> static void PrintClockInfo(){ std::cout << " precision: " << T::num << "/" << T::den << " second " << "\n"; typedef typename std::ratio_multiply<T,std::kilo>::type MillSec; typedef typename std::ratio_multiply<T,std::mega>::type MicroSec; std::cout << std::fixed; std::cout << " " << static_cast<double>(MillSec::num)/MillSec::den << " milliseconds " << "\n"; std::cout << " " << static_cast<double>(MicroSec::num)/MicroSec::den << " microseconds " << "\n"; } }; int main(){ std::cout << std::boolalpha << "\n"; std::cout << "std::chrono::system_clock: " << "\n"; std::cout << " is steady: " << std::chrono::system_clock::is_steady << "\n"; ChronoUtil::PrintClockInfo<std::chrono::system_clock::period>(); std::cout << "\n"; std::cout << "std::chrono::steady_clock: " << "\n"; std::cout << " is steady: " << std::chrono::steady_clock::is_steady << "\n"; ChronoUtil::PrintClockInfo<std::chrono::steady_clock::period>(); std::cout << "\n"; std::cout << "std::chrono::high_resolution_clock: " << "\n"; std::cout << " is steady: " << std::chrono::high_resolution_clock::is_steady << "\n"; ChronoUtil::PrintClockInfo<std::chrono::high_resolution_clock::period>(); std::cout << "\n"; return 0; } |
Benim makinemde, yukarıdaki kod aşağıdaki gibi bir sonuç verdi. Gördüğünüz üzere high_resolution_clock, steady_clock ile benzer özellikleri sergiliyor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
std::chrono::system_clock: is steady: false precision: 1/10000000 second 0.000100 milliseconds 0.100000 microseconds std::chrono::steady_clock: is steady: true precision: 1/1000000000 second 0.000001 milliseconds 0.001000 microseconds std::chrono::high_resolution_clock: is steady: true precision: 1/1000000000 second 0.000001 milliseconds 0.001000 microseconds |
Saatlere bu kadar eğildikten sonra, sıra geldi, std::chrono konusunda değineceğimiz zaman anlarına bir diğer ifade ile “time_point”.
Zaman Anları (“time_point”)
std::chrono ile sunulan zaman anlarını her ne kadar sona bıraksak da, aslına bakarsanız, bu kütüphanenin en önemli sınıflarından birisidir kendisi. Daha önce de ifade ettiğimiz üzere, zaman anı (“time_point”), bir saat, bu saatin başlangıç zamanı (“epoch”) ve ondan bu yana geçen süre ile tanımlanan anı ifade eder.
İlk yazımda, sizler ile paylaştığım bir figür ile bu kavramı sizlere ifade etmeye çalışacağım. Aslına bakarsanız, zaman anı, belirlenen bu epoch zamanından, belirli bir süre önce ya da sonraki anı ifade eden kavramdır.
Hatta yukarıda, “MakeTimePoint” fonksiyonu ile birlikte herhangi bir takvim tarih/saatinden zaman anı nasıl elde edeceğimizi de görmüştük. Bu sınıf aşağıdaki gibi tanımlanmaktadır:
1 2 3 4 5 6 7 |
namespace std { namespace chrono { template <typename Clock, typename Duration = typename Clock::duration> class time_point; } } |
Her bir saat ile sunulan dört önemli zaman anı bulunmaktadır (tahmin edeceğiniz üzere bunlar sunulan API’ler ile sağlanıyor, kaynak için bu sayfaya bakabilirsiniz:)
- epoch: başlangıç zaman anı. Bunun için bir API yok. İlgili time_point varsayılan yapıcısı ile oluşturulan, zaman anı nesnesi bunu ifade eder,
- now(): mevcut zaman anını ifade eden değeri almak için kullanılabilecek statik fonksiyon,
- min(): bu saat ile ifade edilen minimum zaman anını ifade eden değeri almak için kullanılabilecek statik fonksiyon,
- max(): bu saat ile ifade edilen maksimum zaman anını ifade eden değeri almak için kullanılabilecek statik fonksiyon.
Yukarıda tanımlamaya başladığımız ChronoUtils sınıfına birkaç fonksiyon daha ekleyelim hadi.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#include <chrono> #include <ctime> #include <string> #include <iostream> // Bundan sonra kullanacağımız bazı faydalı fonksiyonları toplayacağımız sınıftır class ChronoUtil { public: // timepoint nesnelerimizi time_t tipine, onu da takvimsel saat gösterimine (ör. Mon May 23 13:44:00 2011), çevirmek için kullanacağımız fonksiyondurdur static std::string ToString (const std::chrono::system_clock::time_point& tp) { // sistem zamanına dönüştürelim: std::time_t t = std::chrono::system_clock::to_time_t(tp); // takvimsel zamana dönüştürelim std::string ts = ctime(&t); // bunun sonuna eklenen yeni satır karakterini silelim ts.resize(ts.size()-1); return ts; } }; int main() { // Sistem saatinin, başlangıç zamanına bakalım std::chrono::system_clock::time_point tp; std::cout << "epoch: " << ChronoUtil::ToString(tp) << "\n"; // Sistem saatinin mevcut anına bakalım tp = std::chrono::system_clock::now(); std::cout << "now: " << ChronoUtil::ToString(tp) << "\n"; // Sistem saati ile ifade edilebilecek minimum zaman anına bakalım tp = std::chrono::system_clock::time_point::min(); std::cout << "min: " << ChronoUtil::ToString(tp) << "\n"; // Sistem saati ile ifade edilebilecek maksimum zaman anına bakalım tp = std::chrono::system_clock::time_point::max(); std::cout << "max: " << ChronoUtil::ToString(tp) << "\n"; return 0; } |
yukarıdaki kodu çalıştırdığımda aşağıdaki gibi bir sonuç alıyoruz:
1 2 3 4 |
epoch: Thu Jan 1 00:00:00 1970 now: Mon Aug 16 19:10:25 2021 min: Tue Sep 21 00:12:44 1677 max: Fri Apr 11 23:47:16 2262 |
Zaman anları ile ilgili bir diğer önemli konu da, bu nesneleri (elbette aynı saate ait) birbirleri ile aritmetik operasyonlarda kullanabilmenizdir (toplama, çıkarma, karşılaştırma, vb. Tam liste için referans sayfasına başvurabilirsiniz). Ör. Basitçe iki zaman anı arasındaki süreyi ölçmek istiyoruz ne yapacağız? Aşağıdaki iki üç satırlık kod ile ne yapılabileceğini görebilirsiniz. Umarım kafanızda şimdi daha netleşmiştir. Bu ve benzeri kullanımlara ilişkin de ayrıca bir yazı yazmayı planlıyorum bu arada.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
auto t1 = std::chrono::system_clock::now() ; // Ölçeceğimiz kısım auto t2 = std::chrono::system_clock::now() ; // Aritmetik operasyonlarda sıkıntı yokhttps://www.yazilimperver.com/wp-admin/post.php?post=2240&action=edit# auto resultTp = t2 - t1 ; // Elbette bu farklı süre olarak ifade etmek için, bir dönüşüme ihtiyacımız var. // O noktada da duration_cast yardımımıza yetişiyor std::chrono::milliseconds resultInMsec = std::chrono::duration_cast< std::chrono::milliseconds > ( resultTp ); std::cout << resultInMsec.count() << "\n"; |
Evet, sevgili yazılımperver dostlarım bir diğer önemli C++ 11 kütüphanesine ilişkin yazımın da sonuna gelmiş bulunuyoruz. Elbette, std::chrono’a ilişkin bütün kabiliyetler bunlar ile sınırlı değil ama umuyorum ki, bu yazılar size, daha detaylı bilgilere dalma ve kabiliyeti kullanmak için yardımcı olur. Bundan sonra da std::chrono ile ilgili bir kaç örnek kod parçasını da sizler ile paylaşıyor olacağım (çeşitli açık kaynak projelerden ya da kendi yazdığım kodlardan). Yazıyı bitirmeden önce, bu kütüphane ile ilgili izlediğim bir video ve bu videoya ilişkin sunumu da sizler ile paylaşıyorum. Bu yazı biraz haftalık video yazılarımdan da rol çaldı ama olsun 🙂
Videoya ilişkin sunuma da buradan ulaşabilirsiniz.
Bir sonraki yazımda görüşmek dileğiyle kendinize çok iyi bakın, sağlıkla kalın. Bol kodlu günler 🙂
Kaynaklar
- https://en.cppreference.com/w/cpp/chrono
- https://en.cppreference.com/w/cpp/chrono/time_point
- https://en.cppreference.com/w/cpp/chrono/high_resolution_clock
- https://en.cppreference.com/w/cpp/chrono/system_clock
- https://en.cppreference.com/w/cpp/chrono/steady_clock
- https://github.com/CppCon/CppCon2016/blob/master/Tutorials/A%20chrono%20Tutorial/A%20chrono%20Tutorial%20-%20Howard%20Hinnant%20-%20CppCon%202016.pdf
- http://antoniak.in/blog/2017/04/14/managing-time-std-chrono/
- https://www.informit.com/articles/article.aspx?p=1881386&seqNum=2
- https://www.modernescpp.com/index.php/the-three-clocks