数学函数的性能表现?

4

我在处理加速度计数据的图形,正在尝试纠正重力影响。为此,我获取球坐标系下的加速向量,将半径减去1g,然后转换回直角坐标系。这种方法每0.03秒定时调用一次:

//poll accleration
ThreeAxisAcceleration current = self.accelerationData;

//math to correct for gravity:
float radius = sqrt(pow(current.x, 2) + pow(current.y, 2) + pow(current.z, 2));
float theta = atan2(current.y, current.x);
float phi = acos(current.z/radius);

//NSLog(@"SPHERICAL--- RADIUS: %.2f -- THETA: %.2f -- PHI: %.2f", radius, theta, phi);

radius = radius - 1.0;

float newX = radius * cos(theta) * sin(phi);
float newY = radius * sin(theta) * sin(phi);
float newZ = radius * cos(phi);

current = (ThreeAxisAcceleration){newX, newY, newZ};

//end math
NSValue *arrayVal = [NSValue value:&current withObjCType:@encode(ThreeAxisAcceleration)];

if ([_dataHistoryBuffer count] > self.bounds.size.width) {
    [_dataHistoryBuffer removeObjectAtIndex:0];
}

[_dataHistoryBuffer addObject:arrayVal];

[self setNeedsDisplay];

一些问题困扰着我,加入重力校正后我的代码变得越来越慢。我很难相信这么少的计算会拖慢程序,但是如果不加它,我的整个显示方法仍然可以运行,尽管它非常冗长。有没有什么选项可以考虑以避免这种情况?我是否漏掉了什么或者数学计算真的很慢吗?我可以在//math和//end math标签之间注释掉它并且完全没问题。
感谢任何帮助。
附言:如果有必要,可能会对某些人感兴趣,我正在使用Cocoa编程,而这个方法属于CALayer的子类,并实现了-drawInContext:。

1
你确定瓶颈不是当前分配,而是数学计算吗?我不熟悉那种语法,但如果那是某种构造函数和/或内存分配器,那可能比数学调用更昂贵。 - Jim Lewis
3
你是否尝试过使用Shark或其他性能分析工具?使用这些工具可以帮助你准确定位瓶颈所在。 - Ira Cooke
1
Jim Lewis:那是一个字面表达式,而不是在堆上的分配。C99将该语法称为“复合字面量”。它不应比字符串字面量更昂贵。 - Peter Hosey
12个回答

8

你在使用iPhone吗?试着使用这些函数的浮点变体:powf、sqrtf等。

更多信息请参考Kendall Helmstetter Gelner的答案中的第4条,此SO问题


3
实际上,iPhone 确实具有双精度硬件。然而,在3gs上,单精度算术因在NEON单元上执行而比VFP更快。此外,数学库中的单精度入口点通常被很好地优化,并且比相应的双精度函数更快。 - Stephen Canon
谢谢,我已经从答案中删除了那个错误的信息。 - nall
1
iPhone的CPU可能支持这些功能,但默认情况下所有内容都是为Thumb和使用软浮点编译的,您需要明确切换到ARM或者一切都将被模拟。 - Louis Gerbarg
1
它使用软浮点ABI并调用浮点操作的shim,但这些shim仅切换到ARM模式并使用浮点硬件。话虽如此,您绝对正确,在ARMv6上,Thumb模式下的浮点是性能隐患,而且浮点重代码应在该平台上关闭Thumb编译。 - Stephen Canon
ARM浮点硬件不能直接执行三角函数(但会使用基本FPU操作)。可以使用查找表在性能和精度之间进行权衡(速度更快,但精度较低)。 - Adriaan

6
正常缩短向量的方法如下:
float originalMagnitude = sqrtf(current.x * current.x, current.y * current.y, current.z* current.z);
float desiredMagnitude = originalMagnitude - 1.0f;
float scaleFactor = (originalMagnitude != 0) ? desiredMagnitude / originalMagnitude : 0.0f; // avoid divide-by-zero

