我必须做一个类练习,我必须做一些子程序,然后检查缓存未命中,这是atachment:
我必须创建2个子程序,其中s1是我的L1缓存32kb的容量,b1是64字节的行。
subroutine A: increment all bytes of a memory buffer containing 2*s1 bytes
in the order of increasing memory addresses;
subroutine B: increment each b1-th byte of a memory buffer containing 2*s1
bytes in the order of increasing memory addresses;
对于子程序A,我认为我必须这样做:
char buffer[2*s1];
printf...
buffer++;
printf...
printf都会显示:
buffer[0]= 0x7fff36769fe0 buffer[1]= 0x7fff36769fe1
buffer[0]= 0x7fff36769fe1 buffer[1]= 0x7fff36769fe2
所有字节都会增加,所以,我认为它是正确的,对于子程序B,我不知道......所以,我想对子程序B有一些帮助。
如果有人可以帮助我,那就太好了。
谢谢!
答案 0 :(得分:0)
这些练习仅作为优化课程才有意义。不幸的是,他们并没有完全击中指甲的头部。
程序员浪费了大量时间来考虑或担心程序中非关键部分的速度,而这些效率尝试实际上在考虑调试和维护时会产生很大的负面影响。我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源。然而,我们不应该把这个关键的3%的机会放弃。
这是一个着名的引文,与其背景一样,也许是我们在这个领域可以达成一致的最有声望的老师:Donald Knuth。继本练习之后,您的教授可能会引导您完成未经优化的代码的分析,为您介绍个人资料。
如果我们应用手动优化,好像编译器不执行任何操作,并且只是稍后启用编译器自动优化,我们还能希望改进什么?也许编译器可能会自动执行我们手动执行的相同优化(或更好的优化),但只需要很短的时间。
在该引言中,这些数字背后有一个营销/策划策略。在商业世界中,甚至作为业余爱好者,您将花费97%的时间来开发并测试解决实际问题的程序。注意强调实际问题。您不会为了观察缓存行为而编写和测试程序。您将编写和测试程序来解决现实生活中的问题。
一旦您完整地编写并测试了整个程序,您就会知道感觉是否足够快(例如,您可能会被您的客户告知/雇主优化它)。如果感觉足够快,即使每个地方都有缓存未命中,您也不会对其进行优化;你甚至不知道有缓存未命中。但是,如果感觉太慢,那么您需要在完全优化的代码上使用分析器来确定最重要的瓶颈在哪里,花费大约3%你的时间在那个瓶颈上......
可以乐观地编程,但这不是像这些练习那样的观察练习;它是计划和保理的一项练习。一个指南是避免重复自己,这是我用下面的代码完成的。通过确定两个练习之间的常见行为(例如,从低索引到高索引的循环,将每个元素递增1)我已经将两个循环减少到一个,并避免重复自己同时。考虑到您的程序尽量避免重复,并且您的代码会更小,从而减少测试,维护,当然也可以减少重复的击键次数。 大多数优化是我们的时间的优化,而不是计算机时间的优化!碰巧我们也通过缩小代码来优化计算机时间。
void increment_every(char *buffer, size_t multiple, size_t maximum) {
while (maximum > multiple) {
buffer[0]++;
buffer += multiple;
maximum -= multiple;
}
}
您应该能够在此功能方面解决这两个问题。例如:
#define S1 32768 /* 32KB */
#define B1 64 /* 64 B */
int main(void) {
char buffer[2 * S1] = { 0 };
increment_every(buffer, 1, sizeof buffer); // subroutine A
if (B1 < S1) { // subroutine B
increment_every(buffer + B1, B1, sizeof buffer - B1);
}
}
假设S1
和B1
是来自文件或交互式设备的固定值。这种假设会导致编译器在优化方面非常积极。事实上,这个程序可能会优化到int main(void) { }
,因为没有可观察的行为;此程序中任何地方的任何文件或交互设备都没有输入或输出!希望现在,您将在下一课中看到一些真正的教训: