Ext4는 ext2와 비교하여 예상치 못한 쓰기 대기 시간 차이를 나타냅니다.

Ext4는 ext2와 비교하여 예상치 못한 쓰기 대기 시간 차이를 나타냅니다.

임베디드 시스템에서 대기 시간에 민감한 응용 프로그램을 실행하고 있는데 동일한 물리적 장치에서 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개 쓰기 배치는 표준 편차가 8밀리초인 2012밀리초를 기록했습니다. 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은 중요하지 않은 것 같습니다.

  • nodellalloc은 약간의 도움이 되지만 대기 시간 변경을 제거하기에는 충분하지 않습니다.

  • 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

관련 정보