与 ext2 相比,Ext4 表现出意外的写入延迟差异

与 ext2 相比,Ext4 表现出意外的写入延迟差异

我有一个在嵌入式系统上运行的延迟敏感应用程序,并且我发现写入同一物理设备上的 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 分区上运行测试时,前一百批 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 似乎没有太大影响

  • 屏障=0/1 似乎并不重要

  • nodelalloc 有一点帮助,但不足以消除延迟变化。

  • ext4 分区仅占 10% 左右。

感谢您对这个问题的任何想法!

答案1

一个词:写日记。

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

当您谈论嵌入式时,假设您有某种形式的闪存?闪存上的日志式 ext4 的性能非常突出。推荐使用Ext2。

如果您必须使用 ext4,这里有一篇关于禁用日志记录和调整文件系统以不记录日志的好文章: http://fenidik.blogspot.com/2010/03/ext4-disable-journal.html

相关内容