0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Redis的LRU實(shí)現(xiàn)和應(yīng)用

馬哥Linux運(yùn)維 ? 來源:稀土掘金技術(shù)社區(qū) ? 2023-12-15 09:24 ? 次閱讀

編程中,計(jì)數(shù)器是一種基本但強(qiáng)大的工具,用于跟蹤和管理數(shù)據(jù)和資源。本文將深入探討不同類型的計(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算法來決定淘汰哪些數(shù)據(jù)以釋放內(nèi)存。這個(gè)算法的關(guān)鍵在于跟蹤每個(gè)對(duì)象最后一次被訪問的時(shí)間,但為了節(jié)約內(nèi)存,Redis并不使用完整的時(shí)間戳。相反,它采用了一種巧妙的方法:

時(shí)間戳精度的簡(jiǎn)化:Redis通過將時(shí)間戳除以一個(gè)固定的分辨率(例如1000毫秒)來降低其精度。

位數(shù)限制:Redis進(jìn)一步使用固定位數(shù)(例如24位)來存儲(chǔ)這些簡(jiǎn)化的時(shí)間戳。

按位與操作:通過使用按位與操作確保計(jì)數(shù)器在達(dá)到最大值后自動(dòng)從零開始,有效避免溢出。

這種方法在減少內(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位來存儲(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開始計(jì)數(shù),最大值就是 (2^{24} - 1)。

精度

Redis中,時(shí)間戳的精度被降低到秒。這意味著每個(gè)時(shí)間戳表示從某個(gè)固定點(diǎn)(通常是Unix紀(jì)元,即1970年1月1日0000 UTC)開始的秒數(shù)。

相對(duì)時(shí)間點(diǎn)

使用24位存儲(chǔ)時(shí)間戳,意味著能夠表示的最大秒數(shù)是16777215秒。換算成更直觀的時(shí)間單位:

天數(shù):194天

小時(shí)數(shù): 4655小時(shí)

因此,24位時(shí)間戳可以表示從某個(gè)起始點(diǎn)開始的大約194天內(nèi)的任意秒。

位運(yùn)算和精度

當(dāng)時(shí)間戳的值超過24位可以表示的最大值時(shí),由于按位與操作(與16777215按位與),時(shí)間戳將自動(dòng)回繞到0。這意味著每過194天左右,時(shí)間戳就會(huì)重置。重置后的時(shí)間戳值是從0開始的,但這并不影響Redis LRU算法的有效性,因?yàn)樵撍惴ㄖ饕P(guān)心的是對(duì)象相對(duì)于彼此的“最近使用”狀態(tài),而不是絕對(duì)的時(shí)間點(diǎn)。

總結(jié)

所以,在Redis的LRU算法中,雖然時(shí)間戳的精度仍然是秒,但由于使用24位存儲(chǔ),它只能表示大約194天的時(shí)間跨度。一旦超過這個(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)景中也非常有用,特別是在需要以內(nèi)存高效的方式存儲(chǔ)大量數(shù)據(jù)時(shí)。以下是兩個(gè)具體的應(yīng)用示例:

2.1 內(nèi)存效率的例子

假設(shè)你正在開發(fā)一個(gè)高性能的日志處理系統(tǒng),該系統(tǒng)需要跟蹤數(shù)百萬條日志記錄的時(shí)間戳。如果使用完整的64位時(shí)間戳(精確到毫秒),對(duì)于每個(gè)日志記錄,時(shí)間戳將占用8字節(jié)的存儲(chǔ)空間。這在大量數(shù)據(jù)的情況下會(huì)導(dǎo)致巨大的內(nèi)存消耗。

為了提高內(nèi)存效率,你可以選擇使用24位來表示時(shí)間戳。雖然這會(huì)減少時(shí)間的精度,但對(duì)于很多日志分析任務(wù)來說,這種精度已經(jīng)足夠。在這種情況下,每個(gè)時(shí)間戳只占用3字節(jié)的存儲(chǔ)空間。

通過這種方法,你可以顯著減少內(nèi)存使用,同時(shí)仍然保留了足夠的信息來進(jìn)行有效的日志分析。

不節(jié)省內(nèi)存情況

使用標(biāo)準(zhǔn)的64位時(shí)間戳(精確到毫秒)。

每個(gè)時(shí)間戳占用8字節(jié)。

總內(nèi)存使用量 = 100萬個(gè)事件 × 8字節(jié)/事件 = 800萬字節(jié)(約7.63 MB)。

節(jié)省內(nèi)存情況

使用24位時(shí)間戳(精確到某個(gè)更大的時(shí)間單位,比如分鐘)。

每個(gè)時(shí)間戳占用3字節(jié)。

總內(nèi)存使用量 = 100萬個(gè)事件 × 3字節(jié)/事件 = 300萬字節(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è)頁面最后一次被訪問的時(shí)間。通過將Unix時(shí)間戳轉(zhuǎn)換為以10分鐘為單位的近似值,可以減少存儲(chǔ)需求,同時(shí)仍然提供足夠的信息來有效管理緩存。

下面是一個(gè)舉例說明:

完整精度情況

使用完整的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)過的10分鐘間隔的數(shù)量。

時(shí)間戳示例:對(duì)于2021年3月31日14:15的時(shí)間,計(jì)算得到的20位時(shí)間戳可能是99000(這是一個(gè)假設(shè)的值,具體取決于基準(zhǔn)日期和計(jì)算方法)。

精度對(duì)比

完整精度時(shí)間戳能精確到秒。

近似精度時(shí)間戳精確到10分鐘,對(duì)于緩存淘汰決策而言,這通常是足夠的。例如,它可以用來判斷一個(gè)頁面是在最近一小時(shí)內(nèi)被訪問過,還是在更久之前。

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位)來存儲(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ǔ)資源受限的情況下,這種方法顯得尤為重要。通過簡(jiǎn)單的位運(yù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é)來存儲(chǔ)的情況,確實(shí)會(huì)面臨內(nèi)存分配和管理的挑戰(zhàn)。有幾種方法可以處理這種情況:

