在做一个基于内存的系统,测试多性能的时候发现一个问题,多线程的读取内存比写入内存更快,不同的机器上甚至要快很多。
1 测试环境
测试在笔记本上进行,硬件配置如下:
CPU
Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz
基准速度: 1.80 GHz
插槽: 1
内核: 4
逻辑处理器: 8
虚拟化: 已启用
L1 缓存: 256 KB
L2 缓存: 1.0 MB
L3 缓存: 6.0 MB
内存
24.0 GB
速度: 2667 MHz
已使用的插槽: 2/2
外形规格: SODIMM
为硬件保留的内存: 129 MB
经查,CPU是numa架构。内存有两根,8GB+16GB。
操作系统:win10 家庭版 1809 17763.914
编译器:VS2019.
2 测试代码
针对这个问题,简化了系统代码,提取能复现问题的思路。主要是分配两块内存,一个1M大小,另外一个是多个1M大小的内存集合,总共1GB。有两种操作,读操作是将1GB的内存复制到1M的块中,写操作则相反。创建多个线程同时执行这两个操作,分别记录时间,计算总的速度。
主要代码如下所示。
#include <iostream>
#include <list>
#include <vector>
#include <thread>
#include<stdlib.h>
#include<time.h>
const static int64_t runnum = 100;
const static int mb = 1 << 20;
const static int sizemb = 2048;
// 从多个数据块复制到同一数据块
void test_read_list() {
std::list<char*> bufs;
char* mbuf = new char[mb];
memset(mbuf, 0, mb);
for (int k = 0; k < sizemb; ++k) {
char*t = new char[mb];
memset(t, 0, mb);
bufs.push_back(t);
}
for (int n = 0; n < runnum; ++n) {
for (auto t: bufs) {
memcpy(mbuf, t, mb);
}
}
delete[] mbuf;
for (auto t : bufs)
delete[] t;
}
void test_read_list_mt() {
std::thread ts[2];
time_t st, ed;
time(&st);
ts[0] = std::thread(test_read_list);
ts[1] = std::thread(test_read_list);
ts[0].join();
ts[1].join();
time(&ed);
auto vel_mb = (2 * runnum * sizemb) / (ed - st);
std::cout << "read-mt result: t=" << (ed - st) << " " << vel_mb << "MB/s" << std::endl;
}
// 从同一数据块复制到多个数据块
void test_write_list() {
std::list<char*> bufs;
char* mbuf = new char[mb];
memset(mbuf, 0, mb);
for (int k = 0; k < sizemb; ++k) {
char* t = new char[mb];
memset(t, 0, mb);
bufs.push_back(t);
}
for (int n = 0; n < runnum; ++n) {
for (auto t : bufs) {
//memcpy(t, mbuf, mb);
memmove(t, mbuf, mb);
}
}
delete[] mbuf;
for (auto t : bufs)
delete[] t;
}
void test_write_list_mt() {
std::thread ts[2];
time_t st, ed;
time(&st);
ts[0] = std::thread(test_write_list);
ts[1] = std::thread(test_write_list);
ts[0].join();
ts[1].join();
time(&ed);
auto vel_mb = (2 * runnum * sizemb) / (ed - st);
std::cout << "write-mt result: t=" << (ed - st) << " " << vel_mb << "MB/s" << std::endl;
}
int main()
{
test_read_list_mt();
test_write_list_mt();
return 0;
}
main()里先进行读测试,后面进行写测试,线程个数、数据块大小都完全一致。读写前都使用memset()初始化内存,使操作系统能分配到物理内存。
3 测试结果
1线程测试输出如下。
read result: t=16 12800MB/s
write result: t=14 14628MB/s
过程中cpu使用率如下所示。
2线程测试输出如下。
read-mt result: t=22 18618MB/s
write-mt result: t=28 14628MB/s
3线程测试输出如下。
read result: t=34 18070MB/s
write result: t=53 11592MB/s
cpu使用率曲线如下所示。
根据测试结果可以发现几个问题:
1、仅看读速度,2线程比1线程速度快45%,与3线程速度近似;
2、仅看写速度,2线程和1线程速度一样,3线程速度就下降了20%;
3、单线程时写内存更快,而多线程写速度就远不如读内存速度;
4、多线程读的过程中,cpu使用率从100%降到了80%左右,相比之下,单线程的cpu使用率则很平稳;
上述问题应该都是和numa架构有关系,涉及到多核心CPU访问内存的模式,但是这个架构具体怎么导致读写速度差异,这个就留着后面再解决吧。