current.x *= scaleFactor;
current.y *= scaleFactor;
current.z *= scaleFactor;

话虽如此,但每秒调用几个三角函数不会对速度造成太大影响。另一方面,-[NSMutableArray removeObjectAtIndex:] 对于大型数组来说可能会很慢。使用环形缓冲区(可以使用NSMutableArray或一个结构体的C数组)会更有效率。

OP应该提供时间,数组操作很明显。 - u0b34a0f6ae
我不确定你的意思。_historyDataBuffer是一个NSMutableArray。我的区域宽度,因此在开始替换之前的最大宽度约为475。一旦达到这个宽度,我就会从索引0处取出一个并添加一个新的。 - cemulate
从CFArray头文件中(也适用于NSArray):“插入或删除操作通常将是数组中值的数量线性的”即使您只删除单个值,由于该操作的结果,数组实现可能需要复制数组中的所有数据。 - Stephen Canon
删除索引为0的对象可能是O(n)操作。(NSMutableArray可能比这更快,但不能保证。)更快的方法是在移动索引处使用replaceObjectAtIndex:。有关更多信息,请参见环形缓冲区上的链接文章。尽管如此,一个475项的数组不应该明显地减慢您的速度。像James Jones和kaizer.se所说的那样,要找到真正的问题,请进行分析。(实际上,尝试完全不使用数学 - 我怀疑绘图是成本。) - Jens Ayton

6
除了理论上无法简单地消除地球引力的事实外,我会采取的第一步是对您执行的每个操作(乘法、除法、正弦、atan2等)进行基准测试,然后设计一种方法来避免计算时间显著较长的操作(或避免计算有问题的操作)。确保在基准测试中使用与最终产品相同的数据类型。
这是时间/精度权衡的典型例子。通常有多种算法可用于执行相同的计算,并且您还可以使用LUT /插值。
当我制作自己的Wii风格遥控器时,我遇到了相同的问题。如果您确定了昂贵的操作并且在工程方面遇到困难,请提出另一个问题。 :)

2
赞扬你观察到问题提出者想要做的事情根本不可能。 - Stephen Canon
2
我用了一种艰苦的方式学习它。:P 这被我的教授们验证了。 - James Jones
1
可靠地确定重力轴的唯一方法是通过外部约束来强制执行它...例如,如果您将重力轴保持平行于y轴,则可以可靠地确定重力影响y轴。您不能仅仅从重力净大小中推断1g。 - James Jones
在采用Ahruman第四个答案中提供的更好的解决方案后,我的代码仍然存在滞后问题。我稍后会尝试使用LUT并回报结果。至于不可能性,我实际上正在绘制来自Wii遥控器的加速度计输出,并且我想要校正由重力产生的幻象1g背景力,尽管我理解您关于相对性的观点。 - cemulate
爱因斯坦的理论指出,除非您将Wii遥控器垂直于重力场并将校正硬编码到其中一个轴中,否则您将无法“校正幻影1g背景力”。如果您计划像大多数人一样使用Wii遥控器,这对您将不起作用。并确保在编写LUT方法之前确定瓶颈。如果您没有必要,您不想降低精度或浪费时间。 - James Jones

5
  • 了解情况再行动,不要臆测。在你知道要改变什么之前,不要改变任何内容。

假设您得到的配置文件显示所有数学计算确实减缓了速度:

  • 绝不要写pow(someFloat,2)。编译器应该能够为您进行优化,但通常情况下,在较新的硬件上这些优化可能还没有到位。这应始终写作someFloat*someFloat。 pow()函数通常是数学库中最昂贵的函数。简单的乘法将始终至少与调用pow()一样快,并且始终至少与其一样准确(假设符合IEEE-754规范的算术)。而且对于编译器来说,这更容易优化。
  • 在C中使用float时,请使用带后缀的数学库函数。 sinf比sin要快。 sqrtf比sqrt要快。除了函数本身更快之外,还避免了不必要的转换为和从double。
  • 如果您在ARMv6处理器上看到减慢速度(不是3GS或新款iPod Touch),请确保在执行大量浮点运算时未编译为拇指代码。拇指指令集(在拇指2之前)无法访问VFP寄存器,因此每个浮点操作都需要一个小工具。这可能非常昂贵。
  • 如果您只想将加速度向量的长度减小1.0(提示:这不是您想要的效果),则有更有效的算法可以实现。

