学习效率不高,怎么调整
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习效率不高,怎么调整相关的知识,希望对你有一定的参考价值。
要想进入高效率学习状态,一般要做到以下几点:
(1)保持良好的作息习惯
保持良好的作息习惯能在一定程度上帮助大家提高白天学习的效率。对于很多同学来说,早上的第一节课基本上都是在浑浑噩噩中度过,排山倒海般袭来的困意很难让自己打起精神来听课,难免会错过课上老师讲的知识点和解题技巧。因此,大家在平时一定要养成良好的作息习惯,每天严格执行,按时起床、睡觉。
(2)养成良好的学习习惯
良好的作息习惯是高效学习的前提,那么良好的学习习惯则是高效学习的助推剂。在学习的过程中,首先应该注重听课效率。一定要注重课堂教学,提高听课效率,争取在课堂上就理解老师讲的知识点。
(3)用好学习资源
有的同学上课学习时已经非常专注了,但还是无法使效率达到最大化,课后硬学效率又很低,只能找老师同学再请教。但这种情况反反复复下来,你可能要考虑是否自身学习方法有缺陷了,所以要学会选择和利用学习资源,减少大量重复的无效学习,提升学习效率和学习质量,如“精英特速读记忆”运用的是快速阅读方法,简单来就是说通过快速阅读文章和材料,然后快速的提取段落、文章的脉络和重点,促进整理归纳分析,训练学习者的阅读速度、快速理解和重点记忆,达到事半功倍的效果。另外软件中有对注意力、阅读理解思维、记忆力的训练,训练过程中用实际的练习来体现出来,而不是简单的告诉你怎样去练习。当你认真地锻炼后,对学习中思维迟缓、理解跟不上、记不住等根本问题会有明显的改善,培养看良好的学习方法后,对于你以后学习生活都是受益匪浅的。
(4) 制定“每日计划”
对于每天的空闲时间,建议大家制作一个简易的每日计划,主要列出当天需要完成的任务以及完成程度。每日一结,每日一反思。对于当天没有完成的任务,应该反思原因并做出下一步的安排,尽快完成,最好不要拖好几天。
参考技术A 1、随身携带学习卡片。 不积跬步,无以至千里,不积小流,无以成江海。将一些学习内容制成学习卡片随身携 带,利用闲碎的时间进行复习,效果显著。2、“随手笔记”。 所谓“随手笔记”就是上课时随机的记。记自己的灵感、记重点、易错点、 他人错点。“随 手笔记”自己明了就可。
3、做标记符号。 对以下内容我们要标记不同的符号,老师讲课时的重点、易错点,他人的错点,老师讲 课时点出的“题眼”,错误的题目,不会的题目,经他人提示后会的题目等。做标记符号 是使书“变薄”的重要手段,是我们复习时的重要资料。
4、错题集的整理和浏览。 有的同学怕麻烦不愿意整理错题集,这是个不好的习惯。学习是如何发展的,就是不断 地查找漏洞,弥补漏洞。错题集的整理和浏览就是查缺补漏的一个手段。
5、复习时先“回忆”。 “回忆”是高级的复习。复习时先“回忆”,使你尽快进入学习状态,培养动脑的习惯, 并且做到复习时心中有数。
6、帮助他人学习。 帮助他人学习实际上就是复习的过程, 所谓的“教学相长”,在帮助他人学习过程中随时 可以发现自己的不足,可以随时纠正,也可以在帮助他人的过程中学习他人的长处。帮 助他人学习是“深度学习”。
7、目标。 人活着就得有个目标,目标是人成长的持久动力,是人前进的不枯的源泉。作为一个学 生要有:人生目标、现阶段目标、学期目标、每日目标。
8、整理书包。 整理书包就是在梳理学习思路,是无意识中的学习计划。
9、使复习像考试一样紧张。 实验表明:适度的紧张可以提高效率。我们复习时应该采用“限时复习法”,也就是说每 一段复习内容限定适当的时间,尽量要求自己在规定时间内完成。
10、“全局观念”。 所谓的“全局观念”,就是在学习过程中一定要掌握这一章、这一阶段、这一学期的学习 内容。很多同学学习时“糊涂”了,主要原因就是“不识庐山真面目,只缘身在此山中”。 我们要“会当凌绝顶,一览众山小”。因此,在预习、复试时都要注意“全局观念”。
为啥这段代码效率不高?
【中文标题】为啥这段代码效率不高?【英文标题】:Why this code is not efficient?为什么这段代码效率不高? 【发布时间】:2011-12-15 11:42:32 【问题描述】:我想改进下一个代码,计算平均值:
void calculateMeanStDev8x8Aux(cv::Mat* patch, int sx, int sy, int& mean, float& stdev)
unsigned sum=0;
unsigned sqsum=0;
const unsigned char* aux=patch->data + sy*patch->step + sx;
for (int j=0; j< 8; j++)
const unsigned char* p = (const unsigned char*)(j*patch->step + aux ); //Apuntador al inicio de la matrix
for (int i=0; i<8; i++)
unsigned f = *p++;
sum += f;
sqsum += f*f;
mean = sum >> 6;
int r = (sum*sum) >> 6;
stdev = sqrtf(sqsum - r);
if (stdev < .1)
stdev=0;
我还使用 NEON 内在函数改进了下一个循环:
for (int i=0; i<8; i++)
unsigned f = *p++;
sum += f;
sqsum += f*f;
这是为另一个循环改进的代码:
int32x4_t vsum= 0 ;
int32x4_t vsum2= 0 ;
int32x4_t vsumll = 0 ;
int32x4_t vsumlh = 0 ;
int32x4_t vsumll2 = 0 ;
int32x4_t vsumlh2 = 0 ;
uint8x8_t f= vld1_u8(p); // VLD1.8 d0, [r0]
//int 16 bytes /8 elementos
int16x8_t val = (int16x8_t)vmovl_u8(f);
//int 32 /4 elementos *2
int32x4_t vall = vmovl_s16(vget_low_s16(val));
int32x4_t valh = vmovl_s16(vget_high_s16(val));
// update 4 partial sum of products vectors
vsumll2 = vmlaq_s32(vsumll2, vall, vall);
vsumlh2 = vmlaq_s32(vsumlh2, valh, valh);
// sum 4 partial sum of product vectors
vsum = vaddq_s32(vall, valh);
vsum2 = vaddq_s32(vsumll2, vsumlh2);
// do scalar horizontal sum across final vector
sum += vgetq_lane_s32(vsum, 0);
sum += vgetq_lane_s32(vsum, 1);
sum += vgetq_lane_s32(vsum, 2);
sum += vgetq_lane_s32(vsum, 3);
sqsum += vgetq_lane_s32(vsum2, 0);
sqsum += vgetq_lane_s32(vsum2, 1);
sqsum += vgetq_lane_s32(vsum2, 2);
sqsum += vgetq_lane_s32(vsum2, 3);
但它或多或少慢了 30 毫秒。有谁知道为什么?
所有代码都运行正常。
【问题讨论】:
有很多因素会影响性能(假设您以正确的方式测量处理时间)。显然你正在使用 OpenCV,所以我会说图像的大小有很大的不同。有多大? 单位“mms”是什么意思?是“毫秒”吗? “毫米秒”? 为什么标记为 C?它是 C++。cv::Mat
等
您是否将其用于实时流数据?
图片是320X240,是的,我是实时使用的,所以帧的时间不同
【参考方案1】:
添加到伦丁。是的,像 ARM 这样的指令集,你有一个基于寄存器的索引或一些直接索引,你可能会受益于鼓励编译器使用索引。此外,例如 ARM 可以在加载指令中增加其指针寄存器,基本上 *p++ 在一条指令中。
使用 p[i] 或 p[i++] 与 *p 或 *p++ 总是一个折腾,一些指令集更明显地采用哪条路径。
您的索引也是如此。如果你不使用它倒计时而不是倒计时可以节省每个循环的指令,也许更多。有些人可能会这样做:
inc reg
cmp reg,#7
bne loop_top
如果您正在倒计时,但您可能会在每个循环中保存一条指令:
dec reg
bne loop_top
甚至是我知道的一个处理器
decrement_and_jump_if_not_zero loop_top
编译器通常知道这一点,您不必鼓励他们。但是,如果您使用内存读取顺序很重要的 p[i] 形式,那么编译器不能或至少不应该任意更改读取顺序。因此,对于这种情况,您可能希望代码倒计时。
所以我尝试了所有这些方法:
unsigned fun1 ( const unsigned char *p, unsigned *x )
unsigned sum;
unsigned sqsum;
int i;
unsigned f;
sum = 0;
sqsum = 0;
for(i=0; i<8; i++)
f = *p++;
sum += f;
sqsum += f*f;
//to keep the compiler from optimizing
//stuff out
x[0]=sum;
return(sqsum);
unsigned fun2 ( const unsigned char *p, unsigned *x )
unsigned sum;
unsigned sqsum;
int i;
unsigned f;
sum = 0;
sqsum = 0;
for(i=8;i--;)
f = *p++;
sum += f;
sqsum += f*f;
//to keep the compiler from optimizing
//stuff out
x[0]=sum;
return(sqsum);
unsigned fun3 ( const unsigned char *p, unsigned *x )
unsigned sum;
unsigned sqsum;
int i;
sum = 0;
sqsum = 0;
for(i=0; i<8; i++)
sum += (unsigned)p[i];
sqsum += ((unsigned)p[i])*((unsigned)p[i]);
//to keep the compiler from optimizing
//stuff out
x[0]=sum;
return(sqsum);
unsigned fun4 ( const unsigned char *p, unsigned *x )
unsigned sum;
unsigned sqsum;
int i;
sum = 0;
sqsum = 0;
for(i=8; i;i--)
sum += (unsigned)p[i-1];
sqsum += ((unsigned)p[i-1])*((unsigned)p[i-1]);
//to keep the compiler from optimizing
//stuff out
x[0]=sum;
return(sqsum);
同时使用 gcc 和 llvm (clang)。当然,两者都展开了循环,因为它是一个常数。 gcc,为每个实验产生相同的代码,以防寄存器组合发生细微的变化。我会争辩一个错误,因为其中至少有一个错误不是按照代码描述的顺序进行的。
所有四个的 gcc 解决方案都是这样,有一些读取重新排序,请注意源代码中的读取是无序的。如果这违反了依赖于按照代码描述的顺序读取的硬件/逻辑,那么您将遇到一个大问题。
00000000 <fun1>:
0: e92d05f0 push r4, r5, r6, r7, r8, sl
4: e5d06001 ldrb r6, [r0, #1]
8: e00a0696 mul sl, r6, r6
c: e4d07001 ldrb r7, [r0], #1
10: e02aa797 mla sl, r7, r7, sl
14: e5d05001 ldrb r5, [r0, #1]
18: e02aa595 mla sl, r5, r5, sl
1c: e5d04002 ldrb r4, [r0, #2]
20: e02aa494 mla sl, r4, r4, sl
24: e5d0c003 ldrb ip, [r0, #3]
28: e02aac9c mla sl, ip, ip, sl
2c: e5d02004 ldrb r2, [r0, #4]
30: e02aa292 mla sl, r2, r2, sl
34: e5d03005 ldrb r3, [r0, #5]
38: e02aa393 mla sl, r3, r3, sl
3c: e0876006 add r6, r7, r6
40: e0865005 add r5, r6, r5
44: e0854004 add r4, r5, r4
48: e5d00006 ldrb r0, [r0, #6]
4c: e084c00c add ip, r4, ip
50: e08c2002 add r2, ip, r2
54: e082c003 add ip, r2, r3
58: e023a090 mla r3, r0, r0, sl
5c: e080200c add r2, r0, ip
60: e5812000 str r2, [r1]
64: e1a00003 mov r0, r3
68: e8bd05f0 pop r4, r5, r6, r7, r8, sl
6c: e12fff1e bx lr
加载索引和微妙的寄存器混合是 gcc 函数之间的唯一区别,所有操作都以相同的顺序进行。
llvm/clang:
00000000 <fun1>:
0: e92d41f0 push r4, r5, r6, r7, r8, lr
4: e5d0e000 ldrb lr, [r0]
8: e5d0c001 ldrb ip, [r0, #1]
c: e5d03002 ldrb r3, [r0, #2]
10: e5d08003 ldrb r8, [r0, #3]
14: e5d04004 ldrb r4, [r0, #4]
18: e5d05005 ldrb r5, [r0, #5]
1c: e5d06006 ldrb r6, [r0, #6]
20: e5d07007 ldrb r7, [r0, #7]
24: e08c200e add r2, ip, lr
28: e0832002 add r2, r3, r2
2c: e0882002 add r2, r8, r2
30: e0842002 add r2, r4, r2
34: e0852002 add r2, r5, r2
38: e0862002 add r2, r6, r2
3c: e0870002 add r0, r7, r2
40: e5810000 str r0, [r1]
44: e0010e9e mul r1, lr, lr
48: e0201c9c mla r0, ip, ip, r1
4c: e0210393 mla r1, r3, r3, r0
50: e0201898 mla r0, r8, r8, r1
54: e0210494 mla r1, r4, r4, r0
58: e0201595 mla r0, r5, r5, r1
5c: e0210696 mla r1, r6, r6, r0
60: e0201797 mla r0, r7, r7, r1
64: e8bd41f0 pop r4, r5, r6, r7, r8, lr
68: e1a0f00e mov pc, lr
更容易阅读和遵循,也许考虑缓存并一次性读取所有内容。至少在一种情况下,llvm 的读取也出现了乱序。
00000144 <fun4>:
144: e92d40f0 push r4, r5, r6, r7, lr
148: e5d0c007 ldrb ip, [r0, #7]
14c: e5d03006 ldrb r3, [r0, #6]
150: e5d02005 ldrb r2, [r0, #5]
154: e5d05004 ldrb r5, [r0, #4]
158: e5d0e000 ldrb lr, [r0]
15c: e5d04001 ldrb r4, [r0, #1]
160: e5d06002 ldrb r6, [r0, #2]
164: e5d00003 ldrb r0, [r0, #3]
是的,为了对 ram 中的一些值进行平均,顺序不是问题,继续。
所以编译器选择了展开的路径并且不关心微优化。由于循环的大小,两者都选择烧毁一堆寄存器,每个循环都保存一个加载的值,然后从这些临时读取或乘法中执行加法。如果我们稍微增加循环的大小,我希望在展开的循环中看到 sum 和 sqsum 累积,因为它用完了寄存器,或者将达到他们选择不展开循环的阈值。
如果我传入长度,并将上面代码中的 8 替换为传入的长度,则强制编译器对此进行循环。您可以看到优化,使用如下指令:
a4: e4d35001 ldrb r5, [r3], #1
作为 arm,他们在一个地方对循环寄存器进行修改,如果不等于稍后的指令数量,则分支......因为他们可以。
虽然这是一个数学函数,但使用浮点数很痛苦。并且使用乘法是痛苦的,分裂更糟,幸运的是使用了转变。幸运的是,这是无符号的,因此您可以使用移位(如果您对有符号数使用除法,编译器会/应该知道使用算术移位)。
所以基本上专注于内部循环的微优化,因为它会运行多次,如果可以改变它,那么它会变成移位和添加,如果可能的话,或者安排数据,这样你就可以把它从循环中取出(如果可能,不要在其他地方浪费其他复制循环来执行此操作)
const unsigned char* p = (const unsigned char*)(j*patch->step + aux );
你可以获得一些速度。我没有尝试过,但因为它是循环中的循环,编译器可能不会展开该循环...
长话短说,根据指令集与笨拙的编译器相比,您可能会有所收获,但这段代码并不是很糟糕,因此编译器可以尽可能地优化它。
【讨论】:
【参考方案2】:首先,如果您改为在Code review 发帖,您可能会得到关于此类问题的非常好的、详细的答案。
关于效率和可疑变量类型的一些 cmets:
unsigned f = *p++;
如果您通过数组索引访问p
然后使用 p[i] 访问数据,您可能会更好。这高度依赖于编译器、缓存内存优化等(在这件事上,一些 ARM 专家可以给出比我更好的建议)。
顺便说一句,整个 const char 到 int 看起来非常可疑。我认为那些字符被视为 8 位无符号整数?标准 C uint8_t
可能是一个更好的类型,char
有各种你想避免的未定义签名问题。
另外,你为什么要疯狂混合unsigned
和int
?您要求的是隐式整数平衡错误。
stdev < .1
。只是一件小事:将其更改为 .1f
或强制将浮点数隐式提升为双精度,因为 .1 是双精度文字。
【讨论】:
【参考方案3】:由于您的数据是以 8 个字节为一组读取的,具体取决于您的硬件总线和数组本身的对齐方式,您可以通过单次 long long 读取读取内部循环来获得一些收益,然后要么手动将数字拆分为单独的值,或者使用 ARM 内部函数使用 add8 指令(在 1 个寄存器中一次将 4 个数字相加)与一些内联 asm 并行进行加法,或者进行一些移位并使用 add16 来允许值溢出到 16 位的空间中。还有一个双符号乘法和累加指令,只需一点帮助,您的第一个累加循环就可以通过 ARM 得到几乎完美的支持。此外,如果传入的数据可以被处理为 16 位值,那也可以加快速度。
至于为什么 NEON 速度较慢,我的猜测是设置向量的开销以及您使用较大类型推送的添加数据会扼杀它使用如此小的数据集可能获得的任何性能。原始代码一开始就对 ARM 非常友好,这意味着设置开销可能会害死你。如有疑问,请查看汇编输出。这将告诉你真正发生了什么。也许编译器在尝试使用内在函数时会到处推送和弹出数据——这不是我第一次看到这种行为。
【讨论】:
【参考方案4】:感谢 Lundin、dwelch 和 Michel。 我做了下一个改进,它似乎对我的代码是最好的。 我正在尝试减少改善缓存访问的周期数,因为只访问一次缓存。
int step=patch->step;
for (int j=0; j< 8; j++)
p = (uint8_t*)(j*step + aux ); /
i=8;
do
f=p[i];
sum += f;
sqsum += f*f;
while(--i);
【讨论】:
以上是关于学习效率不高,怎么调整的主要内容,如果未能解决你的问题,请参考以下文章