内存优化最后一弹——优化函数运行

简介: 快起来,这真的是最后一篇啦!计算非零位的个数 / counting the number of bits set例1:测试单个的最低位,计数,然后移位。

快起来,这真的是最后一篇啦!

计算非零位的个数 / counting the number of bits set

例1:测试单个的最低位,计数,然后移位。

//example1
int countbit1(uint n)
{
    int bits = 0;
    while (n != 0) {
        if(n & 1) bits++;
            n >>= 1;
    }
      return bits;
}

例2:先除4,然后计算被4处的每个部分。循环拆解经常会给程序优化带来新的机会。

//example - 2
int countbit2(uint n)
{
    int bits = 0;
    while (n != 0) {
        if (n & 1) bits++;
        if (n & 2) bits++;
        if (n & 4) bits++;
        if (n & 8) bits++;
            n >>= 4;
    }
    return bits;
}

尽早地退出循环 / Early loop breaking

通常没有必要遍历整个循环。举例来说,在数组中搜索一个特定的值,我们可以在找到我们需要值之后立刻退出循环。下面的例子在10000个数字中搜索-99。

found = FALSE; 
for(i=0;i<10000;i++) 
{ 
    if(list[i] == -99) { 
         found = TRUE; 
    } 
} 
if(found) printf("Yes, there is a -99. Hooray!\n");

这样做是可行的,但是不管这个被搜索到的项目出现在什么位置,都会搜索整个数组。跟好的方法是,再找到我们需要的数字以后,立刻退出循环。

found = FALSE; 
for(i = 0; i < 10000; i++) 
{ 
    if( list[i] == -99 ) { 
        found = TRUE; 
        break; 
    } 
} 
if( found ) printf("Yes, there is a -99. Hooray!\n");

如果数字出现在位置23上,循环就会终止,忽略剩下的9977个。

函数设计 / Function Design

保持函数短小精悍,是对的。这可以使编译器能够跟高效地进行其他的优化,比如寄存器分配。

调用函数的开销 / Function call overhead

对处理器而言,调用函数的开销是很小的,通常,在被调用函数所进行的工作中,所占的比例也很小。能够使用寄存器传递的函数参数个数是有限制的。这些参数可以是整型兼容的(char,short,int以及float都占用一个字),或者是4个字以内的结构体(包括2个字的double和long long)。

假如参数的限制是4,那么第5个及后面的字都会被保存到堆栈中。这会增加在调用函数是存储这些参数的,以及在被调用函数中恢复这些参数的代价。

int f1(int a, int b, int c, int d) { 
    return a + b + c + d;
}
int g1(void) {
    return f1(1, 2, 3, 4);
}
int f2(int a, int b, int c, int d, int e, int f) {
    return a + b + c + d + e + f;
}
ing g2(void) {
    return f2(1, 2, 3, 4, 5, 6);
}

g2函数中,第5、6个参数被保存在堆栈中,在f2中被恢复,每个参数带来2次内存访问。

最小化参数传递的开销 / Minimizing parameter passing overhead

为了将传递参数给函数的代价降至最低,我们可以: 尽可能确保函数的形参不多于四个,甚至更少,这样就不会使用堆栈来传递参数。

如果一个函数形参多于四个,那就确保在这个函数能够做大量的工作,这样就可以抵消由传递堆栈参数所付出的代价。

用指向结构体的指针作形参,而不是结构体本身。 把相关的参数放到一个结构里里面,然后把它的指针传给函数,可以减少参数的个数,增加程序的可读性。 将long类型的参数的个数降到最小,因为它使用两个参数的空间。对于double也同样适用。

避免出现参数的一部分使用寄存器传输,另一部分使用堆栈传输的情况。这种情况下参数将被全部压到堆栈里。 避免出现函数的参数个数不定的情况。这种情况下,所有参数都使用堆栈。

叶子函数 / Leaf functions

如果一个函数不再调用其他函数,这样的函数被称为叶子函数。在许多应用程序中,大约一半的函数调用是对叶子函数的调用。叶子函数在所有平台上都可以得到非常高效的编译,因为他们不需要进行参数的保存和恢复。在入口压栈和在出口退栈的代价,跟一个足够复杂的需要4个或者5个参数的叶子函数所完成的工作相比,是非常小的。

如果可能的话,我们就要尽量安排经常被调用的函数成为叶子函数。函数被调用的次数可以通过模型工具(profiling facility)来确定。这里有几种方法可以确保函数被编译成叶子函数:

不调用其他函数:包括那些被转换成调用C语言库函数的运算,比如除法、浮点运算。

使用关键字__inline修饰小的函数。

内联函数 / Inline functions

对于所有调试选项,内嵌函数是被禁止的。使用inline关键字修饰函数后,跟普通的函数调用不同,代码中对该函数的调用将会被函数体本身代替。这会使代码更快,另一方面它会影响代码的长度,尤其是内嵌函数比较大而且经常被调用的情况下。

