高性能数值计算编程小技巧(带实例,持续更新.......)

2023-10-13 00:30

本文主要是介绍高性能数值计算编程小技巧(带实例,持续更新.......),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1、乘法比除法运算速度更快,一般情况下,尽量用乘法代替除法
  • 2、两相近的数相减,会导致计算精度丢失,应想办法将减法运算转化为其他运算
  • 3、多项式求值时,采用霍纳方法(或秦九韶方法)的递推公式,减小计算量,提高计算精度及可靠性
  • 4、将不变条件的计算移到循环体外
  • 5、对于多维大数组,避免来回跳跃式访问数组成员
  • 6、创建资源库,以减少分配对象的开销
  • 7、将多次被调用的 “小函数”改为inline函数或者宏实现
  • 8、内存不紧张情况下,可考虑空间换时间的策略,提高计算效率
  • 参考文献

1、乘法比除法运算速度更快,一般情况下,尽量用乘法代替除法

   a = 0.5 ∗ b a=0.5*b a=0.5b a = b / 2.0 a=b/2.0 a=b/2.0分别计算1000000次。在笔者的程序运行环境下,前者运行时间1.62ms,后者运行时间3.85ms(不同平台结果不一样)。乘法计算效率大概是除法的2.3倍!

#include <time.h>
#include <stdio.h>
#include <math.h>
#define TEST_COUNT 100
#define CALCULATE_COUNT 1000000double calculate_mean_runtime(double timeArray[]);
double calculate_std_runtime(double timeArray[], double meanRuntime);double calculate_mean_runtime(double timeArray[])
{long n;double sum = 0.0;for (n = 0; n < TEST_COUNT; n++){sum += timeArray[n];}return sum / TEST_COUNT;
}double calculate_std_runtime(double timeArray[], double meanRuntime)
{long n;double sum = 0.0;for (n = 0; n < TEST_COUNT; n++){sum += (timeArray[n] - meanRuntime) * (timeArray[n] - meanRuntime);}return sqrt(sum / (TEST_COUNT - 1));
}void main(void)
{long i, n, startTime, finishTime;double meanRuntime, stdRuntime, timeArray[TEST_COUNT];double a, b = 1.0;for (n = 0; n < TEST_COUNT; n++){startTime = clock();for (i = 0; i < CALCULATE_COUNT; i++){//a = 0.5 * b;a = b / 2.0;}finishTime = clock();timeArray[n] = (double)(finishTime - startTime);}meanRuntime = calculate_mean_runtime(timeArray);stdRuntime = calculate_std_runtime(timeArray, meanRuntime);printf("The mean run time is:%f ms!\n", meanRuntime);printf("The std run time is:%f ms!\n", stdRuntime);
}

2、两相近的数相减,会导致计算精度丢失,应想办法将减法运算转化为其他运算

  在一定条件下,利用恒等变换,可以将减法运算转化为其他运算,从而提高计算精度。以下函数的取值均在定义域内。
  1).当 x → 0 x \rightarrow0 x0时,作变换:
1 − c o s x = 2 s i n 2 ( x 2 ) (1) 1-cosx=2sin^2(\frac{x}{2})\tag{1} 1cosx=2sin2(2x)(1)
  2).当 x 1 x_1 x1 x 2 x_2 x2很接近时,作变换:
l o g a x 1 − l o g a x 2 = l o g a x 1 x 2 (2) log_ax_1-log_ax_2=log_a\frac{x_1}{x_2} \tag{2} logax1logax2=logax2x1(2)
  3).当 x x x较大时,作变换:
x + 1 − x = 1 x + 1 + x (3.1) \sqrt{x+1}-\sqrt{x}=\frac{1}{\sqrt{x+1}+\sqrt{x}} \tag{3.1} x+1 x =x+1 +x 1(3.1)
l o g a ( x − x 2 − 1 ) = − l o g a ( x + x 2 − 1 ) (3.2) log_a(x-\sqrt{x^2-1})=-log_a(x+\sqrt{x^2-1}) \tag{3.2} loga(xx21 )=loga(x+x21 )(3.2)
a r c t a n ( x + 1 ) − a r c t a n ( x ) = a r c t a n ( 1 x 2 + x + 1 ) (3.3) arctan(x+1) - arctan(x)= arctan(\frac{1}{x^2+x+1}) \tag{3.3} arctan(x+1)arctan(x)=arctan(x2+x+11)(3.3)
  令 a r c t a n ( x + 1 ) = A , a r c t a n ( x ) = B arctan(x+1)=A,arctan(x)=B arctan(x+1)=A,arctan(x)=B t a n ( A − B ) = t a n ( A ) − t a n ( B ) 1 + t a n ( A ) t a n ( B ) = x + 1 − x 1 + ( x + 1 ) x = 1 x 2 + x + 1 tan(A-B)=\frac{tan(A)-tan(B)}{1+tan(A)tan(B)}=\frac{x+1-x}{1+(x+1)x}=\frac{1}{x^2+x+1} tan(AB)=1+tan(A)tan(B)tan(A)tan(B)=1+(x+1)xx+1x=x2+x+11,则 A − B = a r c t a n ( 1 x 2 + x + 1 ) A-B=arctan(\frac{1}{x^2+x+1}) AB=arctan(x2+x+11),上式得证。
  4).当 b 2 > > 4 a c b^2>>4ac b2>>4ac时,对于一元二次方程求根公式 x 1 , 2 = − b ± b 2 − 4 a c 2 a x_{1,2}=\frac{-b\pm\sqrt{b^2-4ac}}{2a} x1,2=2ab±b24ac ,作变换(详见博文:一元二次方程高精度实数根(C语言)):
x 1 = − 2 c b + b 2 − 4 a c ( b > 0 ) (4.1) x_1=\frac{-2c}{b+\sqrt{b^2-4ac}} \ \ (b>0)\tag{4.1} x1=b+b24ac 2c  (b>0)(4.1)
x 2 = − 2 c b − b 2 − 4 a c ( b < 0 ) (4.2) x_2=\frac{-2c}{b-\sqrt{b^2-4ac}} \ \ (b<0)\tag{4.2} x2=bb24ac 2c  (b<0)(4.2)
  5).当 f ( x ) ≈ f ( x ∗ ) f(x)\approx f(x^*) f(x)f(x)时,利用泰勒展开,作变换:
f ( x ) − f ( x ∗ ) = f ′ ( x ∗ ) ( x − x ∗ ) + f ′ ′ ( x ∗ ) 2 ( x − x ∗ ) 2 + ⋯ (5) f(x)-f(x^*)=f'(x^*)(x-x^*)+\frac{f''(x^*)}{2}(x-x^*)^2 + \cdots \tag{5} f(x)f(x)=f(x)(xx)+2f(x)(xx)2+(5)

#include <stdio.h>
#include <math.h>void main(void)
{float x, x1, x2;double xx, xx1, xx2;//(1)x = 1.234567e-4f;xx = (double)x;printf("1-cos(x) = %.15f\n", 1.0 - cosf(x));printf("2*sin^2(x/2) = %.15f\n", 2.0 * sinf(x / 2.0) * sinf(x / 2.0));printf("精度较高的计算参考值:%.15f\n\n", 2.0 * sin(xx / 2.0) * sin(xx / 2.0));//(2)x1 = 123456.0f;x2 = 123456.9f;xx1 = (double)x1;xx2 = (double)x2;printf("log(x1) - log(x2) = %.15f\n", logf(x1) - logf(x2));printf("log(x1/x2) = %.15f\n", logf(x1 / x2));printf("精度较高的计算参考值:%.15f\n\n", log(xx1 / xx2));//(3.1)x = 1234567.0f;xx = (double)x;printf("sqrt(x+1)-sqrt(x) = %.15f\n", sqrtf(x + 1) - sqrtf(x));printf("1/(sqrt(x+1)+sqrt(x)) = %.15f\n", 1.0 / (sqrtf(x + 1) + sqrtf(x)));printf("精度较高的计算参考值:%.15f\n\n", 1.0 / (sqrt(xx + 1) + sqrt(xx)));//(3.2)x = 1234.0f;xx = (double)x;printf("log(x-sqrt(x^2-1)) = %.15f\n", logf(x - sqrtf(x * x - 1.0)));printf("-log(x+sqrt(x^2-1)) = %.15f\n", -logf(x + sqrtf(x * x - 1.0)));printf("精度较高的计算参考值:%.15f\n\n", -log(xx + sqrt(xx * xx - 1.0)));//(3.3)printf("atan(x+1)-atan(x) = %.15f\n", atanf(x + 1) - atanf(x));printf("atan(1/(x^2+x+1)) = %.15f\n", atanf(1.0 / (x * x + x + 1.0)));printf("精度较高的计算参考值:%.15f\n", atan(1.0 / (xx * xx + xx + 1.0)));
}

