Про тестирование оперативной памяти

Вчера осуществил задуманное и накатал простенький тест памяти, который запускается прямо из под Windows.

В качестве подопытного кролика была планка на 16GB, с которой возникла заминка (см. мой комментарий за 2017-10-20).

Как положено, сначала была простейшая однопоточная реализация, которая гоняла в цикле код вида:

void MemTest_001(void* const pvBeg,void* const pvEnd)
{
 //последовательное заполнение одним и тем же значением

 typedef unsigned char value_type;
 
 const size_t nValues=(((const char*)pvEnd)-((const char*)pvBeg))/sizeof(value_type);

 value_type* const beg=reinterpret_cast<value_type*>(pvBeg);
 value_type* const end=beg+nValues;

 value_type v=0;

 for(;;++v)
 {
  std::fill(beg,end,v);

  for(const value_type* p=beg;p!=end;++p)
  {
   const value_type m=*p;
   
   if(m==v)
    continue;

   structure::str_formatter
    fmsg("bad value [%1]. expected value [%2]. position [%3].");
  
   fmsg<<m<<v<<size_t(p-beg);

   throw std::runtime_error(fmsg.str());
  }//for

  if(v==std::numeric_limits<value_type>::max())
   break;
 }//for v
}//MemTest_001

Запустил на 16GB и не дождался пока этот тест отработает до конца.

Распределил работу этого теста между десятью потоками (процессор 6950X). Отработало приблизительно минут за двенадцать — обвязка тестов убогая, поэтому время измерялось на глаз.

Если честно, я не ожидал, что все будет так медленно.

Потом я накатал несколько других тестов, которые последовательно заполняют память. Отрабатывают, но, чувствую — что-то все не то.

Полез в сеть, почитать про настоящий MemTest. Нашел его исходный код на github.

Прочитав его философию тестирования памяти, понял как можно обмануть кэши процессора.

Оказывается это не так и сложно. Достаточно заполнять буфер не последовательно за один проход, а с небольшим шагом и за несколько проходов:

void MemTest_002_modx(void* const pvBeg,void* const pvEnd)
{
 typedef unsigned char value_type;
 
 const size_t nValues=(((const char*)pvEnd)-((const char*)pvBeg))/sizeof(value_type);

 value_type* const beg=reinterpret_cast<value_type*>(pvBeg);
 value_type* const end=beg+nValues;

 size_t c_step=37;

 for(value_type s=0;;++s)
 {
  value_type v=s;

  for(size_t iStep=0;iStep!=c_step;++iStep)
  {
   for(value_type* p=beg+iStep;p<end;p+=c_step,++v)
    (*p)=v;
  }

  v=s;

  for(size_t iStep=0;iStep!=c_step;++iStep)
  {
   for(value_type* p=beg+iStep;p<end;p+=c_step,++v)
   {
    const value_type m=*p;
   
    if(m==v)
     continue;

    structure::str_formatter
     fmsg("bad value [%1]. expected value [%2]. position [%3].");
  
    fmsg<<m<<v<<size_t(p-beg);

    throw std::runtime_error(fmsg.str());
   }//for
  }//for
  
  if(s==std::numeric_limits<value_type>::max())
   break;
 }//for s
}//MemTest_002_modx

Запустив этот тест в 10 потоков, через пару часов я обнаружил, что он отработал только на 20%.

Пришлось оставить его на ночь.

За ночь он осилил первый проход и начал второй.

Судя по грубым прикидкам, такая работа с оперативной памяти в пятьдесят (ПЯТЬДЕСЯТ, Карл!) медленнее последовательной. ~10 часов против ~12 минут.

Хорошо так отрезвляет. Вспомнились две вещи.

Первая — Celeron 266.

Втораямашина Тьюринга оптимизация работы с HDD. Намного быстрее сначала прочитать блок страниц, модифицировать нужные страницы, а потом записать блок назад. Это я узнал от LOA, Олега LOA 🙂 Как внезапно оказалось — с оперативной памятью нужно делать тоже самое.

2 комментария

hvlad  on 21 октября, 2017

Memtest не зря работает до загрузки ОС (IIRC).
Как ты выделяешь память для теста ?
И как ты гарантируешь полное покрытие физ.памяти тестом ?

Dmitry Kovalenko  on 21 октября, 2017

>Как ты выделяешь память для теста ?

VirtualAlloc(NULL,c_MemSize,MEM_COMMIT|MEM_RESERVE,PAGE_READWRITE)

>И как ты гарантируешь полное покрытие физ.памяти тестом ?

Никак. Более того, c_MemSize был равен 15.5GB, а не 16GB ровно, чтобы немного пощадить SSD на котором лежит своп.


Замысел был в создании нагрузки на память без участия сложных вещей.

Leave a Comment