__inline int square(int x) {
    return x * x;
}
#include <MATH.H>
double length(int x, int y){
    return sqrt(square(x) + square(y));
}

使用内嵌函数有几个优点:

没有调用函数的开销。

因为函数被直接代替,没有任何额外的开销,比如存储和恢复寄存器。

更低的参数赋值开销。

参数传递的开销通常会更低,因为它不需要复制变量。如果其中一些参数是常量,编译器还可以作进一步的优化。

内嵌函数的缺点是如果函数在许多地方被调用,将会增加代码的长度。长度差别的大小非常依赖于内嵌函数的大小和调用的次数。

仅将少数关键函数设置成内嵌函数是明智的。如果设置得当,内嵌函数可以减少代码的长度,一次函数调用需要一定数量的指令,但是,使用优化过的内嵌函数可以编译成更少的指令。

使用查找表 / Using Lookup Tables

有些函数可以近似成查找表,这样可以显著的提高效率。查找表的精度一般比计算公式的精度低,不过在大多数程序中,这种精度就足够了。

许多信号处理软件(比如MODEM调制软件)会大量的使用sin和cos函数,这些函数会带来大量的数学运算。对于实时系统来说,精度不是很重要,sin/cos查找表显得更加实用。使用查找表的时候,尽量将相近的运算合并成一个查找表,这样要比使用多个查找表要更快和使用更少的空间。

浮点运算 / Floating-Point Arithmetic

尽管浮点运算对于任何处理器来讲都是很费时间的,有的时候,我们还是不得不用到浮点运算,比方说实现信号处理。尽管如此,编写浮点运算代码的时候,我们要牢记:

浮点除法是慢的

除法要比加法或者乘法慢两倍,我们可以把被一个常数除的运算写成被这个数的倒数乘(比如,x=x/3.0写成x=x*(1.0/3.0))。倒数的计算在编译阶段就被完成。

使用float代替double

Float型变量消耗更少的内存和寄存器,而且因为它的低精度所以具有更高的效率。在精度足够的情况下,就要使用float。

不要使用先验函数/transcendental functions

先验函数(比如sin,cos,log)是通过使用一系列的乘法和加法实现的,所以这些运算会比普通的乘法慢10倍以上。

简化浮点表达式

编译器在整型跟浮点型混合的运算中不会进行太多的优化。比如3 * (x / 3) 不会被优化成x,因为浮点运算通常会导致精度的降低,甚至表达式的顺序都是重要的: (a + b) + c 不等于 a + (b + c)。因此,进行手动的优化是有好处的。

不过,在特定的场合下,浮点运算的效率达不到指定的水平,这种情况下,最好的办法可能是放弃浮点运算,转而使用定点运算。当变量的变化范围足够的小,定点运算要比浮点运算精度更高、速度更快。

其他的技巧 / Misc tips

一般情况下,可以用存储空间换取时间。你可以缓存那些经常用到的数据,而不是每次都重新计算、或者重新装载。比如sin/cos表,或者伪随机数的表(如果你不是真的需要随机数,你可以在开始的时候计算1000个,在随后的代码中重复利用就是了) 尽量少的使用全局变量。

将一个文件内部的变量声明成静态的,除非它有必要成为全局的。

不要使用递归。递归可以使代码非常整齐和美观,但会产生大量的函数调用和开销。

访问单维数组要比多维数组快 使用#defined宏代替经常用到的小函数。

不要用递归,不要用递归,不要用递归!重要的话说三遍,小伙伴们,还请持续关注更新,更多干货和资料请直接联系我,也可以加群710520381,邀请码:柳猫,欢迎大家共同讨论

