16. 普通函数调用开销

1.普通函数调用开销

2.内联函数


1.普通函数调用开销

普通函数调用不是"直接执行函数代码", 而是需要先做"准备工作", 执行完毕后还要做"收尾工作", 再加上指令跳转的损耗

这三部分共同构成了调用专属开销("这部分开销和函数体本身的业务逻辑执行无关, 是调用动作带来的额外损耗")
1).压栈开销(调用前的准备工作)

程序执行时, 有一块专门的内存区域叫"调用栈", 每个函数调用都会在调用栈上创建一个独立的栈帧; 调用函数前, CPU必须

执行额外指令, 把两件东西放进这个栈帧里:

a.函数的参数值: 比如调用Add(3,5), 要先把35压入栈帧, 这样函数体内部才能读取到这两个参数

b.返回地址: 就是"当前代码执行到调用函数的下一行指令地址"; 比如: Main函数里第10行调用Add, 返回地址就是第11,

压入栈帧是为了函数执行完后, 知道该回到哪里继续执行

这部分"压入数据"的指令执行, 就是压栈开销(额外的CPU运算和内存操作)
2).弹栈开销(调用后的收尾工作)

函数执行完毕后(比如: Add计算出结果8), 不能直接结束, 还要做收尾, 这就是弹栈开销

a.从栈帧中取出之前压入的返回地址, 让CPU跳转到这个地址(回到调用函数的下一行继续执行)

b.释放当前函数的栈帧(把栈帧里的参数、返回地址等数据清除, 回收栈内存空间), 避免内存泄漏

这两步同样需要执行额外的CPU指令, 构成弹栈开销
3).CPU指令跳转开销

普通函数调用时, CPU的执行流程是中断式的, 不是连续的

原本CPU在按顺序执行Main函数的指令(1行 → 第2行→…→ 第10), 到第10行调用Add函数时, CPU需要暂停Main函数的执行

跳转到Add函数体的起始指令地址去执行; Add函数执行完后, 又要再次跳转(根据返回地址)回Main函数的第11,这种指令地

址的跳转”会带来额外损耗; 比如CPU的"指令流水线"(提前预取指令提高效率)会被清空, 需要重新填充, 这就是跳转开销

2.内联函数

内联函数是在编译优化阶段, 直接把被调用函数的完整函数体代码, "复制粘贴"到调用它的位置, 替换掉原来的函数调用语

句; 简单说: 原来的"调用函数→压栈→跳转→弹栈→返回"的流程, 被直接替换为"原地执行函数体代码", 没有了调用”这个动作

压栈、弹栈、指令跳转这些开销自然就彻底消失
1).普通函数调用(有开销)

在这里插入图片描述

普通调用的执行流程: Main 第1(调用 Add) → 压栈(35、Main 第 2 行地址) → 跳转至Add函数体 → 执行a + b →弹栈

(取返回地址) → 跳转回Main第2行 → 执行 Console.WriteLine

2).内联优化后(无调用开销)

在这里插入图片描述

内联后的执行流程: Main第1(直接执行 3+5)→ Main 第2(执行 Console.WriteLine), 全程没有压栈、弹栈、指令跳转