使用位域結(jié)構(gòu)體

在C或C++中,你可以使用位域(bit fields)在結(jié)構(gòu)體中精確指定每個(gè)成員的位數(shù)。例如,你可以定義一個(gè)24位的位域來存儲(chǔ)時(shí)間戳:


struct Timestamp {
    unsigned int time: 24;
};

這種方法允許你以3字節(jié)的存儲(chǔ)空間存儲(chǔ)時(shí)間戳,但它可能會(huì)引起端對(duì)齊(endian)問題,因此在跨平臺(tái)時(shí)需要小心處理。

使用字節(jié)數(shù)組你也可以使用一個(gè)字節(jié)數(shù)組(如3個(gè)char或uint8_t)來存儲(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)置類型并接受一些空間浪費(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)的整型類型可能是更好的選擇。

鏈接: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)注明出處。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 算法
    +關(guān)注

    關(guān)注

    23

    文章

    4588

    瀏覽量

    92505
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    2976

    瀏覽量

    73815
  • 計(jì)數(shù)器
    +關(guān)注

    關(guān)注

    32

    文章

    2253

    瀏覽量

    94287
  • Redis
    +關(guān)注

    關(guān)注

    0

    文章

    370

    瀏覽量

    10830

原文標(biāo)題:從Redis的LRU實(shí)現(xiàn)到內(nèi)存效率和位操作

