在編程中,計(jì)數(shù)器是一種基本但強(qiáng)大的工具,用于跟蹤和管理數(shù)據(jù)和資源。本文將深入探討不同類(lèi)型的計(jì)數(shù)器的應(yīng)用,從Redis的LRU(最近最少使用)緩存淘汰算法的實(shí)現(xiàn),到如何在內(nèi)存受限的環(huán)境中有效地使用計(jì)數(shù)器,再到普通計(jì)數(shù)器的巧妙應(yīng)用。
1. Redis的LRU實(shí)現(xiàn)
Redis,作為一個(gè)高性能的鍵值存儲(chǔ)系統(tǒng),使用LRU算法來(lái)決定淘汰哪些數(shù)據(jù)以釋放內(nèi)存。這個(gè)算法的關(guān)鍵在于跟蹤每個(gè)對(duì)象最后一次被訪問(wèn)的時(shí)間,但為了節(jié)約內(nèi)存,Redis并不使用完整的時(shí)間戳。相反,它采用了一種巧妙的方法:
時(shí)間戳精度的簡(jiǎn)化:Redis通過(guò)將時(shí)間戳除以一個(gè)固定的分辨率(例如1000毫秒)來(lái)降低其精度。
位數(shù)限制:Redis進(jìn)一步使用固定位數(shù)(例如24位)來(lái)存儲(chǔ)這些簡(jiǎn)化的時(shí)間戳。
按位與操作:通過(guò)使用按位與操作確保計(jì)數(shù)器在達(dá)到最大值后自動(dòng)從零開(kāi)始,有效避免溢出。
這種方法在減少內(nèi)存占用和保持時(shí)間戳更新效率之間取得了平衡,從而使LRU算法的實(shí)現(xiàn)既高效又節(jié)省空間。
Redis計(jì)算LRU時(shí)間的源碼如下(6.0.6)
#define LRU_BITS 24 #define LRU_CLOCK_MAX ((1<lru */ #define LRU_CLOCK_RESOLUTION 1000 * in a reduced-bits format that can be used to set and check the * object->lru field of redisObject structures. */ unsigned int getLRUClock(void) { return (mstime()/LRU_CLOCK_RESOLUTION) & LRU_CLOCK_MAX; }
在Redis的LRU算法實(shí)現(xiàn)中,使用24位來(lái)存儲(chǔ)時(shí)間戳是一種權(quán)衡內(nèi)存使用和精度的方法。下面我們分析這種方法的具體細(xì)節(jié):
24位時(shí)間戳的最大值:
首先,24位可以表示的最大值是 (2^{24} - 1),即16777215。這是因?yàn)槊總€(gè)二進(jìn)制位有兩個(gè)可能的值(0或1),所以24位可以表示 (2^{24}) 個(gè)不同的值,從0開(kāi)始計(jì)數(shù),最大值就是 (2^{24} - 1)。
精度
Redis中,時(shí)間戳的精度被降低到秒。這意味著每個(gè)時(shí)間戳表示從某個(gè)固定點(diǎn)(通常是Unix紀(jì)元,即1970年1月1日0000 UTC)開(kāi)始的秒數(shù)。
相對(duì)時(shí)間點(diǎn)
使用24位存儲(chǔ)時(shí)間戳,意味著能夠表示的最大秒數(shù)是16777215秒。換算成更直觀的時(shí)間單位:
天數(shù):194天
小時(shí)數(shù): 4655小時(shí)
因此,24位時(shí)間戳可以表示從某個(gè)起始點(diǎn)開(kāi)始的大約194天內(nèi)的任意秒。
位運(yùn)算和精度
當(dāng)時(shí)間戳的值超過(guò)24位可以表示的最大值時(shí),由于按位與操作(與16777215按位與),時(shí)間戳將自動(dòng)回繞到0。這意味著每過(guò)194天左右,時(shí)間戳就會(huì)重置。重置后的時(shí)間戳值是從0開(kāi)始的,但這并不影響Redis LRU算法的有效性,因?yàn)樵撍惴ㄖ饕P(guān)心的是對(duì)象相對(duì)于彼此的“最近使用”狀態(tài),而不是絕對(duì)的時(shí)間點(diǎn)。
總結(jié)
所以,在Redis的LRU算法中,雖然時(shí)間戳的精度仍然是秒,但由于使用24位存儲(chǔ),它只能表示大約194天的時(shí)間跨度。一旦超過(guò)這個(gè)時(shí)間跨度,時(shí)間戳就會(huì)回繞。這種設(shè)計(jì)在維持足夠精度的同時(shí),大幅減少了每個(gè)對(duì)象的內(nèi)存占用,非常適合于內(nèi)存受限的高效緩存系統(tǒng)。
2. 內(nèi)存效率和時(shí)間戳的近似表示
除了Redis的應(yīng)用,固定位數(shù)的計(jì)數(shù)器在其他許多場(chǎng)景中也非常有用,特別是在需要以?xún)?nèi)存高效的方式存儲(chǔ)大量數(shù)據(jù)時(shí)。以下是兩個(gè)具體的應(yīng)用示例:
2.1 內(nèi)存效率的例子
假設(shè)你正在開(kāi)發(fā)一個(gè)高性能的日志處理系統(tǒng),該系統(tǒng)需要跟蹤數(shù)百萬(wàn)條日志記錄的時(shí)間戳。如果使用完整的64位時(shí)間戳(精確到毫秒),對(duì)于每個(gè)日志記錄,時(shí)間戳將占用8字節(jié)的存儲(chǔ)空間。這在大量數(shù)據(jù)的情況下會(huì)導(dǎo)致巨大的內(nèi)存消耗。
為了提高內(nèi)存效率,你可以選擇使用24位來(lái)表示時(shí)間戳。雖然這會(huì)減少時(shí)間的精度,但對(duì)于很多日志分析任務(wù)來(lái)說(shuō),這種精度已經(jīng)足夠。在這種情況下,每個(gè)時(shí)間戳只占用3字節(jié)的存儲(chǔ)空間。
通過(guò)這種方法,你可以顯著減少內(nèi)存使用,同時(shí)仍然保留了足夠的信息來(lái)進(jìn)行有效的日志分析。
不節(jié)省內(nèi)存情況
使用標(biāo)準(zhǔn)的64位時(shí)間戳(精確到毫秒)。
每個(gè)時(shí)間戳占用8字節(jié)。
總內(nèi)存使用量 = 100萬(wàn)個(gè)事件 × 8字節(jié)/事件 = 800萬(wàn)字節(jié)(約7.63 MB)。
節(jié)省內(nèi)存情況
使用24位時(shí)間戳(精確到某個(gè)更大的時(shí)間單位,比如分鐘)。
每個(gè)時(shí)間戳占用3字節(jié)。
總內(nèi)存使用量 = 100萬(wàn)個(gè)事件 × 3字節(jié)/事件 = 300萬(wàn)字節(jié)(約2.86 MB)。
節(jié)省效果
節(jié)省了約4.77 MB的內(nèi)存。
對(duì)于某些應(yīng)用(如日志分析),精確到分鐘可能已足夠,因此這種方法既節(jié)省內(nèi)存又能提供所需的時(shí)間信息。
2.2 時(shí)間戳的近似表示
考慮一個(gè)網(wǎng)站緩存系統(tǒng),它需要記錄每個(gè)頁(yè)面最后一次被訪問(wèn)的時(shí)間。通過(guò)將Unix時(shí)間戳轉(zhuǎn)換為以10分鐘為單位的近似值,可以減少存儲(chǔ)需求,同時(shí)仍然提供足夠的信息來(lái)有效管理緩存。
下面是一個(gè)舉例說(shuō)明:
完整精度情況
使用完整的32位Unix時(shí)間戳(精確到秒)。
時(shí)間戳示例:1617181723(代表2021年3月31日1423 UTC)。
近似精度情況
使用簡(jiǎn)化的20位時(shí)間戳(以10分鐘為單位)。
假設(shè)我們以2021年1月1日為基準(zhǔn),計(jì)算從那時(shí)起經(jīng)過(guò)的10分鐘間隔的數(shù)量。
時(shí)間戳示例:對(duì)于2021年3月31日14:15的時(shí)間,計(jì)算得到的20位時(shí)間戳可能是99000(這是一個(gè)假設(shè)的值,具體取決于基準(zhǔn)日期和計(jì)算方法)。
精度對(duì)比
完整精度時(shí)間戳能精確到秒。
近似精度時(shí)間戳精確到10分鐘,對(duì)于緩存淘汰決策而言,這通常是足夠的。例如,它可以用來(lái)判斷一個(gè)頁(yè)面是在最近一小時(shí)內(nèi)被訪問(wèn)過(guò),還是在更久之前。
3. 循環(huán)計(jì)數(shù)器的實(shí)際應(yīng)用
利用固定位數(shù)和按位與操作實(shí)現(xiàn)高效的循環(huán)計(jì)數(shù)器
在編程中,經(jīng)常需要跟蹤特定的事件或狀態(tài)的次數(shù),尤其是在資源受限(如內(nèi)存或存儲(chǔ)空間有限)的環(huán)境中。傳統(tǒng)的方法可能會(huì)涉及檢查計(jì)數(shù)器是否達(dá)到某個(gè)值,然后手動(dòng)將其重置。然而,這種方法既繁瑣又容易出錯(cuò)。幸運(yùn)的是,有一種更優(yōu)雅、高效的方法可以實(shí)現(xiàn)同樣的目標(biāo):使用固定位數(shù)的計(jì)數(shù)器結(jié)合按位與操作。
固定位數(shù)計(jì)數(shù)器的原理
固定位數(shù)計(jì)數(shù)器的概念很簡(jiǎn)單。就是選擇一個(gè)特定的位數(shù)(比如16位、24位或32位)來(lái)存儲(chǔ)計(jì)數(shù)器的值。這個(gè)選擇直接決定了計(jì)數(shù)器的最大值,計(jì)數(shù)器的最大值為 (2^{位數(shù)} - 1)。例如,一個(gè)24位計(jì)數(shù)器的最大值是 (2^{24} - 1 = 16777215)。
為何選擇按位與操作
按位與操作 (&) 是一種基本的位運(yùn)算,它對(duì)兩個(gè)數(shù)的每一位進(jìn)行比較,只有當(dāng)相同位置的兩個(gè)位都為1時(shí),結(jié)果的那位才為1。在這種用法中,它的作用是確保計(jì)數(shù)器值在達(dá)到其最大值后自動(dòng)歸零。
實(shí)現(xiàn)步驟
確定位數(shù):首先,確定你需要的計(jì)數(shù)器位數(shù)。這將取決于你的特定應(yīng)用和所需的最大計(jì)數(shù)范圍。
計(jì)算掩碼值:計(jì)算掩碼值,即計(jì)數(shù)器的最大值。對(duì)于一個(gè)N位計(jì)數(shù)器,掩碼值為 (2^N - 1)。
應(yīng)用按位與:在增加計(jì)數(shù)器值時(shí)使用按位與操作,以確保計(jì)數(shù)器在達(dá)到最大值后自動(dòng)回繞。
示例代碼
假設(shè)我們使用一個(gè)16位計(jì)數(shù)器:
#include#define COUNTER_BITS 16 #define COUNTER_MAX ((1 << COUNTER_BITS) - 1) int main() { unsigned int counter = 0; for(int i = 0; i < 70000; i++) { counter = (counter + 1) & COUNTER_MAX; // 可以在這里執(zhí)行其他操作 } printf("Final counter value: %u ", counter); return 0; }
在這個(gè)例子中,計(jì)數(shù)器將在65535之后回繞到0,這種方法適用于需要循環(huán)計(jì)數(shù)器的場(chǎng)景,如緩存淘汰、時(shí)間標(biāo)記或狀態(tài)跟蹤。
結(jié)論
使用固定位數(shù)和按位與操作的循環(huán)計(jì)數(shù)器是一種節(jié)省資源、減少錯(cuò)誤和提高代碼效率的強(qiáng)大工具。特別是在內(nèi)存和存儲(chǔ)資源受限的情況下,這種方法顯得尤為重要。通過(guò)簡(jiǎn)單的位運(yùn)算,我們可以?xún)?yōu)雅地實(shí)現(xiàn)計(jì)數(shù)器的自動(dòng)回繞功能,從而使我們的代碼更加簡(jiǎn)潔和健壯。
4. 如何有效存儲(chǔ)固定位數(shù)
在大多數(shù)編程環(huán)境中,基本的整型(如int)通常占用4字節(jié)(32位)。對(duì)于只需要3字節(jié)來(lái)存儲(chǔ)的情況,確實(shí)會(huì)面臨內(nèi)存分配和管理的挑戰(zhàn)。有幾種方法可以處理這種情況:
使用位域結(jié)構(gòu)體
在C或C++中,你可以使用位域(bit fields)在結(jié)構(gòu)體中精確指定每個(gè)成員的位數(shù)。例如,你可以定義一個(gè)24位的位域來(lái)存儲(chǔ)時(shí)間戳:
struct Timestamp { unsigned int time: 24; };
這種方法允許你以3字節(jié)的存儲(chǔ)空間存儲(chǔ)時(shí)間戳,但它可能會(huì)引起端對(duì)齊(endian)問(wèn)題,因此在跨平臺(tái)時(shí)需要小心處理。
使用字節(jié)數(shù)組你也可以使用一個(gè)字節(jié)數(shù)組(如3個(gè)char或uint8_t)來(lái)存儲(chǔ)這個(gè)值。在這種情況下,你需要手動(dòng)處理值的設(shè)置和解析:
uint8_t timestamp[3]; // 設(shè)置值(示例) timestamp[0] = (value >> 16) & 0xFF; // 最高有效字節(jié) timestamp[1] = (value >> 8) & 0xFF; // 中間字節(jié) timestamp[2] = value & 0xFF; // 最低有效字節(jié) // 解析值 uint32_t recovered_value = (timestamp[0] << 16) | (timestamp[1] << 8) | timestamp[2];
這種方法給予了你更多控制,但增加了代碼的復(fù)雜性。
使用內(nèi)置類(lèi)型并接受一些空間浪費(fèi)
有時(shí),簡(jiǎn)單地使用標(biāo)準(zhǔn)的4字節(jié)整型(如int或uint32_t)可能是更實(shí)際的選擇,即使這意味著你不會(huì)完全利用所有的位。雖然這會(huì)浪費(fèi)一些空間,但代碼會(huì)更簡(jiǎn)單、更清晰,并且更容易維護(hù)。這在不是極度內(nèi)存受限的環(huán)境下通常是可接受的。
選擇方法
選擇哪種方法取決于你的具體需求。如果內(nèi)存效率至關(guān)重要,并且你能夠處理額外的復(fù)雜性,那么使用位域或字節(jié)數(shù)組可能是合適的。如果代碼的可讀性和維護(hù)性更重要,那么簡(jiǎn)單地使用標(biāo)準(zhǔn)的整型類(lèi)型可能是更好的選擇。
鏈接:https://juejin.cn/post/7312035412159528969
(版權(quán)歸原作者所有,侵刪)
原文標(biāo)題:從Redis的LRU實(shí)現(xiàn)到內(nèi)存效率和位操作
文章出處:【微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
-
算法
+關(guān)注
關(guān)注
23文章
4601瀏覽量
92677 -
內(nèi)存
+關(guān)注
關(guān)注
8文章
3004瀏覽量
73900 -
計(jì)數(shù)器
+關(guān)注
關(guān)注
32文章
2254瀏覽量
94374 -
Redis
+關(guān)注
關(guān)注
0文章
371瀏覽量
10850
原文標(biāo)題:從Redis的LRU實(shí)現(xiàn)到內(nèi)存效率和位操作
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論