Ext4

與 ext2 相比,Ext4 表現出意外的寫入延遲差異

  • July 5, 2013

我有一個在嵌入式系統上執行的對延遲敏感的應用程序,我發現在同一物理設備上寫入 ext4 分區和 ext2 分區之間存在一些差異。具體來說,我看到在記憶體映射上執行許多小更新時會出現間歇性延遲,但僅限於 ext4。我已經嘗試了一些通過使用不同選項安裝 ext4 來提高性能(尤其是延遲變化)的常用技巧,並確定了這些安裝選項:

mount -t ext4 -o remount,rw,noatime,nodiratime,user_xattr,barrier=1,data=ordered,nodelalloc /dev/mmcblk0p6 /media/mmc/data

barrier=0似乎沒有提供任何改進。

對於 ext2 分區,使用以下標誌:

/dev/mmcblk0p3 on /media/mmc/data2 type ext2 (rw,relatime,errors=continue)

這是我正在使用的測試程序:

#include <stdio.h>
#include <cstring>
#include <cstdio>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <cstdlib>
#include <time.h>
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>

uint32_t getMonotonicMillis()
{
   struct timespec time;
   clock_gettime(CLOCK_MONOTONIC, &time);
   uint32_t millis = (time.tv_nsec/1000000)+(time.tv_sec*1000);
   return millis;
}

void tune(const char* name, const char* value)
{
   FILE* tuneFd = fopen(name, "wb+");
   fwrite(value, strlen(value), 1, tuneFd);
   fclose(tuneFd);
}

void tuneForFasterWriteback()
{
   tune("/proc/sys/vm/dirty_writeback_centisecs", "25");
   tune("/proc/sys/vm/dirty_expire_centisecs", "200");
   tune("/proc/sys/vm/dirty_background_ratio", "5");
   tune("/proc/sys/vm/dirty_ratio", "40");
   tune("/proc/sys/vm/swappiness", "0");
}


class MMapper
{
public:
   const char* _backingPath;
   int _blockSize;
   int _blockCount;
   bool _isSparse;

   int _size;
   uint8_t *_data;
   int _backingFile;
   uint8_t *_buffer;

   MMapper(const char *backingPath, int blockSize, int blockCount, bool isSparse) :
       _backingPath(backingPath),
       _blockSize(blockSize),
       _blockCount(blockCount),
       _isSparse(isSparse),
       _size(blockSize*blockCount)
   {
       printf("Creating MMapper for %s with block size %i, block count %i and it is%s sparse\n",
               _backingPath,
               _blockSize,
               _blockCount,
               _isSparse ? "" : " not");
       _backingFile = open(_backingPath, O_CREAT | O_RDWR | O_TRUNC, 0600);

       if(_isSparse)
       {
           ftruncate(_backingFile, _size);
       }
       else
       {
           posix_fallocate(_backingFile, 0, _size);
           fsync(_backingFile);
       }
       _data = (uint8_t*)mmap(NULL, _size, PROT_READ | PROT_WRITE, MAP_SHARED, _backingFile, 0);
       _buffer = new uint8_t[blockSize];
       printf("MMapper %s created!\n", _backingPath);
   }

   ~MMapper()
   {
       printf("Destroying MMapper %s\n", _backingPath);
       if(_data)
       {
           msync(_data, _size, MS_SYNC);
           munmap(_data, _size);
           close(_backingFile);
           _data = NULL;
           delete [] _buffer;
           _buffer = NULL;
       }
       printf("Destroyed!\n");
   }

   void writeBlock(int whichBlock)
   {
       memcpy(&_data[whichBlock*_blockSize], _buffer, _blockSize);
   }
};



int main(int argc, char** argv)
{
   tuneForFasterWriteback();

   int timeBetweenBlocks = 40*1000;
   //2^12 x 2^16  = 2^28  = 2^10*2^10*2^8 = 256MB
   int blockSize = 4*1024;
   int blockCount = 64*1024;
   int bigBlockCount = 2*64*1024;
   int iterations = 25*40*60; //25 counts simulates 1 layer for one second, 5 minutes here


   uint32_t startMillis = getMonotonicMillis();

   int measureIterationCount = 50;

   MMapper mapper("sparse", blockSize, bigBlockCount, true);
   for(int i=0; i<iterations; i++)
   {
       int block = rand()%blockCount;
       mapper.writeBlock(block);
       usleep(timeBetweenBlocks);
       if(i%measureIterationCount==measureIterationCount-1)
       {
           uint32_t elapsedTime = getMonotonicMillis()-startMillis;
           printf("%i took %u\n", i, elapsedTime);
           startMillis = getMonotonicMillis();
       }
   }

   return 0;
}

相當簡單的測試案例。我不指望非常準確的時機,我對總體趨勢更感興趣。在執行測試之前,我通過執行以下操作確保系統處於相當穩定的狀態,幾乎沒有發生磁碟寫入活動:

watch grep -e Writeback: -e Dirty: /proc/meminfo

幾乎沒有磁碟活動。這也可以通過在 的輸出中看到等待列中的 0 或 1 來驗證vmstat 1。我還在執行測試之前立即執行同步。請注意也提供給 vm 子系統的積極回寫參數。

當我在 ext2 分區上執行測試時,前 100 批 50 次寫入產生了一個不錯的 2012 毫秒,標準偏差為 8 毫秒。當我在 ext4 分區上執行相同的測試時,我看到平均為 2151 毫秒,但標準偏差為 409 毫秒。我主要關心的是延遲的變化,所以這很令人沮喪。ext4 分區測試的實際時間如下所示:

{2372, 3291, 2025, 2020, 2019, 2019, 2019, 2019, 2019, 2020, 2019, 2019, 2019, 2019, 2020, 2021, 2037, 2019, 2021, 2021, 2020, 2152, 2020, 2021, 2019, 2019, 2020, 2153, 2020, 2020, 2021, 2020, 2020, 2020, 2043, 2021, 2019, 2019, 2019, 2053, 2019, 2020, 2023, 2020, 2020, 2021, 2019, 2022, 2019, 2020, 2020, 2020, 2019, 2020, 2019, 2019, 2021, 2023, 2019, 2023, 2025, 3574, 2019, 3013, 2019, 2021, 2019, 3755, 2021, 2020, 2020, 2019, 2020, 2020, 2019, 2799, 2020, 2019, 2019, 2020, 2020, 2143, 2088, 2026, 2017, 2310, 2020, 2485, 4214, 2023, 2020, 2023, 3405, 2020, 2019, 2020, 2020, 2019, 2020, 3591}

不幸的是,我不知道 ext2 是否是最終解決方案的一個選項,所以我試圖了解文件系統之間的行為差異。我很可能至少可以控制用於掛載 ext4 系統並對其進行調整的標誌。

  • noatime/nodiratime 似乎沒有太大影響
  • barrier=0/1 似乎無關緊要
  • nodelalloc 有點幫助,但在消除延遲變化方面做得還不夠。
  • ext4 分區只有大約 10% 已滿。

感謝您對這個問題的任何想法!

一個詞:日記。

http://www.thegeekstuff.com/2011/05/ext2-ext3-ext4/

當您談論嵌入式即時通訊時,假設您有某種形式的快閃記憶體?快閃記憶體上的日誌記錄 ext4 的性能非常高。推薦使用 Ext2。

如果您必須使用 ext4,這裡有一篇關於禁用日誌和調整 fs 以實現無日誌的好文章:http: //fenidik.blogspot.com/2010/03/ext4-disable-journal.html

引用自:https://unix.stackexchange.com/questions/81943