一次性对内存副本进行基准测试

圣安东尼奥

Whiskey Lake i7-8565U

我正在尝试学习如何手动编写基准测试(不使用任何基准测试框架),该示例以内存复制例程为例,其中常规写入和非临时写入WB内存,并希望进行某种审查。


宣言:

void *avx_memcpy_forward_llss(void *restrict, const void *restrict, size_t);

void *avx_nt_memcpy_forward_llss(void *restrict, const void *restrict, size_t);

定义:

avx_memcpy_forward_llss:
    shr rdx, 0x3
    xor rcx, rcx
avx_memcpy_forward_loop_llss:
    vmovdqa ymm0, [rsi + 8*rcx]
    vmovdqa ymm1, [rsi + 8*rcx + 0x20]
    vmovdqa [rdi + rcx*8], ymm0
    vmovdqa [rdi + rcx*8 + 0x20], ymm1
    add rcx, 0x08
    cmp rdx, rcx
    ja avx_memcpy_forward_loop_llss
    ret

avx_nt_memcpy_forward_llss:
    shr rdx, 0x3
    xor rcx, rcx
avx_nt_memcpy_forward_loop_llss:
    vmovdqa ymm0, [rsi + 8*rcx]
    vmovdqa ymm1, [rsi + 8*rcx + 0x20]
    vmovntdq [rdi + rcx*8], ymm0
    vmovntdq [rdi + rcx*8 + 0x20], ymm1
    add rcx, 0x08
    cmp rdx, rcx
    ja avx_nt_memcpy_forward_loop_llss
    ret

基准代码:

#include <stdio.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <immintrin.h>
#include <x86intrin.h>
#include "memcopy.h"

#define BUF_SIZE 128 * 1024 * 1024

_Alignas(64) char src[BUF_SIZE];
_Alignas(64) char dest[BUF_SIZE];

static inline void warmup(unsigned wa_iterations, void *(*copy_fn)(void *, const void *, size_t));
static inline void cache_flush(char *buf, size_t size);
static inline void generate_data(char *buf, size_t size);

uint64_t run_benchmark(unsigned wa_iteration, void *(*copy_fn)(void *, const void *, size_t)){
    generate_data(src, sizeof src);
    warmup(4, copy_fn); 
    cache_flush(src, sizeof src);
    cache_flush(dest, sizeof dest);
    __asm__ __volatile__("mov $0, %%rax\n cpuid":::"rax", "rbx", "rcx", "rdx", "memory"); 
    uint64_t cycles_start = __rdpmc((1 << 30) + 1); 
    copy_fn(dest, src, sizeof src); 
    __asm__ __volatile__("lfence" ::: "memory"); 
    uint64_t cycles_end = __rdpmc((1 << 30) + 1); 
    return cycles_end - cycles_start; 
}

int main(void){
    uint64_t single_shot_result = run_benchmark(1024, avx_memcpy_forward_llss);
    printf("Core clock cycles = %" PRIu64 "\n", single_shot_result);
}

static inline void warmup(unsigned wa_iterations, void *(*copy_fn)(void *, const void *, size_t)){
    while(wa_iterations --> 0){
        copy_fn(dest, src, sizeof src);
        copy_fn(dest, src, sizeof src);
        copy_fn(dest, src, sizeof src);
        copy_fn(dest, src, sizeof src);
        copy_fn(dest, src, sizeof src);
        copy_fn(dest, src, sizeof src);
        copy_fn(dest, src, sizeof src);
        copy_fn(dest, src, sizeof src);
    }
}

static inline void generate_data(char *buf, size_t sz){
    int fd = open("/dev/urandom", O_RDONLY);
    read(fd, buf, sz);
}

static inline void cache_flush(char *buf, size_t sz){
    for(size_t i = 0; i < sz; i+=_SC_LEVEL1_DCACHE_LINESIZE){
        _mm_clflush(buf + i);
    }
}

结果

avx_memcpy_forward_llss中位数:44479368核心周期

UPD:时间

real    0m0,217s
user    0m0,093s
sys     0m0,124s

avx_nt_memcpy_forward_llss中位数:24053086核心周期

UPD:时间

real    0m0,184s
user    0m0,056s
sys     0m0,128s