目录
相关文章
|
5月前
|
机器学习/深度学习 算法 PyTorch
125_训练加速:FlashAttention集成 - 推导注意力优化的独特内存节省
2025年,大型语言模型的训练面临着前所未有的挑战。随着模型参数量和序列长度的不断增加,传统注意力机制的内存瓶颈问题日益突出。FlashAttention作为一种突破性的注意力算法,通过创新的内存访问模式和计算优化,显著提升了训练效率和内存利用。
650 3
|
5月前
|
存储 机器学习/深度学习 PyTorch
119_LLM训练的高效内存管理与优化技术:从ZeRO到Flash Attention
大型语言模型(LLM)的训练面临着前所未有的计算和内存挑战。随着模型规模达到数百亿甚至数千亿参数,高效的内存管理成为训练成功的关键因素之一。2025年,LLM训练的内存优化技术已经取得了显著进展,从ZeRO优化器到Flash Attention等创新技术,为训练超大规模模型提供了可能。
683 159
|
8月前
|
缓存 固态存储 Windows
如何让内存发挥到最大效能?全面优化指南,提升电脑运行体验
电脑内存使用不合理会导致卡顿,本文教你如何优化内存性能。检查内存容量与主板支持上限,考虑升级或调整配置;关闭后台程序、管理浏览器标签、结束异常进程以释放内存;设置虚拟内存、调整视觉效果、定期重启提升效率;必要时增加内存条、选择高频内存、更换固态硬盘。避免盲目清理内存和依赖大内存忽视其他硬件瓶颈。只需合理设置,无需额外花钱,就能显著提升电脑速度。
|
8月前
|
存储 人工智能 自然语言处理
AI代理内存消耗过大?9种优化策略对比分析
在AI代理系统中,多代理协作虽能提升整体准确性,但真正决定性能的关键因素之一是**内存管理**。随着对话深度和长度的增加,内存消耗呈指数级增长,主要源于历史上下文、工具调用记录、数据库查询结果等组件的持续积累。本文深入探讨了从基础到高级的九种内存优化技术,涵盖顺序存储、滑动窗口、摘要型内存、基于检索的系统、内存增强变换器、分层优化、图形化记忆网络、压缩整合策略以及类操作系统内存管理。通过统一框架下的代码实现与性能评估,分析了每种技术的适用场景与局限性,为构建高效、可扩展的AI代理系统提供了系统性的优化路径和技术参考。
545 4
AI代理内存消耗过大?9种优化策略对比分析
|
8月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
401 15
|
8月前
|
存储 人工智能 API
AI代理性能提升实战:LangChain+LangGraph内存管理与上下文优化完整指南
在AI代理系统开发中,上下文工程成为提升系统性能的关键技术。本文探讨了从提示工程到上下文工程的转变,强调其通过为AI系统提供背景信息和工具支持,显著提升智能化程度和实用价值。文章系统分析了上下文工程的理论基础、核心策略(如写入、选择、压缩和隔离),并结合LangChain和LangGraph工具,展示了如何实现上下文工程技术以优化AI代理性能。通过Scratchpad机制、内存管理、RAG系统集成、多代理架构及沙盒环境等技术手段,开发者可以更高效地构建高性能、可扩展的AI系统。
1119 0
AI代理性能提升实战:LangChain+LangGraph内存管理与上下文优化完整指南
|
9月前
|
缓存 监控 Cloud Native
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
本文深入解析了Java Solon v3.2.0框架的实战应用,聚焦高并发与低内存消耗场景。通过响应式编程、云原生支持、内存优化等特性,结合API网关、数据库操作及分布式缓存实例,展示其在秒杀系统中的性能优势。文章还提供了Docker部署、监控方案及实际效果数据,助力开发者构建高效稳定的应用系统。代码示例详尽,适合希望提升系统性能的Java开发者参考。
495 4
Java Solon v3.2.0 高并发与低内存实战指南之解决方案优化
|
9月前
|
存储 自然语言处理 算法
基于内存高效算法的 LLM Token 优化:一个有效降低 API 成本的技术方案
本文探讨了在构建对话系统时如何通过一种内存高效算法降低大语言模型(LLM)的Token消耗和运营成本。传统方法中,随着对话深度增加,Token消耗呈指数级增长,导致成本上升。
877 7
基于内存高效算法的 LLM Token 优化:一个有效降低 API 成本的技术方案
|
7月前
|
边缘计算 算法 Java
Java 绿色计算与性能优化:从内存管理到能耗降低的全方位优化策略与实践技巧
本文探讨了Java绿色计算与性能优化的技术方案和应用实例。文章从JVM调优(包括垃圾回收器选择、内存管理和并发优化)、代码优化(数据结构选择、对象创建和I/O操作优化)等方面提出优化策略,并结合电商平台、社交平台和智能工厂的实际案例,展示了通过Java新特性提升性能、降低能耗的显著效果。最终指出,综合运用这些优化方法不仅能提高系统性能,还能实现绿色计算目标,为企业节省成本并符合环保要求。
267 0
|
7月前
|
编解码 Ubuntu Linux
ubuntu系统安装指南:免费且适合老旧电脑,4GB内存也能流畅运行!
点击启动台,找到并点击设置。在设置中,选择语言和区域,再点击管理语言。安装所需的语言包,输入密码进行确认。等待大约2分钟,语言包安装完成后,点击安装语言,选择中文选项。这里有简体和繁体两种选择,根据个人需求进行选择。再次等待2分钟,安装完成后,点击这里,选择中文并应用。然后,将出现的中文拖动到最上面,应用更改并退出设置。最后,重启虚拟机,再次进入系统时,你会发现界面已经变成了中文,而且系统依然保持流畅。Ubuntu系统不仅外观漂亮、干净,而且性能稳定、安全可靠。如果你的电脑内存只有4GB,或者你对Windows系统感到厌倦,那么Ubuntu绝对是一个值得尝试的选择。它不仅办公打印一应俱全,还拥