Ext4 exibe variância de latência de gravação inesperada vs. ext2

1

Eu tenho um aplicativo sensível à latência em execução em um sistema embarcado, e estou vendo alguma discrepância entre gravar uma partição ext4 e uma partição ext2 no mesmo dispositivo físico. Especificamente, vejo atrasos intermitentes ao executar muitas pequenas atualizações em um mapa de memória, mas apenas no ext4. Eu tentei o que parece ser alguns dos truques habituais para melhorar o desempenho (especialmente as variações de latência) montando ext4 com opções diferentes e decidiram sobre essas opções de montagem:

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

barrier=0 não parece fornecer nenhuma melhoria.

Para a partição ext2, os seguintes sinalizadores são usados:

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

Aqui está o programa de teste que estou usando:

#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;
}

Caso de teste bastante simplista. Eu não espero um timing muito preciso, estou mais interessado em tendências gerais. Antes de executar os testes, assegurei-me de que o sistema está em um estado razoavelmente estável, com muito pouca atividade de gravação de disco ocorrendo fazendo algo como:

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

Existe muito pouca ou nenhuma atividade de disco. Isso também é verificado ao ver 0 ou 1 na coluna de espera da saída de vmstat 1 . Eu também faço uma sincronização imediatamente antes de executar o teste. Observe os parâmetros agressivos de write-back sendo fornecidos também ao subsistema vm.

Quando executo o teste na partição ext2, os primeiros cem lotes de cinquenta gravações resultam em um sólido sólido de 2012 ms com um desvio padrão de 8 ms. Quando executo o mesmo teste na partição ext4, vejo uma média de 2151 ms, mas um desvio padrão abismal de 409 ms. Minha principal preocupação é a variação na latência, então isso é frustrante. Os tempos reais para o teste de partição ext4 são assim:

{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}

Infelizmente, não sei se o ext2 é uma opção para a solução final, por isso estou tentando entender a diferença de comportamento entre os sistemas de arquivos. Eu provavelmente teria controle sobre pelo menos as bandeiras sendo usadas para montar o sistema ext4 e ajustá-las.

  • noatime / nodiratime não parece fazer muito sentido

  • barrier = 0/1 não parece importar

  • nodelalloc ajuda um pouco, mas não faz o suficiente para suavizar a variação de latência.

  • A partição ext4 está apenas 10% cheia.

Obrigado por qualquer opinião sobre este assunto!

    
por J Trana 05.07.2013 / 23:32

1 resposta

1

Uma palavra: registro no diário.

link

Como você fala sobre im incorporado, você tem alguma forma de memória flash? O desempenho é muito espetacular no ext4 em memória flash. Ext2 é recomendado.

Aqui está um bom artigo sobre como desabilitar o registro no diário e ajustar o fs para o não registro no diário se você precisar usar o ext4: link

    
por 06.07.2013 / 00:06