0

除非我误解了你的代码,否则你基本上是通过某个因子来缩放你的点。 我认为以下代码应该等同于你所做的。

double radius = sqrt(current.x * current.x 
                     + current.y * current.y 
                     + current.z * current.z);
double newRadius = radius - 1.0;
double scale = newRadius/radius;
current.x *= scale;
current.y *= scale;
current.z *= scale;

0

这些数学代码看起来很好。但我不太懂 Objective C,不知道当前的 = ... 行在做什么。它是否在堆上分配了内存,但没有被回收呢? 如果你将其注释掉会发生什么?你有观察过进程的执行情况吗?使用 top 命令查看是否开始占用更多的 CPU 或内存。


1
我不太了解Objective C,不知道current = ...这行代码在做什么。它可能会在堆上分配内存,但是没有被回收吗?不,这是一个C语言的字面表达式,与Objective-C无关,也不是堆分配。 - Peter Hosey

0

除了其他评论者使用浮点数(而不是双精度运算符)之外,做所有那些_dataHistoryBuffer的事情都会让你的应用程序崩溃。这将像没有明天一样消耗内存,而且由于您正在使用NSValue,那么所有这些对象都将被添加到自动释放池中,从而使内存消耗激增。除非您真的非常需要,否则最好避免保留值列表,并找出更适当的(即:固定大小、非对象)机制来存储它们。即使是结构体的循环缓冲区(例如10个结构体的数组,然后有一个计数器执行i++ % 10来索引它)也会更好。


最好使用2的幂模数,编译器可以将i % 16优化为i & 0xf。 - Zan Lynx

0

对其进行分析以确定问题所在。如有必要,逐步注释掉“数学”部分的子集。性能通常是人们猜测错误的东西,即使是聪明、深思熟虑、经验丰富的人。


0

只是出于兴趣 - 你知道Math SQRT函数是如何实现的吗?如果它使用了低效的近似算法,那么它可能是罪魁祸首。你最好的选择是创建一些测试工具,可以获得你正在使用的每个指令的平均性能。

另一个问题 - 增加或减少运算符的精度(即使用双精度浮点数而不是单精度浮点数)是否会以任何方式改变性能?


它使用VFP硬件sqrt指令。然而,即使是迭代方法,在iPhone的CPU上每秒也可以计算数百万个平方根。 - Stephen Canon

0

正如其他人所说,你应该进行性能分析以确保。话虽如此,添加额外的计算很可能会使其变慢。

默认情况下,iPhone 的所有代码都是为 Thumb-1 指令集编译的。Thumb-1 不支持本地浮点数操作,因此它最终会调用软件浮点实现。有两种处理方法:

  1. 为ARM编译代码。iPhone中的处理器可以自由混合Thumb和ARM代码,因此您只需将必要的部分编译为ARM即可。您应该注意,GCC(以及代理Xcode)无法将单个函数编译为ARM,您需要将所有相关代码隔离到其自己的编译单元中。最简单的方法可能是将整个项目设置为编译为ARM,以查看是否修复了问题(取消选中“构建选项”>“为Thumb编译”)。您应该注意,虽然ARM会加速浮点运算,但它会降低指令密度,从而损害缓存效率并降低所有其他代码的质量,因此请尽可能避免使用。
  2. 为Thumb-2编译。Thumb-2是Thumb的增强版本,添加了对某些浮点操作的支持。它仅适用于iPhone 3GS和新款iPod Touch,因此这可能不是您的选择。您可以通过将架构切换为“优化”,来构建一个针对旧设备的当前慢速版本和针对支持的设备的更快版本的fat二进制文件。

如果这似乎是最好的选择,您也可以结合使用这两个选项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接