有参考其他的
一、实验预备知识
(1)库函数 memset
简介:
memset
函数作用:将从 s 开始的 n 个字节的内存区域全都填充为 c 的低位字节。它是对较大的结构体或数组进行清零操作的一种最快方法。
(2)库函数memset
的最直接实现:
/* Basic implementation of meset */ void *basic_memset(void *s, int c, size_t n) { size_t cnt = 0; unsigned char *schar = (unsigned char *)s; while (cnt < n) { *schar++ = (unsigned char) c; cnt++; } return s; }
(3)CPE:cycles per element,每元素的周期数,是度量程序性能的指标。CPE越低,程序运行速度越快。因此,优化程序的一个目标是减少CPE。
(4)size_t:它是一个与机器相关的 unsigned 类型,定义在 cstddef 头文件中,其大小足以保证存储内存中对象的大小。设计 size_t 是为了适应多个平台,增强程序在不同平台上的可移植性。通常使用 sizeof() 操作得到的结果就是 size_t 类型的。在64位系统中为 long long unsigned int ,大小为8个字节。
二、实验目的
(1)实验预期:实现 memset
函数的更有效的版本,在参考机上,能做到把CPE从直接实现的1.00降低到0.127,也就是程序在每个周期可以写8个字节。
(2)实验可行性分析:
从库函数 memset
直接实现方式可以看出,填充的区域长度 n 是 size_t 类型的,用于填充的数据 c 是 int 类型的,且实现时我们将 c 强制转换为 unsigned char 类型。在64位系统里,int类型为4个字节,size_t 是8个字节,而 unsigned char 是1个字节的,也就是我们用c的最低位字节来填充长度为n的区域。
联系所学的代码优化方法,基本的代码移动、共用表达式(已有的表达式重复使用不额外重复计算)等方法在此处不能很好地改善代码效率。
观察发现,每次我们只填充1个字节的长度,需要填充 n 次。那么,可以考虑利用循环展开的方式,每次填充 k 个字节,这样在 n%k = 0 的情况下只需要填充 n/k 次即可,当 n 不为 k 的倍数时另做考虑。但很容易知道,这种方法可以减少循环次数,最终可以降低此程序的CPE,提高程序运行速度。
三、实验设计
根据(二)中的实验可行性分析,我们考虑采用循环展开方式进行程序优化。
在这里,我尝试在填充时每次使用数据类型为unsigned long的字来进行填充。unsigned long类型标准为4字节或8字节,在本机系统上,其为4字节的。
记 K = sizeof(unsigned long),我们参考原有的 memset
实现来进行改进。
四、实验结果
采用这种设计方法进行memset
库函数的优化,可以改进代码的性能。利用 clock() 函数可以计算代码运行所消耗的时钟周期数。通过与memset
的最直接实现进行比较,我们会发现利用循环展开方式进行优化后代码性能有所提升。
五、源代码
#include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> // memset库函数的更有效实现,填充数据改为 unsigned long 类型 void* eff_memset(void* s, unsigned long c, size_t n) { size_t K = sizeof(unsigned long); //本机上K=4 size_t cnt = 0; //记循环次数 unsigned char* schar = (unsigned char*)s; //将s强制转换为unsigned char类型 while (cnt < n) { if ((size_t)schar % K == 0) //判断开始位置是否为K的倍数 break; //开始地址不是4的倍数,先用unsigned char类型进行填充 *schar++ = (unsigned char)c; cnt++; } unsigned long* slong = (unsigned long*)schar; //将s强制转换为unsigned long类型 size_t num = n - cnt; //去除已填充的区域 size_t main_body = num / K; size_t rest = num % K; //主体采用循环展开方式用unsigned long类型数据填充 for (size_t i = 0; i < main_body; i++) *slong++ = c; //针对num不是K的倍数时,收尾部分用unsigned char类型数据填充 schar = (unsigned char*)slong; for (size_t i = 0; i < rest; i++) *schar++ = (unsigned char)c; return s; } int main(int argc, char* argv[]) { size_t space = sizeof(char) * 256; void* eff_space = malloc(space); unsigned long fill = ~0; eff_memset(eff_space, fill, space); free(eff_space); return 0; }