文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    LRU緩存模塊最佳實(shí)踐

    lru模塊,可以方便地實(shí)現(xiàn)LRU緩存。 基礎(chǔ)用法 Cargo.toml引入 lru 模塊 lru = "0.10.0" 創(chuàng)建一個(gè)
    的頭像 發(fā)表于 09-30 16:47 ?843次閱讀

    Redis Stream應(yīng)用案例

    的基本使用介紹和設(shè)計(jì)理念可以看我之前的一篇文章(Redis Stream簡(jiǎn)介)。Redis Stream本質(zhì)上是在Redis內(nèi)核上(非Redis Module)
    發(fā)表于 06-26 17:15

    Redis Cluster的基本原理及實(shí)現(xiàn)細(xì)節(jié)

    Redis Cluster的基本原理和架構(gòu) Redis Cluster是分布式Redis實(shí)現(xiàn)。隨著Redis版本的更替,以及各種已知bug
    發(fā)表于 09-28 19:09 ?0次下載
    <b class='flag-5'>Redis</b> Cluster的基本原理及<b class='flag-5'>實(shí)現(xiàn)</b>細(xì)節(jié)

    redis設(shè)計(jì)與實(shí)現(xiàn)

    redis
    發(fā)表于 06-20 14:44 ?0次下載

    談?wù)?b class='flag-5'>Redis怎樣配置實(shí)現(xiàn)主從復(fù)制?

    之前總結(jié)過redis的持久化機(jī)制:深度剖析Redis持久化機(jī)制,持久化機(jī)制主要解決redis數(shù)據(jù)單機(jī)備份問題;redis的高可用需要考慮數(shù)據(jù)的多機(jī)備份,多機(jī)備份通過主從復(fù)制來
    發(fā)表于 01-31 11:31 ?631次閱讀

    Redis實(shí)現(xiàn)限流的三種方式分享

    當(dāng)然,限流有許多種實(shí)現(xiàn)的方式,Redis具有很強(qiáng)大的功能,我用Redis實(shí)踐了三種的實(shí)現(xiàn)方式,可以較為簡(jiǎn)單的實(shí)現(xiàn)其方式。
    的頭像 發(fā)表于 02-22 09:52 ?1016次閱讀

    設(shè)計(jì)并實(shí)現(xiàn)一個(gè)滿足LRU約束的數(shù)據(jù)結(jié)構(gòu)

    LRUCache(int capacity)` 以 **「正整數(shù)」** 作為容量 `capacity` 初始化 `LRU` 緩存
    的頭像 發(fā)表于 06-07 17:05 ?949次閱讀
    設(shè)計(jì)并<b class='flag-5'>實(shí)現(xiàn)</b>一個(gè)滿足<b class='flag-5'>LRU</b>約束的數(shù)據(jù)結(jié)構(gòu)

    RedisLRU與LFU算法實(shí)現(xiàn)

    Redis是一款基于內(nèi)存的高性能NoSQL數(shù)據(jù)庫,數(shù)據(jù)都緩存在內(nèi)存里, 這使得Redis可以每秒輕松地處理數(shù)萬的讀寫請(qǐng)求。
    的頭像 發(fā)表于 07-11 09:48 ?1031次閱讀
    <b class='flag-5'>Redis</b>的<b class='flag-5'>LRU</b>與LFU算法<b class='flag-5'>實(shí)現(xiàn)</b>

    redis分布式鎖如何實(shí)現(xiàn)

    Redis分布式鎖是一種基于Redis實(shí)現(xiàn)的機(jī)制,可以用于多個(gè)進(jìn)程或多臺(tái)服務(wù)器之間對(duì)共享資源的并發(fā)訪問控制。在分布式系統(tǒng)中,由于多個(gè)進(jìn)程或多臺(tái)服務(wù)器同時(shí)訪問共享資源,可能會(huì)發(fā)生數(shù)據(jù)競(jìng)爭(zhēng)和資源沖突
    的頭像 發(fā)表于 11-16 11:29 ?489次閱讀

    Redis工具集的實(shí)現(xiàn)和使用

    Redis 基本上是互聯(lián)網(wǎng)公司必備的工具了,Redis的應(yīng)用場(chǎng)景實(shí)在太多了,但是有很多相似的功能如果每個(gè)項(xiàng)目都要實(shí)現(xiàn)一遍就顯得太麻煩了,所以為了方便,我打算開發(fā)一個(gè)基于 Redis
    的頭像 發(fā)表于 12-03 17:32 ?1169次閱讀
    <b class='flag-5'>Redis</b>工具集的<b class='flag-5'>實(shí)現(xiàn)</b>和使用

    Java redis鎖怎么實(shí)現(xiàn)

    在Java中實(shí)現(xiàn)Redis鎖涉及到以下幾個(gè)方面:Redis的安裝配置、Redis連接池的使用、Redis數(shù)據(jù)結(jié)構(gòu)的選擇、
    的頭像 發(fā)表于 12-04 10:47 ?1091次閱讀

    redis的淘汰策略

    的寫入。 Redis的淘汰策略主要有以下幾種: LRU(Least Recently Used,最近最少使用): 這是Redis默認(rèn)的淘汰策略。當(dāng)內(nèi)存空間不足時(shí),Redis會(huì)選擇最近最
    的頭像 發(fā)表于 12-04 16:23 ?515次閱讀

    redis hash底層實(shí)現(xiàn)原理

    數(shù)據(jù)結(jié)構(gòu)是如何實(shí)現(xiàn)的呢?本文將詳細(xì)介紹Redis哈希底層的實(shí)現(xiàn)原理。 在Redis中,每個(gè)哈希都是由一個(gè)類似于字典(Dictionary)的結(jié)構(gòu)實(shí)現(xiàn)
    的頭像 發(fā)表于 12-04 16:27 ?544次閱讀

    redislru原理

    Redis是一種基于內(nèi)存的鍵值數(shù)據(jù)庫,它使用了LRU(Least Recently Used)算法來進(jìn)行緩存的數(shù)據(jù)淘汰。LRU算法的核心思想是最近最少使用的數(shù)據(jù)將會(huì)在未來也不常用,因此應(yīng)該優(yōu)先
    的頭像 發(fā)表于 12-05 09:56 ?591次閱讀

    redis數(shù)據(jù)結(jié)構(gòu)的底層實(shí)現(xiàn)

    Redis是一種內(nèi)存鍵值數(shù)據(jù)庫,常用于緩存、消息隊(duì)列、實(shí)時(shí)數(shù)據(jù)分析等場(chǎng)景。它的高性能得益于其精心設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu)和底層實(shí)現(xiàn)。本文將詳細(xì)介紹Redis常用的數(shù)據(jù)結(jié)構(gòu)和它們的底層實(shí)現(xiàn)
    的頭像 發(fā)表于 12-05 10:14 ?575次閱讀