c - C 函数优化
问题描述
我有一个看起来像这样的函数,常量a1-e8(双精度浮点数)在代码中,比如说硬编码或#defined。该函数接受 -1.0 到 1.0 范围内的双精度数,并且需要分成四等份,如图所示。
在汇编语言优化之前,我可以进行任何其他代码优化来提高运行时性能吗?我尝试制作一个 x2 来保存 x*x 并使 e 常数乘以 x2*x2 但它实际上降低了性能。我还尝试查看是否可以将 x 的副本转换为整数并使用 switch 语句,但这也会降低性能。
double operation(double x) {
if (x <= -0.75 && x >= -1.0) {
return a1 + b1*x + c1*x*x + d1*x*x*x + e1*x*x*x*x;
}
else if (x <= -0.5) {
return a2 + b2*x - c2*x*x - d2*x*x*x - e2*x*x*x*x;
}
else if (x <= -0.25) {
return a3 - b3*x - c3*x*x - d3*x*x*x - e3*x*x*x*x;
}
else if (x <= 0.0) {
return a4 - b4*x - c4*x*x - d4*x*x*x + e4*x*x*x*x;
}
else if (x <= 0.25) {
return a5 + b5*x - c5*x*x + d5*x*x*x + e5*x*x*x*x;
}
else if (x <= 0.5) {
return a6 + b6*x - c6*x*x + d6*x*x*x - e6*x*x*x*x;
}
else if (x <= 0.75) {
return a7 - b7*x - c7*x*x + d7*x*x*x - e7*x*x*x*x;
}
else if (x <= 1.0) {
return a8 - b8*x + c8*x*x - d8*x*x*x + f8*x*x*x*x;
}
return 0.0;
}
解决方案
在汇编语言优化之前,我可以进行任何其他代码优化来提高运行时性能吗?
重新排列比较,以便您基本上是在对正确的情况进行二进制搜索而不是线性搜索,从而大大加快了速度:
double op2(double x) {
if (x <= 0) {
if (x <= -0.5) {
if (x <= -0.75 && x >= -1.0) {
return a1 + b1*x + c1*x*x + d1*x*x*x + e1*x*x*x*x;
}
return a2 + b2*x - c2*x*x - d2*x*x*x - e2*x*x*x*x;
}
else {
if (x <= -0.25) {
return a3 - b3*x - c3*x*x - d3*x*x*x - e3*x*x*x*x;
}
return a4 - b4*x - c4*x*x - d4*x*x*x + e4*x*x*x*x;
}
}
else {
if (x <= 0.5) {
if (x <= 0.25) {
return a5 + b5*x - c5*x*x + d5*x*x*x + e5*x*x*x*x;
}
return a6 + b6*x - c6*x*x + d6*x*x*x - e6*x*x*x*x;
}
else {
if (x <= 0.75) {
return a7 - b7*x - c7*x*x + d7*x*x*x - e7*x*x*x*x;
}
else if (x <= 1.0) {
return a8 - b8*x + c8*x*x - d8*x*x*x + e8*x*x*x*x;
}
}
}
return 0.0;
}
我通过在同一个循环中调用原始版本op1
(op2
两个函数返回相同的值。分析超过一亿次循环迭代的代码,我得到以下结果:
因此,该op2
版本的速度比原始版本快不到两倍。
更新:
我还测试了一个将输入映射到整数然后打开它的版本。这只有效,因为间隔都是相同的大小,所以虽然方法op2
可以适用于任意间隔,但这个方法不会。为了进行映射,我将输入加 1,将输入范围移动到 [0, 2.0],然后乘以 4,将范围扩大到 [0, 8.0]。然后我将其转换为int
以便我们可以打开它。switch
具有多个连续值的语句的好处是编译器可以将其实现为跳转表,这使得它非常快。代价是额外的浮点乘法。这是功能:
double op3(double x) {
int c = (int)((x + 1) * 4); // mapping from double to int
switch (c) {
case 0: {
return a1 + b1*x + c1*x*x + d1*x*x*x + e1*x*x*x*x;
}
case 1: {
return a2 + b2*x - c2*x*x - d2*x*x*x - e2*x*x*x*x;
}
case 2: {
return a3 - b3*x - c3*x*x - d3*x*x*x - e3*x*x*x*x;
}
case 3: {
return a4 - b4*x - c4*x*x - d4*x*x*x + e4*x*x*x*x;
}
case 4: {
return a5 + b5*x - c5*x*x + d5*x*x*x + e5*x*x*x*x;
}
case 5: {
return a6 + b6*x - c6*x*x + d6*x*x*x - e6*x*x*x*x;
}
case 6: {
return a7 - b7*x - c7*x*x + d7*x*x*x - e7*x*x*x*x;
}
case 7: {
return a8 - b8*x + c8*x*x - d8*x*x*x + e8*x*x*x*x;
}
default: {
return 0.0;
}
}
}
结果:
所以,op3
比原来的要快很多op1
,但op2
在这种情况下仍然是赢家。但是,如果您有更多案例,我认为您最终会达到将输入映射到整数的成本小于比较的成本的地步op2
。
查看这三个函数,您可以看到该op1
方法的复杂度为 O(n),其中 n 是区间数。该op2
方法是 O(log n),因为 n 个间隔需要 log n 个级别的比较。方法是op3
O(1):一旦将输入映射到一个区间,switch 语句就可以使用跳转表在恒定时间内找到正确的情况。
推荐阅读
- ios - SwiftUI TabView 不更新视图
- .net - Visual Studio 跳过我机器上的单元测试
- javascript - PhpStorm / WebIdea / WebStorm 没有在 Javascript 中提示正确的代码完成
- html - Bootstrap 4.5 网格
- video - 如何在不影响移动设备的情况下为桌面设置视频大小?
- preloader - 加载完网站的所有内容(包括图像、视频等)后,如何淡出我的预加载器?
- amazon-s3 - plesk 上的 Amazon s3 出现问题
- python - 应用程序成功推送到heroku,但如何正确引用文件名,使其像在本地主机中一样在线工作
- c# - 仅当第一个对象实现第二个对象所需的接口时,如何在 C# 中在运行时组合两个对象?
- reactjs - 使用 docker compose 时如何正确构建我的客户端?