Самопальный бенчмарк памяти
На работе у меня депрессия, и чтобы отвлечься, решил написать самодельный бенчмарк пропускной способности памяти. На самом деле тдея зародилась гораздо раньше - когда я вдргу обнаружил, что в интернете нет ни одной бесплатной подобной проги, что очень удивительно на фоне бесплатного 7-зипа, линукса, мускла, опенофиса, мплеера и прочих крайне полезных и мощных совтин, которые гораздо, гораздо сложнее, чем какой-то сраный бенчмарк. Да и вообще, те довольно абстрактные цифры, которые показывает та же сисофт сандра - они вообще непонятно о чем: на одном и том же железе разные версии сандры показывают разные цифры. Как правило, более новыен версии сандры показывают большее число мегабайт в секунду. Последнее мне непонятно: я, конечно, понимаю, что оно тешит самолюбие владельца внезапно "ускорившегося" ПК, но тогда проще уж перейти сразу на пересчет в попугаях. То ли дело самому написать: заюзал функцию типа memcpy() и все тебе понятно. А откуда сандровские попугаи берутся - хуй его знает.
Да, и еще небольшой ликбез по контроллерам памяти. Контроллеры памяти сейчас могут работать в двухканальном режиме, работая с двумя планками памяти с шиной по 64 бита на каждую как с одной большой планкой памяти с шиной 128 бит. Но недавно оказалось, что все несколько сложнее: даже работая в этом dual-режиме, контроллер может работать либо в ganged, либо в unganged режиме. Ganged - это тот самый режим с как бы одной большой планкой на 128 бит, а вот unganged - это когда планки работают каждая на своем 64-битном канале, но независимо. То есть можно в одну планку что-то писать, а с другой читать. Какой из режимов лучше? Тестеры пришли к мнению, что обычному юзеру в принципе похуй. Вот если у вас какой-то супернагруженный сервак - то там, наверное, в целях многозадачности лучше бы анганжед режим. Теоретически.
Короче из всего этого можно сделать один очень важный вывод: чтобы написать адекватный бенчмарк, необходимо, чтобы он был двухпоточный, чтобы задействовать два независимых канала памяти для unganged режима. Иначе результаты будут занижены (что впоследствии подтвердилось экспериментально). А для ganged режима должно быть все равно, сколько потоков. Короче говоря, вот исходный код (Qt4, windows):
Ну и, самое интересное: результаты тестирования. Сначала я протестировал на работе тамошний CoreDuo Е7400 2.8ГГц с интеловским чипом G41 с двумя планками DDR2-800:
Ну и мой домашний компьютер с процом Athlon X4 640 3.0GHz на плате AMD-760G и с двумя планками памяти DDR3-1333:
Понятно, что для сравнения корректнее было бы взят от интела что-то на Core i3, i5 или i7, но что было под рукой, то и протестировал. Зато сразу понятно, что ДДР3 лучше, чем ДДР2 и встроенный в процессор контроллер памяти заметно шустрее отдельного. Так что технический прогресс - он идет, и это очень хорошо заметно даже на протяжении пары лет, кто бы что не говорил.
Да, и еще небольшой ликбез по контроллерам памяти. Контроллеры памяти сейчас могут работать в двухканальном режиме, работая с двумя планками памяти с шиной по 64 бита на каждую как с одной большой планкой памяти с шиной 128 бит. Но недавно оказалось, что все несколько сложнее: даже работая в этом dual-режиме, контроллер может работать либо в ganged, либо в unganged режиме. Ganged - это тот самый режим с как бы одной большой планкой на 128 бит, а вот unganged - это когда планки работают каждая на своем 64-битном канале, но независимо. То есть можно в одну планку что-то писать, а с другой читать. Какой из режимов лучше? Тестеры пришли к мнению, что обычному юзеру в принципе похуй. Вот если у вас какой-то супернагруженный сервак - то там, наверное, в целях многозадачности лучше бы анганжед режим. Теоретически.
Короче из всего этого можно сделать один очень важный вывод: чтобы написать адекватный бенчмарк, необходимо, чтобы он был двухпоточный, чтобы задействовать два независимых канала памяти для unganged режима. Иначе результаты будут занижены (что впоследствии подтвердилось экспериментально). А для ganged режима должно быть все равно, сколько потоков. Короче говоря, вот исходный код (Qt4, windows):
#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;
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();
}
#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;
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();
}
Ну и, самое интересное: результаты тестирования. Сначала я протестировал на работе тамошний CoreDuo Е7400 2.8ГГц с интеловским чипом G41 с двумя планками DDR2-800:
Ну и мой домашний компьютер с процом Athlon X4 640 3.0GHz на плате AMD-760G и с двумя планками памяти DDR3-1333:
Понятно, что для сравнения корректнее было бы взят от интела что-то на Core i3, i5 или i7, но что было под рукой, то и протестировал. Зато сразу понятно, что ДДР3 лучше, чем ДДР2 и встроенный в процессор контроллер памяти заметно шустрее отдельного. Так что технический прогресс - он идет, и это очень хорошо заметно даже на протяжении пары лет, кто бы что не говорил.
Комментарии