在这里插入图片描述

3、多项式求值时,采用霍纳方法(或秦九韶方法)的递推公式,减小计算量,提高计算精度及可靠性

  具体参见博文:多项式求值(Evaluation of a Polynomial)

4、将不变条件的计算移到循环体外

  将循环中与循环无关,不是每次循环都要做的操作,移到循环外部执行。
  示例一:

for (int i = 0; i < 10; i++ )
{sum += i;back_sum = sum;
}

  对于此for循环来说语句“back_Sum = sum;” 没必要每次都执行,只需要执行一次即可,因此可以改为:

for (int i = 0; i < 10; i++ )
{sum += i;
}
back_sum = sum;

  示例二:

for (_UL i = 0; i < func_calc_max(); i++)
{//process;
}

  函数func_calc_max()没必要每次都执行,只需要执行一次即可,因此可以改为:

_UL max = func_calc_max();
for (_UL i = 0; i < max; i++)
{//process;
}

5、对于多维大数组,避免来回跳跃式访问数组成员

  多维数组在内存中是从最后一维开始逐维展开连续存储的。下面这个对二维数组访问是以SIZE_B为步长跳跃访问,到尾部后再从头(第二个成员)开始,依此类推。局部性比较差,当步长较大时,可能造成cache不命中,反复从内存加载数据到cache。应该把i和j交换。

for (int i = 0; i < SIZE_B; i++)
{for (int j = 0; j < SIZE_A; j++){sum += x[j][i];}
}

  上面这段代码,在 SIZE_B 数值较大时,效率可能会比下面的代码低:

for (int i = 0; i < SIZE_B; i++)
{for (int j = 0; j < SIZE_A; j++){sum += x[i][j];}
}

6、创建资源库,以减少分配对象的开销

  例如,使用线程池机制,避免线程频繁创建、销毁的系统调用;使用内存池,对于频繁申请、释放的小块内存,一次性申请一个大块的内存,当系统申请内存时,从内存池获取小块内存,使用完毕再释放到内存池中,避免内存申请释放的频繁系统调用。

7、将多次被调用的 “小函数”改为inline函数或者宏实现

  如果编译器支持inline,可以采用inline函数。否则可以采用宏。在做这种优化的时候一定要注意下面inline函数的优点:其一编译时不用展开,代码SIZE小。其二可以加断点,易于定位问题,例如对于引用计数加减的时候。其三函数编译时,编译器会做语法检查。

8、内存不紧张情况下,可考虑空间换时间的策略,提高计算效率

  以下程序效率低下:

for (i = 0; i < 1000; i++)
{x[i] = a[i] * sin(theta[i]) + b[i] * cos(theta[i]);y[i] = b[i] * sin(theta[i]) + a[i] * sin(theta[i]);
}

  可以改为:

for (i = 0; i < 1000; i++)
{sinTheta = sin(theta[i]);cosTheta = cos(theta[i]);x[i] = a[i] * sinTheta + b[i] * cosTheta;y[i] = b[i] * sinTheta + a[i] * cosTheta;
}

参考文献

华为C语言编程规范 2011年5月9日发布
《数值分析》李庆扬,王能超,易大义编

这篇关于高性能数值计算编程小技巧(带实例,持续更新.......)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/199498

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

hdu 1754 I Hate It(线段树,单点更新,区间最值)

题意是求一个线段中的最大数。 线段树的模板题,试用了一下交大的模板。效率有点略低。 代码: #include <stdio.h>#include <string.h>#define TREE_SIZE (1 << (20))//const int TREE_SIZE = 200000 + 10;int max(int a, int b){return a > b ? a :