UPD:在运行基准测试时获得结果 taskset -c 1 ./bin

因此,两次内存复制例程实现之间的核心周期差异几乎达到了2倍。我将其解释为在将常规存储到WB存储器的情况下,我们有RFO请求在IOM / 3.6.12(强调我的)中规定的总线带宽上竞争:

尽管由于 非临时性存储而导致的完整64字节总线写入的数据带宽是总线写入WB存储器的数据带宽的两倍,但传输8字节块会浪费总线请求带宽并显着降低数据带宽。

问题1:单发情况下如何进行基准分析?由于性能启动开销和预热迭代开销,性能计数器似乎没有用。

问题2:这样的基准是否正确?cpuid一开始就进行了说明,以便开始使用干净的CPU资源进行测量,以避免由于先前的飞行指令而造成停顿。我添加了内存碎片作为编译屏障,并lfence避免rdpmc被执行OoO。

约翰·麦卡平

基准测试应尽可能以尽可能允许“健全性检查”的方式报告结果。在这种情况下,启用这种检查的几种方法包括:

  1. 对于涉及主内存带宽的测试,结果应以可直接与系统已知DRAM峰值带宽进行比较的单位表示。对于Core i7-8565U的典型配置,这是2个通道* 8字节/传输* 24亿个传输/秒= 38.4 GB / s(另请参阅下文第(6)项。)
  2. 对于涉及在存储器层次结构中任何地方进行数据传输的测试,结果应包括对“内存占用量”大小(访问的不同缓存行地址的数量乘以缓存行大小)的清晰描述,以及重复的次数。转移。您的代码在这里很容易阅读,并且大小对于主内存测试是完全合理的。
  3. 对于任何定时测试,都应包括绝对时间,以便与合理的定时开销进行比较。您仅使用CORE_CYCLES_UNHALTED计数器就无法直接计算经过的时间(尽管测试时间足够长,因此计时开销可以忽略不计)。

其他重要的“最佳实践”原则:

  1. 任何使用RDPMC指令的测试都必须绑定到单个逻辑处理器。结果的显示方式应向读者确认已采用这种绑定。在Linux中强制执行此类绑定的常见方法包括使用“任务集”或“ numactl --physcpubind = [n]”命令,或使用单个允许的逻辑处理器对“ sched_setaffinity()”进行内联调用,或设置环境变量这会导致运行时库(例如OpenMP)将线程绑定到单个逻辑处理器。
  2. 使用硬件性能计数器时,需要格外小心,以确保计数器的所有配置数据均可用并正确描述。上面的代码使用RDPMC读取IA32_PERF_FIXED_CTR1,其事件名称为CPU_CLK_UNHALTED。事件名称的修饰符取决于IA32_FIXED_CTR_CTRL(MSR 0x38d)位7:4的编程。从所有可能的控制位到事件名称修饰符的映射没有普遍接受的方式,因此最好提供IA32_FIXED_CTR_CTRL的完整内容以及结果。
  3. CPU_CLK_UNHALTED性能计数器事件是用于对处理器行为随处理器核心频率直接扩展的部分进行基准测试的正确方法,例如指令执行和仅涉及L1和L2高速缓存的数据传输。内存带宽涉及处理器的某些部分,这些部分的性能不会直接随处理器频率扩展。特别是,在不强制执行固定频率操作的情况下使用CPU_CLK_UNHALTED使得无法计算经过时间(上述(1)和(3)要求)。在您的情况下,RDTSP比RDPMC更容易-RDTSC不需要将进程绑定到单个逻辑处理器,它不受其他配置MSR的影响,并且可以直接计算经过的时间(以秒为单位)。
  4. 进阶:对于涉及存储器层次结构中数据传输的测试,控制缓存内容和缓存内容的状态(干净或脏)并提供对“ before”和“ after”状态的明确描述很有帮助。结果。给定数组的大小,您的代码应使用源数组和目标数组的部分组成部分完全填充缓存的所有级别,然后刷新所有这些地址,从而使(几乎)完全无效的缓存层次结构(干净)条目。
  5. 高级:将CPUID用作序列化指令几乎对基准测试毫无用处。尽管它保证排序,但执行时间也很长-Agner Fog的“指令表”以100-250个周期报告它(大概取决于输入参数)。(更新:短时间间隔内的测量总是非常棘手。CPUID指令执行时间长且可变,并且不清楚微编码实现对处理器内部状态的影响。在某些情况下可能会有所帮助,但不应将其视为基准测试中自动包含的内容。对于长时间间隔的测量,可以忽略跨越测量边界的乱序处理,因此不需要CPUID。)
  6. 高级:仅当您以非常精细的粒度(少于几百个周期)进行测量时,才在基准测试中使用LFENCE才有意义。有关此主题的更多说明,请访问http://sites.utexas.edu/jdm4372/2018/07/23/comments-on-timing-short-code-sections-on-intel-processors/

