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!