彻底消除了调用开销
源码地址: https://pan.quark.cn/s/3916362e5d0a 在C#编程平台下,构建一个曲线编辑器是一项融合了图形用户界面(GUI)构建、数据管理及数学运算的应用开发任务。 接下来将系统性地介绍这个曲线编辑器开发过程中的核心知识点:1. **定制曲线面板展示数据曲线**: - 控件选用:在C#的Windows Forms或WPF框架中,有多种控件可用于曲线呈现,例如PictureBox或用户自定义的UserControl。 通过处理重绘事件,借助Graphics对象执行绘图动作,如运用DrawCurve方法。 - 数据图形化:通过线性或贝塞尔曲线连接数据点,以呈现数据演变态势。 这要求掌握直线与曲线的数学描述,例如两点间的直线公式、三次贝塞尔曲线等。 - 坐标系统与缩放比例:构建X轴和Y轴,设定坐标标记,并开发缩放功能,使用户可察看不同区间内的数据。 2. **在时间轴上配置多个关键帧数据**: - 时间轴构建:开发一个时间轴组件,显示时间单位刻度,并允许用户在特定时间点设置关键帧。 时间可表现为连续形式或离散形式,关键帧对应于时间轴上的标识。 - 关键帧维护:利用数据结构(例如List或Dictionary)保存关键帧,涵盖时间戳和关联值。 需考虑关键帧的添加、移除及调整位置功能。 3. **调整关键帧数据,通过插值方法获得曲线**: - 插值方法:依据关键帧信息,选用插值方法(如线性插值、样条插值,特别是Catmull-Rom样条)生成平滑曲线。 这涉及数学运算,确保曲线在关键帧之间无缝衔接。 - 即时反馈:在编辑关键帧时,即时刷新曲线显示,优化用户体验。 4. **曲线数据的输出**: - 文件类型:挑选适宜的文件格式存储数据,例如XML、JSON或...
### JavaScript 中 new 操作符与普通函数调用的性能对比 在 JavaScript 中,`new` 操作符与普通函数调用在性能上存在一定的差异,主要体现在对象创建、作用域绑定、原型链设置等方面。以下从多个维度分析两者的性能开销差异,并结合代码示例说明。 #### 1. `new` 操作符的性能开销 使用 `new` 创建对象时,JavaScript 引擎会执行一系列步骤,包括: - 创建一个空对象; - 将该对象的 `__proto__` 指向构造函数的 `prototype` 属性; - 将构造函数的 `this` 绑定到新创建的对象; - 如果构造函数返回一个对象,则返回该对象;否则返回新创建的对象。 这一过程比普通函数调用复杂得多,因此性能开销更大。例如: ```javascript function Person(name, age) { this.name = name; this.age = age; } const person1 = new Person('Tom', 20); // 使用 new 创建实例 [^2] ``` 在这个例子中,`new Person()` 会创建一个新的对象,并将其原型设置为 `Person.prototype`,同时绑定 `this` 到该对象。这些额外的步骤增加了执行时间。 #### 2. 普通函数调用的性能开销 普通函数调用不会创建新对象,也不会设置原型链,因此其性能开销远小于 `new` 操作符。例如: ```javascript function greet(name) { return `Hello, ${name}`; } const message = greet('Tom'); // 普通函数调用 ``` 该调用仅涉及函数执行和返回值,没有对象创建和原型设置的开销,因此执行速度更快。 #### 3. 性能对比测试 可以通过简单的性能测试比较 `new` 和普通函数调用的执行时间。例如: ```javascript function testNew() { function Foo() { this.value = 42; } for (let i = 0; i < 100000; i++) { new Foo(); } } function testFunction() { function bar() { return 42; } for (let i = 0; i < 100000; i++) { bar(); } } console.time('new'); testNew(); console.timeEnd('new'); console.time('function'); testFunction(); console.timeEnd('function'); ``` 运行结果通常显示 `new` 的执行时间显著高于普通函数调用,特别是在循环中频繁调用时。 #### 4. 性能优化建议 在对性能敏感的场景下,应尽量避免在高频函数或循环中使用 `new`。可以考虑以下优化方式: - **对象复用**:通过对象池等方式复用已创建的对象,减少重复创建的开销。 - **延迟初始化**:仅在真正需要对象时才进行创建。 - **避免不必要的对象创建**:如果函数仅用于计算并返回值,可使用普通函数而非构造函数。 例如,对象池实现如下: ```javascript function createObjectPool(factory, size) { const pool = []; for (let i = 0; i < size; i++) { pool.push(factory()); } return { get() { return pool.pop(); }, release(obj) { pool.push(obj); } }; } const pool = createObjectPool(() => new Person('Tom', 20), 100); const person = pool.get(); // 使用 person pool.release(person); ``` 此方式通过复用对象减少 `new` 的调用次数,从而提升性能。 ### 结论 JavaScript 中 `new` 操作符的性能开销高于普通函数调用,主要体现在对象创建、原型链设置和作用域绑定等方面。在性能敏感的场景下,应尽量减少 `new` 的使用频率,或通过对象复用等手段进行优化[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值