1.8. sycuricon( 八 ): benchmark¶
为了对处理器的性能、正确性等进行验证,我们在 buildroot 平台移植了一些简单的 benchmark,后续应该会继续完善。 这部分代码和机制被我们继承在 riscv-rss-sdk 仓库当中,其他细节可以参看 riscv-rss-sdk 的介绍。
1.8.1. Lmbench¶
1.8.1.1. 简单介绍¶
Lmbench 的 submodule 定义如下,同步该仓库开始后续操作。正常的编译测试方式如下:
执行 make build,Lmbench 会执行 src 下的 make build,将 src 下的所有 c 文件编译为 elf 文件保存到 bin 目录下
执行 make results/make rerun,Lmbench 会执行 scripts 下面的 config-run 脚本,对 bin 的程序进行执行,并且保存执行结果
config-run 的执行方式是执行 benchmp 对需要测试的函数进行多线程的验证,然后每个线程跑一个测试函数。 该测试函数会对一个测试目标执行若干次,然后统计执行时间,进而计算平均执行时间。 例如执行 2^32 次 kill 操作计算 signal 传递操作的时间等。
[submodule "benchmark/lmbench-3.0-a9"]
path = benchmark/lmbench-3.0-a9
url = https://github.com/docularxu/lmbench-3.0-a9.git
config-run 在执行程序的时候会先检查一下处理器的内存大小,如果内存大小满足测试需求,他才会开始进行测试。 这个内存检查过程非常慢,考虑到我们处理器内存一般是完全够用的,我们可以将对应的内存检查代码注释掉,节约内存检查的时间。
echo $ECHON "MB [default $MB]: $ECHOC"
read TMP
if [ X$TMP != X ]
then MB=$TMP
fi
# Certain machines tend to barf when you try and bcopy 8MB.
# Figure out how much we can use.
echo "Checking to see if you have $MB MB; please wait for a moment..."
MB=`../bin/$OS/memsize $MB`
MB=`../bin/$OS/memsize $MB`
MB=`../bin/$OS/memsize $MB`
if [ `expr $SYNC_MAX \* $MB` -gt `expr $TOTAL_MEM` ]
then
MB=`expr $TOTAL_MEM / $SYNC_MAX`
MB=`expr $MB / 2`
fi
if [ $MB -lt 8 ]
then echo $0 aborted: Not enough memory, only ${MB}MB available.
exit 1
fi
if [ $MB -lt 16 ]
then echo Warning: you have only ${MB}MB available memory.
echo Some benchmark results will be less meaningful.
fi
1.8.1.2. 移植过程¶
现在我们需要将 benchmark 进行 riscv 交叉编译,因此需要修改 Lmbench 的一些配置。
修改编译依赖的 config 选项:
在 scripts/compiler 中设置 CC 为对应的交叉编译器路径 riscv64-unknown-linux-gnu-gcc
+CC=${RISCV}/bin/riscv64-unknown-linux-gnu-gcc
echo $CC
在 scripts/OS 中设置 OS 为对应的 linux 类型 riscv-OS。之后编译的结果会出现在 bin/riscv-OS 中(不然默认是宿主机的 x86_64)
+OS=riscv-linux
echo $OS
之后想要执行对应的测试,我们执行程序的环境需要 libtirpc,因此我们需要为交叉编译环境和执行环境配置 libtirpc 库。 我们可以回忆一下,我们的交叉编译链接库被保存在 toolchain/lib 中,因此我们需要在这里安装交叉编译的 libtirpc。
安装 libtirpc 库,这个可以从对应的官网直接下载解压到 benchmark 中,并且命名为 libtirpc
然后用 riscv 工具链交叉编译,然后 install 到 toolchian/lib 中
之后我们的交叉编译器就有了链接 libtirpc 的能力。 链接分为动态链接和静态链接,静态连接的程序拥有所有的代码,但是非常庞臃肿; 动态链接不需要链接对应的库代码,尺寸小巧,但是要求执行环境有对应的链接库。 因为我们的环境需要通过 SD 卡将程序读如内存进行执行,这个过程很慢,所以我们希望程序尽可能的小巧,因此我们采用动态链接编译。 然后我们需要额外的执行 install,将 libtirpc 保存到 sysroot 的 lib 目录下。 我们在 makefile 中编写了如下的代码,可以一键执行安装。
libtirpc_srcdir := $(benchmark_srcdir)/libtirpc
libtirpc_wrkdir := $(wrkdir)/libtirpc
libtirpc_lib := $(buildroot_initramfs_sysroot)/lib/libtirpc.so
$(libtirpc_lib):$(libtirpc_srcdir)
rm -rf $(libtirpc_wrkdir)
mkdir -p $(libtirpc_wrkdir)
cd $(libtirpc_wrkdir); $(libtirpc_srcdir)/configure --host=riscv64-unknown-linux-gnu --prefix=$(toolchain_dest)/sysroot/usr --disable-gssapi
make -C $(libtirpc_wrkdir)
make -C $(libtirpc_wrkdir) install
cd $(libtirpc_wrkdir); $(libtirpc_srcdir)/configure --host=riscv64-unknown-linux-gnu --prefix=$(buildroot_initramfs_sysroot) --disable-gssapi
make -C $(libtirpc_wrkdir) install
在 scripts/build 中修改编译选项 LDLIBS,加入 tirpc 和 pthread 的链接
-LDLIBS=-lm
+LDLIBS="-ltirpc -lpthread -lm"
在 Makefile 中修改编译选项 COMPILE,加入对 tirpc 的头文件 include 支持
-COMPILE=$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS)
+COMPILE=$(CC) $(CFLAGS) -Wall -O2 -ffunction-sections -Wl,--gc-sections -s -I$(RISCV)/sysroot/usr/include/tirpc $(CPPFLAGS) $(LDFLAGS)
之后执行编译就可以得到最后的结果。但是我们不方便把所有的 elf 和执行的 script 拷贝到我们的嵌入式环境 starship 中进行执行, 首先我们不需要执行所有的测试程序(太多了);其次,我们很多测试程序的移植需要做调整,比较麻烦。 因此,我们额外写了一个 C 程序 execute.c 用 system 操作执行对应的测试程序,然后计算测试结果。 每个测试程序的执行命令被记录在 scripts/lmbench 当中,我们可以解析这个文件, 让 execute 程序执行需要测试的测试目标在 lmbench 中对应的执行命令。 在 Makefile 中加入对 execute 的编译命令即可。
@@ -74,6 +74,7 @@ SRCS = bw_file_rd.c bw_mem.c bw_mmap_rd.c bw_pipe.c bw_tcp.c bw_udp.c \
lib_udp.c lib_unix.c lib_sched.c \
line.c lmdd.c lmhttp.c par_mem.c par_ops.c loop_o.c memsize.c \
mhz.c msleep.c rhttp.c seek.c timing_o.c tlb.c stream.c \
+ execute.c \
bench.h lib_debug.h lib_tcp.h lib_udp.h lib_unix.h names.h \
stats.h timing.h version.h
@@ -103,7 +104,7 @@ EXES = $O/bw_file_rd $O/bw_mem $O/bw_mmap_rd $O/bw_pipe $O/bw_tcp \
$O/msleep $O/loop_o $O/lat_fifo $O/lmhttp $O/lat_http \
$O/lat_fcntl $O/disk $O/lat_unix_connect $O/flushdisk \
$O/lat_ops $O/line $O/tlb $O/par_mem $O/par_ops \
- $O/stream
+ $O/stream $O/execute
上述所有的修改,被我们打包到 benchmark/patch/lmbench.patch 当中,可以用 git apply 一键修改。
1.8.2. Unixbench¶
现在我们移植 unixbench,比 lmbench 简单一些。
1.8.2.1. 简单介绍¶
执行 make program 可以编译 src 的所有程序,然后保存在 pgms 当中
执行 make run 可以 Run 脚本,这个脚本会依次执行 pgms 中的程序,然后将对应的执行结果和输出 log 保存在 result 中
和 lmbench 不同的是,unixbench 统计的不是每个测试的执行时间,而是执行时间的倒数。 unixbench 每个 elf 接受一个额外参数:执行时间 latency,然后这个 elf 会执行两个 thread。 主 thread 做 sleep 操作,sleep latency 的时间长度;另一个 thread 会执行测试函数,每执行一次给一个全局变量 +1。 当主 thread 从 sleep 醒来就会输出对应的副 thread 执行轮数作为执行分数,也就是单位时间内测试程序执行的次数, 也就是测试程序执行时间的倒数。这个分数越高说明执行越快,性能越好。
[submodule "benchmark/byte-unixbenchmark"]
path = benchmark/byte-unixbenchmark
url = https://github.com/kdlucas/byte-unixbench.git
1.8.2.2. 移植过程¶
修改编译依赖的 config 选项:
在 Makefile 中设置 CC 为对应的交叉编译器路径 riscv64-unknown-linux-gnu-gcc
-CC=gcc
+CC=$(RISCV)/bin/riscv64-unknown-linux-gnu-gcc
在 Makefile 中设置 ARCH 为对应的 linux 类型 riscv64
- ARCH := $(shell uname -m)
+ ARCH := riscv64
之后我们编写一个类似的 execute 程序来执行我们想测试的程序。
ifeq ($(OSNAME),Linux)
# Not all CPU architectures support "-march" or "-march=native".
# - Supported : x86, x86_64, ARM, AARCH64, riscv64, etc..
@@ -142,7 +142,8 @@ SOURCES = arith.c big.c context1.c \
fstime.c hanoi.c \
pipe.c spawn.c \
syscall.c looper.c timeit.c time-polling.c \
- dhry_1.c dhry_2.c dhry.h whets.c ubgears.c
+ dhry_1.c dhry_2.c dhry.h whets.c ubgears.c \
+ execute.c
TESTS = sort.src cctest.c dc.dat large.txt
ifneq (,$(GRAPHIC_TESTS))
@@ -157,7 +158,8 @@ BINS = $(PROGDIR)/arithoh $(PROGDIR)/register $(PROGDIR)/short \
$(PROGDIR)/hanoi $(PROGDIR)/syscall $(PROGDIR)/context1 \
$(PROGDIR)/pipe $(PROGDIR)/spawn $(PROGDIR)/execl \
$(PROGDIR)/dhry2 $(PROGDIR)/dhry2reg $(PROGDIR)/looper \
- $(PROGDIR)/fstime $(PROGDIR)/whetstone-double $(GRAPHIC_BINS)
+ $(PROGDIR)/fstime $(PROGDIR)/whetstone-double $(GRAPHIC_BINS)\
+ $(PROGDIR)/execute
## These compile only on some platforms...
# $(PROGDIR)/poll $(PROGDIR)/poll2 $(PROGDIR)/select
@@ -293,6 +295,8 @@ $(PROGDIR)/dhry2reg: $(SRCDIR)/dhry_1.c $(SRCDIR)/dhry_2.c \
$(SRCDIR)/dhry.h $(SRCDIR)/timeit.c
$(CC) -o $@ ${CFLAGS} $(SRCDIR)/dhry_1.c $(SRCDIR)/dhry_2.c
+$(PROGDIR)/execute: $(SRCDIR)/execute.c
+
上述所有的修改,被我们打包到 benchmark/patch/unixbench.patch 当中,可以用 git apply 一键修改。
1.8.3. 文件系统调整¶
执行 benchmark_patch,将 benchmark/patch 的 lmbench.patch、unixbench.patch apply 到 Lmbench 和 Unixbench 中,完成上述的调整。
benchmark_patch:$(benchmark_patch)
cd $(unixbench_srcdir); git apply $(benchmark_patch)/UnixBench.patch
cd $(lmbench_srcdir); git apply $(benchmark_patch)/LmBench.patch
执行 benchmark,对 lmbench 和 unixbench 做编译,然后将编译的结果 Lmbench/bin/riscv-OS 和 Unixbench/pgms 拷贝到 sysroot 的 Lmbench 和 Unixbench 中。
benchmark: $(unixbench_wrkdir) $(lmbench_wrkdir) $(buildroot_initramfs_sysroot)
cp -r $(unixbench_wrkdir) $(buildroot_initramfs_sysroot)/
cp -r $(lmbench_wrkdir) $(buildroot_initramfs_sysroot)/
$(lmbench_wrkdir): $(lmbench_srcdir) $(libtirpc_lib)
rm -rf $(lmbench_wrkdir)
mkdir -p $(lmbench_wrkdir)
make -C $(lmbench_srcdir) clean
make -C $(lmbench_srcdir)
cp $(lmbench_srcdir)/bin/riscv-linux/* $(lmbench_wrkdir)
$(unixbench_wrkdir):$(unixbench_srcdir)
rm -rf $(unixbench_wrkdir)
mkdir -p $(unixbench_wrkdir)
make -C $(unixbench_srcdir) clean
make -C $(unixbench_srcdir)
cp $(unixbench_srcdir)/pgms/* $(unixbench_wrkdir)
cp $(unixbench_srcdir)/testdir/sort.src $(unixbench_wrkdir)
我们的一个测试程序需要对 /dev/zero、/dev/null 等进行操作, 所以需要我们的 buildroot 支持这些特殊设备, 所以我们需要对 conf/buildroot_initramfs_config 进行修改,加入 BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_MDEV=y 的配置。
然后我们就可以重新构造镜像下板子了。
1.8.4. regvault 简单测试¶
前面的文章我们讲了添加 regvault 的硬件,然后我们需要执行对应的 regvault 指令测试的对应的正确性。 当然我们可以 linux 内核里面大规模的做插桩,然后直接运行,如果 linux 可以顺利启动,那么 regvault 的硬件实现就成功了。 但是如果执行失败了我们想要调试也是很困难的,所以我们应该先执行一个比较小的 regvault 测试程序做初步的测试。 所以我们设计了一个执行 regvault 的内核模块。
static int __init rgvlt_init(void) {
text_t plaintext = 0xfb623599da6e8127;
qkey_t w0 = 0x84be85ce9804e94b;
qkey_t k0 = 0xec2802d4e0a488e9;
tweak_t tweak = 0x477d469dec0b8762;
text_t ciphertext;
printk(KERN_INFO "QARMA64 Plaintext = 0x%016llx\nKey = 0x%016llx || 0x%016llx\nTweak = 0x%016llx\n\n", plaintext, w0, k0, tweak);
asm volatile (
"csrw 0x5f0, %[k0]\n"
"csrw 0x5f1, %[w0]\n"
:
:[w0] "r" (w0), [k0] "r" (k0)
:
);
printk(KERN_INFO "k0, w0 write done\n");
qkey_t read_k0 = 0;
qkey_t read_w0 = 0;
asm volatile (
"csrr %[read_k0], 0x5f0\n"
"csrr %[read_w0], 0x5f1\n"
:[read_w0] "=r" (read_w0), [read_k0] "=r" (read_k0)
:
:
);
printk(KERN_INFO "read_w0 = 0x%llx, read_k0 = 0x%llx", read_w0, read_k0);
asm volatile (
"csrw 0x5f0, %[k0]\n"
"csrw 0x5f1, %[w0]\n"
"mv t0, %[plaintext]\n"
"mv t1, %[tweak]\n"
"li t2, 0\n"
".insn r 0x6b, 0x0, 0x54, t2, t0, t1\n"
"mv %[ciphertext], t2\n"
:[ciphertext] "=r" (ciphertext)
:[tweak] "r" (tweak), [plaintext] "r" (plaintext), [w0] "r" (w0), [k0] "r" (k0)
:"t0", "t1", "t2"
);
printk(KERN_INFO "Ciphertext = 0x%016llx", ciphertext);
text_t decrypttext;
asm volatile (
"mv t0, %[ciphertext]\n"
"mv t1, %[tweak]\n"
"li t2, 0\n"
".insn r 0x6b, 0x0, 0x55, t2, t0, t1\n"
"mv %[decrypttext], t2\n"
:[decrypttext] "=r" (decrypttext)
:[ciphertext] "r" (ciphertext), [tweak] "r" (tweak)
:"t0", "t1", "t2"
);
printk(KERN_INFO "Decrypttext = 0x%016llx\n", decrypttext);
return 0;
}
然后是对应的 makefile 文件,需要注意的是,因为需要链接的符号表、代码信息都是 build/linux 中交叉编译的结果,所以 KERNEL_DIR 是 build/linux 的结果。
# General Purpose Makefile for Linux Kernel module
ARCH := riscv # set if cross-compile
CROSS_COMPILE := $(RISCV)/bin/riscv64-unknown-linux-gnu- # set if cross-compile
KERNEL_DIR := $(CURDIR)/../../build/linux/
obj-m := rgvlt_test.o
default:
$(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNEL_DIR) M=$(CURDIR) modules
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
之后我们编译得到 rgvlt_test.ko,将他保存在文件系统的 regvault 文件夹中。
1.8.5. 软件测试¶
上述程序的正确性可以在 spike 上进行验证。 我们先构造 spike 的镜像,然后在 spike 上执行这些程序验证。
进入 /Lmbench,然后执行 ./execute,就可以开始测试,测试结果如下:
# cd /LmBench/
# ./execute
execute read
pass 0
Simple read: 8.5936 microseconds
Elapsed time: 0.647 seconds
execute write
pass 0
Simple write: 6.8206 microseconds
Elapsed time: 0.647 seconds
execute open
pass 0
Simple open/close: 192.6923 microseconds
Elapsed time: 0.679 seconds
execute select
pass 0
Select on 500 fd's: 774.1429 microseconds
Elapsed time: 0.658 seconds
...
进入 /Unixbench,然后执行 ./execute,就可以开始测试,测试结果如下:
# cd /UnixBench/
# ./execute
execute dhrystone
pass 0
COUNT|3665097|1|lps
pass 1
COUNT|3671664|1|lps
pass 2
COUNT|3672307|1|lps
pass 3
COUNT|3675095|1|lps
pass 4
COUNT|3676492|1|lps
pass 5
COUNT|3676371|1|lps
pass 6
COUNT|3675313|1|lps
pass 7
COUNT|3673990|1|lps
Elapsed time: 80.639 seconds
execute whetstone
pass 0
Calibrate
0.11 Seconds 1 Passes (x 100)
0.52 Seconds 5 Passes (x 100)
...
最后我们进入 /regvault,然后执行 insmod 操作进行验证;不过需要 spike 支持 regvault。
如果三个执行结果都没有问题,那么就说明软件应该没有明显问题,我们可以下板子进行性能测试了。
1.8.6. 下板测试¶
下板之后的测试方式和 spike 上的测试方式是一样的。
测试结果使用串口输出,打印到机器的 screen 上面,然后可以用如下方式截取:
ctrl+A+[:进入浏览模式,可以滚动滚轮上下拉动屏幕
空格:选择要复制的起点
滚轮:滚轮方向的内容都会被复制选中
空格:选择要复制的终点,这些部分会被保存到 copy buffer 中
ctrl+A+>:将选择的内容保存到 /tmp/screen-exchange 中
之后在本机器的 /tmp/screen-exchange 获得这部分数据即可
需要注意,screen 不会保存所有的串口输出,只有大概 1000 行左右,所以要及时拷贝需要的数据。