пятница, 30 сентября 2011 г.

Бенчмарк оперативной памяти

Не люблю различные бенчмарки типа SiSoft Sandra и подобные за то, что у них у разных версий на одном и том же компьютере разные показатели. Как правило, у новых версий показатели выше. На одном и том же компьютере. Это ставит под сомнение фактическую ценность таких бенчмарков как источников каких-то абсолютных показателей. Да и и их ценность при сравнительном тестировании тоже весьма сомнительна - ну кто знает, что они там имеют ввиду под своими попугаями? Вот функция memcpy(), например, проста для понимания и широко используется и поэтому именно она может быть использована для измерения скорости обмена между процессором и ОЗУ. Набросал простой бенчмарк:

#include <QtCore/QCoreApplication>
#include <vector>
#include <iostream>
#include <QTime>
#include <QDebug>
#include <QThread>
 
class MemReadTestThread : public QThread {
const unsigned char * test_memory_block;
const size_t test_memory_size;
const size_t repeat_count;
const size_t buf_size;
public:
    MemReadTestThread(const unsigned char * block, const size_t size, const size_t bufsize, const size_t repeat_cnt)
        : QThread(), test_memory_block(block), test_memory_size(size), repeat_count(repeat_cnt), buf_size(bufsize)
    {
    }
    void run() {
        setPriority(HighPriority);
        std::vector<unsigned char> buf(buf_size);
 
        for(size_t n = 0; n < repeat_count; n++) {
            size_t i = 0;
            while(i + buf_size < test_memory_size) {
                memcpy(&buf[0], &test_memory_block[i], buf_size);
                i += buf_size;
            }
        }
    }
};
 
class MemWriteTestThread : public QThread {
unsigned char * test_memory_block;
const size_t test_memory_size;
const size_t repeat_count;
const size_t buf_size;
public:
    MemWriteTestThread(unsigned char * block, const size_t size, const size_t bufsize, const size_t repeat_cnt)
        : QThread(), test_memory_block(block), test_memory_size(size), buf_size(bufsize), repeat_count(repeat_cnt)
    {
    }
    void run() {
        setPriority(HighPriority);
        std::vector<unsigned char> buf(buf_size);
 
        for(size_t n = 0; n < repeat_count; n++) {
            size_t i = 0;
            while(i + buf_size < test_memory_size) {
                memcpy(&test_memory_block[i], &buf[0], buf_size);
                i += buf_size;
            }
        }
    }
};
 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    using namespace std;
    const size_t size = 768*1024*1024;
    const size_t bufsize = 32*1024;
    vector<unsigned char> bytes1(size), bytes2(size);
    
    //засекаем время
    QTime time;
    time.restart();
    const size_t count = 10; 
    //создаем и запускаем 2 потока чтения из памяти
    MemReadTestThread rt1(&bytes1[0], bytes1.size(), bufsize, count);
    MemReadTestThread rt2(&bytes2[0], bytes2.size(), bufsize, count);
    rt1.start();
    rt2.start();
    //ждем, пока оба не завершатся
    rt1.wait();
    rt2.wait();
    //узнаем время выполнения и считаем результат
    float secs = (float) time.elapsed()/1000;
    size_t total_megabytes_copied = ((bytes1.size()+bytes2.size())/(1024*1024))*count;
    qDebug() << "********** memory read ************";
    qDebug() << "time = " << secs << " sec, copied size = " << total_megabytes_copied << " megabytes";
    float megbytes_per_sec = (float)total_megabytes_copied/secs;
    qDebug() << megbytes_per_sec << " megabytes per sec";
 
    //засекаем время
    time.restart();
    //создаем и запускаем два потока записи в память
    MemWriteTestThread wt1(&bytes1[0], bytes1.size(), bufsize, count);
    MemWriteTestThread wt2(&bytes2[0], bytes2.size(), bufsize, count);
    wt1.start();
    wt2.start();
    //ждем, пока не завершатся
    wt1.wait();
    wt2.wait();
    //узнаем время и вычисляем результат 
    secs = (float) time.elapsed()/1000;
    total_megabytes_copied = ((bytes1.size()+bytes2.size())/(1024*1024))*count;
    qDebug() << "********** memory write ************";
    qDebug() << "time = " << secs << " sec, copied size = " << total_megabytes_copied << " megabytes";
    megbytes_per_sec = (float)total_megabytes_copied/secs;
    qDebug() << megbytes_per_sec << " megabytes per sec";
 
    return a.exec();
}

Комментировать особо нечего, кроме того, почему нам нужно тестировать в двух потоках. Тут все очень интересно. Как всем известно, в современных контроллерах памяти, если есть две одинаковые (по объему и скорости) планки памяти, то есть возможность использовать двухканальный режим памяти. То есть две 64-битные планки памяти (а они имеют именно такую разрядность) как бы превращаются в одну большую 128-битную планку памяти. Соответственно,  скорость работы увеличивается в 2 раза. Этот режим называется dual ganged mode. А есть еще dual unganged mode - это тоже режим с удвоенной пропускной способностью, но контроллер памяти обращается к памяти не как к одной 128-битной планке, а как к двум планкам, каждая из которых имеет свою собственную независимую 64-битную шину. То есть можно в одну планку писать и одновременно что-то читать с другой планки памяти. При этом удвоенная скорость будет только в том случае, если тестировать две планки памяти независимо друг от друга, то есть в разных потоках. Чтобы убедиться в этом, можно эту програмку сделать однопоточной и увидеть воочию двукратное падение скорости. Разумеется, это относится только к unganged mode, т.к. ganged mode совершенно все равно, сколько потоков будет запущено - на результат это не повлияет. Трудность заключается в том, что нет уверенности, что один тестируемый массив полностью расположен в одной планке, а второй - полностью в другой планке и это может негативно повлиять на итоговый результат. С другой стороны, в реальных приложениях вряд ли кто-то будет пытаться расположить массивы данных так, чтобы они оптимальным образом были рассредоточены по двум планкам памяти, поэтому результаты работы данного простого бенчмарка, в принципе, ближе к реальности, чем в идеальном случае. А результаты у меня такие:


Это материнская плата MSI 760GM-P33 на чипсете 760G и процессор AMD X4 640 и две планки памяти DDR3-1333 по 2 гигабайта.