如果我假设您的处理器在测试过程中以其最大Turbo频率4.6 GHz运行,则报告的周期数分别对应于9.67毫秒和5.23毫秒。将这些插入“健全性检查”中将显示:

  • 假设第一种情况执行一次读取,一次分配和一次回写(每个128MiB),则相应的DRAM流量为27.8GB / s + 13.9 GB / s = 41.6 GB / s ==峰值的108%。
  • 假设第二种情况执行一个读取和一个流存储(每个128MiB),则相应的DRAM流量为25.7 GB / s + 25.7 GB / s = 51.3 GB / s =峰值的134%。

这些“健全性检查”的失败告诉我们,该频率不可能高达4.6 GHz(并且可能不高于3.0 GHz),但是大部分只是指出需要明确地测量经过的时间...。

您在优化手册中对流存储效率低下的报价仅适用于无法合并为完整缓存行传输的情况。您的代码将遵循“最佳实践”建议存储到输出缓存行的每个元素(写入同一行的所有存储指令都将连续执行,并且每个循环仅生成一个存储流)。不可能完全防止硬件破坏流媒体商店,但是在您的情况下,它应该极为罕见-可能只有百万分之一。检测部分流存储是一个非常高级的主题,要求在“非核心”中使用文档记录不佳的性能计数器,和/或通过查找升高的DRAM CAS计数(可能是由于其他原因)间接检测部分流存储。http://sites.utexas.edu/jdm4372/2018/01/01/notes-on-non-temporal-aka-streaming-stores/

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

Valgrind:抑制一次性内存泄漏

来自分类Dev

一次性方法的单元测试

来自分类Dev

一次性随机

来自分类Dev

了解一次性物品

来自分类Dev

XmlWriter-一次性

来自分类Dev

一次性密码

来自分类Dev

HTML一次性页面

来自分类Dev

一次性加密分区

来自分类Dev

Hangfire 的一次性工作

来自分类Dev

C#一次性对象未废弃,但使用的内存未增加

来自分类Dev

实现IDisposable-一次性字段与一次性属性

来自分类Dev

PayPal一次性付款的一次性付款

来自分类Dev

Spring Boot中的外部配置和一次性测试

来自分类Dev

带有随机值的Dynamics CRM 2011一次性批量更新测试记录

来自分类Dev

Spring Boot中的外部配置和一次性测试

来自分类Dev

带有ReactiveCocoa的MVVM,ViewModel如何告诉View进行一次一次性操作?

来自分类Dev

使用Django和Twilio通过SMS进行一次性用户身份验证

来自分类Dev

选择TSDB进行一次性智能家居安装

来自分类Dev

在groovy中一次性进行空值和空值检查

来自分类Dev

在groovy中一次性进行空值和空值检查

来自分类Dev

如何一次性通过DF中的Pandas行进行多列转换

来自分类Dev

如何在 CQRS 系统中正确进行一次性查询?

来自分类Dev

一次性对 scipy 的“curve_fit”进行多次迭代

来自分类Dev

在Keil uVision 5仿真器中对内存使用进行基准测试

来自分类Dev

如何在嵌入式Linux设备中对内存进行基准测试?

来自分类Dev

在 Bash 中对内存和时间进行基准测试的错误自制代码

来自分类Dev

一次性循环使用的必要性

来自分类Dev

为什么第一次进行微基准测试总是最慢?

来自分类Dev

创建并写入文本文件内存,并一次性转换为字节数组

Related 相关文章

热门标签

归档