有了CXL扩展内存,自然是要在DRAM+CXL扩展内存上跑跑实际的应用程序,看看和DRAM+传统磁盘有什么区别。
实际的应用程序其实就是一些工业界部署使用的,比如数据库、深度学习训练项目等等。本文主要找到一个KV存储Viper,搭建并进行简单的测试。
论文:Viper: An Efficient Hybrid PMem-DRAM Key-Value Store
github仓库:https://github.com/hpides/viper
文章目录
- 一、KV存储简介
- 二、Viper简介
- 1. 设计方案
- 2. 访问模式
- 三、Viper安装
- 1. 正常Ubuntu联网状态下的安装
- 2. Ubuntu18.04 磁盘镜像中安装Viper
- 安装cmake 3.26.3
- 安装g++/gcc 9.4
- 安装viper
- 四、bug记录
- 1. 有关gcc中libstdc++版本的问题
- 2. 有关Illegal instruction的问题
- 五、其他KVS
一、KV存储简介
KV存储就是键值对,和传统的关系型数据库不一样,没有很多SQL的范式约束,称作NOSQL,就是简单的键值对put、get操作,所以性能很高。
一般分两种KV存储(KVS):
- 基于服务器的KVS
- 常见:Redis、memchached
- 特点:客户端/服务端API通过网络与应用程序通信、构建分布式存储系统
- 嵌入式KVS
- 常见:RocksDB、FASTER、LevelDB
- 特点:轻量级;嵌入在应用程序中,一般通过引入头文件调用库函数进行访问;数据存储在本地
因为gem5全系统仿真下无法联网,所以使用基于服务器的KVS显然不现实;
另外,目前使用cxl设备的方式还是将其看做一个字符设备,使用mmap的方式来访问,所以需要创建文件来持久化保存数据的诸如LevelDB也不行。
万幸发现了Viper,一个很新的,非常轻量级的,代码可读性很好的,可以提供字符设备访问方式的KVS。
二、Viper简介
Viper是针对Pmem开发的一个KVS。
1. 设计方案
Viper的作者基于这样的出发点进行设计:对比Pmem、DRAM、SSD的读写性能,得到这样的结果:DRAM处理随机读写性能非常高,Pmem处理顺序写操作性能较高。
所以鼓励按顺序写入Pmem,尽可能将随机访问定向到DRAM,基于这样的特点,得到了一个设计方案:
Viper = DRAM中的易失性哈希索引 + PMem中的持久数据块
其实它的思想很简单,就是将键值对和键值对的索引分开存储。
DRAM适合随机访问,那么就让索引的哈希表存在DRAM里;
Pmem适合顺序写,那么就让实际的键值对按顺序存在Pmem里。
2. 访问模式
它支持三种访问模式:
- DRAM+Pmem:Pmem上挂载DAX机制的文件系统;
- DRAM+Pmem:配置Pmem为字符设备进行mmap访问;
- DRAM+DRAM:配置仅仅运行在DRAM上,即哈希表与键值对都存在DRAM里。
三、Viper安装
1. 正常Ubuntu联网状态下的安装
其实Viper的readme说的比较清楚,也比较简单
依赖:
- concurrentqueue 1.0.3的库,在CMakeLists.txt都链接好了
- cmake version >= 3.14
- c++17以上
- 较高版本的g++/gcc,如g++9.4
正常的Ubuntu20.04一般都具备下面三个条件,第一个只要可以正常联网,就可以自动在github上下载。可以直接在Viper的目录下使用命令即可安装标准版:
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
自带有一个playground.cpp测试文件,要测试该文件可以在build目录下使用下面的命令:
cmake -DVIPER_BUILD_PLAYGROUND=ON ..
cmake --build .
就可以看到build目录下有playground可执行文件了,直接./playground
即可测试。
有关cmake编译选项,可以看Viper的CMakeLists.txt,里面有说明可以编译的选项:
option(VIPER_BUILD_PLAYGROUND “Set ON if playground should be built.” OFF)
option(VIPER_BUILD_BENCHMARKS “Set OM if benchmarks should be built.” OFF)
不过可能出现的问题就是,自己的电脑CPU不支持clwb指令集,该指令集主要是Intel的至强CPU支持的,一般的个人电脑可能不支持,可以通过修改CMakeLists.txt和Viper源码的方式避免使用clwb指令:
- 使用
_mm_clflush
替换_mm_clwb
:在viper.hpp
的105
行,将_mm_clwb
换为_mm_clflush
。
(这两个应该算是内嵌的汇编函数,两者功能上我浅显的理解应该差不多,但是前者在大部分的CPU都会支持。)
多亏了这个博客[非易失内存编程] 通过编译器内置函数 (Intrinsic Functions) 发射 CLFLUSH、CLFLUSHOPT、CLWB、NTSTORE、MFENCE、SFENCE 等指令,文中不但介绍了CLFLUSH、CLFLUSHOPT、CLWB等指令的区别,还介绍了使用内嵌汇编函数时遇到的一些问题,比如如何在编译时指定指令集扩展。
可以在这个文档 Intel Intrinsic Guide中查看Intel的一些汇编函数。
- 修改CMakeLists.txt:在
CMakeLists.txt的21行和40行
,将-mclwb
修改为-msse2
。重新进行cmake --build .
即可。
(_mm_clflush
在sse2指令集中,cmake调用的是g++编译器,而-msse2选项是指告诉编译器当前使用了sse2这个扩展指令集。)
2. Ubuntu18.04 磁盘镜像中安装Viper
在磁盘镜像中有很多限制,一方面不能联网,另一方面其软件包如g++、cmake版本太低,所以无法按照上述正常编译使用Viper,需要安装高版本的g++和cmake
安装cmake 3.26.3
具体参考:Ubuntu18.04安装Cmake(最新最详细亲测)
- 在官网下载编译好的二进制包(这种方法简单一点,源码编译的方法容易出错。)
挂载磁盘文件参考:【CXL】使用memkind/vmem管理CXL扩展内存
解压并复制到磁盘文件中
tar -zxvf cmake-3.26.3-linux-x86_64.tar.gz
sudo cp cmake-3.26.3-linux-x86_64 ~/gem5/full-system-image/disks/local_mnt/home/lib_source_code/ -r
- 使用
sudo chroot ./
命令进入磁盘文件之后,为cmake配置环境变量
这里我的做法有点奇怪,不过貌似效果是一样的。
正常按照参考博客的方法二添加软链接应该就可以使用了,如下:
ln -sf /home/lib_source_code/cmake-3.26.3-linux-x86_64/bin/* /usr/local/bin/
但是我不得行,只能使用下面的方法(有点简单粗暴,如果有更好的方法可以不用下面的办法):
我在/usr/local/bin/
与/usr/local/share/
目录下发现了旧版本的cmake文件,于是我选择将3.26的cmake文件进行对应替换,就是直接cp过去覆盖旧文件:
cp cmake-3.26.3-linux-x86_64/bin/ccmake /usr/local/bin/
cp cmake-3.26.3-linux-x86_64/bin/cmake /usr/local/bin/
cp cmake-3.26.3-linux-x86_64/bin/cmake-gui /usr/local/bin/
cp cmake-3.26.3-linux-x86_64/bin/cpack /usr/local/bin/
cp cmake-3.26.3-linux-x86_64/bin/ctest /usr/local/bin/
cp cmake-3.26.3-linux-x86_64/share/cmake-3.26 /usr/local/share/
虽然但是,可能有点其他的问题?但是可以正常用了,后续出问题了再看
安装g++/gcc 9.4
这里编译起来比较恼火,但是安装过程还算顺利,参考博客:gcc特定版本Linux源码编译安装,顺着下来完全没有问题,而且这种方法不用卸载旧版本的gcc,可以选择新旧版本,还比较方便。
- 下载gcc9.4源码
清华大学开源软件镜像站找到对应版本下载即可
- 下载gcc9.4需要的依赖包,全部放在gcc-9.4.0文件夹中
- gmp-6.1.0.tar.bz2
- mpfr-3.1.4.tar.bz2
- mpc-1.0.3.tar.gz
- isl-0.18.tar.bz2
注意不同gcc版本需要的依赖包版本也不同,具体可以进入gcc源码文件夹中contrib/download_prerequisites
文件中查看,上述版本仅针对gcc9.4
前三个包可以在清华镜像Index of /gnu/找到
最后一个包可以在gitee RichardYann / mirrors找到
- 执行命令
./contrib/download_prerequisites
安装依赖包 - 编译安装gcc,这个步骤需要留出足够大的空间,可能要8gb左右,不然会编译失败
# 在gcc源码包里创建build文件
mkdir build && cd build
# 配置安装路径
../configure --prefix=/usr/local/gcc-9.4 --enable-languages=c,c++ --disable-multilib
# 安装
make -j8 && make install
- 使用
update-alternatives
将其配置为默认gcc工具
# 首先注册gcc-9.4
update-alternatives --install /usr/bin/gcc gcc /usr/local/gcc-9.4/bin/gcc 100 --slave /usr/bin/g++ g++ /usr/local/gcc-9.4/bin/g++ --slave /usr/bin/gcov gcov /usr/local/gcc-9.4/bin/gcov
# 再注册原本的gcc-7
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 80 --slave /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-7 --slave /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-7 --slave /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-7
随后输入update-alternatives --config gcc
,显示:
There are 2 choices for the alternative gcc (providing /usr/bin/gcc).
Selection Path Priority Status
------------------------------------------------------------
* 0 /usr/local/gcc-9.4/bin/gcc 100 auto mode
1 /usr/bin/gcc-7 80 manual mode
2 /usr/local/gcc-9.4/bin/gcc 100 manual mode
只需在终端交互中输入相应的序号即可切换 gcc版本。(0是auto mode,会自动选择优先级最高的版本)
到目前为止,Viper依赖的cmake和gcc都安装好了,还有concurrentqueue 1.0.3
安装viper
- 将viper的源码复制到磁盘文件中
# 首先在磁盘文件中创建一个/home/kvs文件夹,然后在本机中执行下面的命令
sudo cp viper ~/gem5/full-system-image/disks/local_mnt/home/kvs/ -r
- 先编译一次
mkdir build && cd build
cmake -DVIPER_BUILD_PLAYGROUND=ON ..
此时会报错,无法联网下载concurrentqueue,但是在build文件夹下面以及创建了_deps文件夹,我们直接将本机下载好的concurrentqueue-src复制到_deps文件夹下;
-
修改CMakeLists.txt文件,注释下载concurrentqueue的命令
-
修改CMakeLists.txt文件,添加-mno-bmi2如下:
这一步的原因在bug记录中说明。 -
重新编译,成功生成playground可执行文件
mkdir build && cd build
cmake -DVIPER_BUILD_PLAYGROUND=ON ..
cmake --build .
- 测试Viper的使用
-
因为要让viper跑在cxl设备上,修改playground中创建
viper_db
的文件路径为:"/dev/cxl_mem0"
-
调整viper的访问模式为devdax,也就是使用字符设备,修改
viper.hpp
的init_pool()
函数如下:
-
另外devdax支持的mmap标志
MAP_SYNC
,我们的驱动无法支持,所以修改viper.hpp
的VIPER_MAP_FLAGS
如下:
-
最后,注意此时虽然成功得到了playground可执行文件,但是众所周知在gem5下运行程序非常的慢,而
viper.hpp
中有个操作非常非常的花时间,而且藏的很隐蔽,是在初始化一个map_
数据结构时的一个for循环,要想快一点跑出结果,需要修改viper.hpp
的源码的一个魔法数字(即不明原因的常数),我修改为1024会快一点,目前看来无伤大雅
此时重新编译并执行playground便可以较快的得到结果:
-
cmake --build .
./playground
另外值得一提的是,发现gem5的X86TimingSimpleCPU是Intel至强服务器的架构,它支持clwb扩展指令集,所以前文中对_mm_clwb
的修改可以保持原样。
四、bug记录
1. 有关gcc中libstdc++版本的问题
gcc安装完成后,在编译viper时可能会出现libstdc++版本太低的问题,使用strings /usr/lib64/libstdc++.so.6 |grep GLIBC
查看当前libstdc++支持的GLIBCXX版本,发现版本号在所需的版本那里就停了,其实是系统没有找到新安装的gcc的libstdc++,做如下操作:
# 把安装后的libstdc++.so.6.0.28 拷贝到/usr/lib
cp /usr/local/gcc-9.4/lib64/libstdc++.so.6.0.28 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28
# 创建软链接
ln -sf libstdc++.so.6.0.28 libstdc++.so.6
这样再使用strings /usr/lib64/libstdc++.so.6 |grep GLIBC
查看,应该就会有所改变。
参考如何升级linux的libstdc++.so
2. 有关Illegal instruction的问题
使用这个viper最难蚌的一点是,它依赖的软件包等都比较新,需要更新cmake和gcc就算了,最大的问题是,它在我本机的硬件环境下编译出来了一个shlx指令,该指令gem5的cpu不支持,想尽了办法规避这个shlx指令。
在运行playground测试viper时,发现总是会出现Illegal instruction
这个报错,通过多方查询,从这个博客中获得一些信息[x86][linux]AVX512指令引起的进程crash,也就是有个汇编指令我的cpu不支持。
- 使用
dmesg | tail -20
命令查看内核的报错:
[ 1751.566928] traps: playground[1140] trap invalid opcode ip:419154 sp:7ffc289ba970 error:0 in playground[400000+32000]
- 通过反汇编可执行文件:
objdump -D playground > playground.txt
- 定位到
419154: c4 e2 f9 f7 c2 shlx %rax,%rdx,%rax
这个罪魁祸首
所以想方设法规避编译出这个shlx指令,尝试了多种方法:
官方的指令说明:SARX/SHLX/SHRX,提到CPU运行的实模式和虚拟8086模式,SHLX指令不支持这两个模式,也就是只能运行保护模式下,这可难倒我了。。。
- 通过改写viper源码,用vector容器代替某些数据结构,编译结果发现出现了3条shlx指令。。。
- 通过gcc查看CPU体系结构:
gcc -march=native -c -Q --help=target | grep march
,通过march=nocano指定体系结构进行编译,同样不行。(参考gcc-编译时使用的指令集)
后来发现可以通过选项禁止某些指令集的编译,参考gcc编译时指定指令集,查到shlx指令包含在bmi2扩展指令集中,遂添加-mno-bmi2选项,发现shlx消失了!成功运行!
编译viper虽然是在磁盘文件的环境下编译的,但是CPU硬件架构还是本电脑的Intel® Core™ i5-9300H CPU,可以使用cat /proc/cpuinfo指令查看cpu支持的指令集有哪些,
比如我本机支持:
包括bmi2扩展指令集,但是同样的查看gem5的cpu指令集(X86TimingSimpleCPU),如下图:
发现并没有bmi2,所以shlx指令不支持。
五、其他KVS
- levelDB github、levelDB简单使用
- Pmemkv官方介绍