我需要帮助才能让这段代码更快:
UnitBase* Formation::operator[](ushort offset)
{
UnitBase* unit = 0;
if (offset < itsNumFightingUnits)
{
ushort j = 0;
for (ushort i = 0; i < itsNumUnits; ++i)
{
if (unitSetup[i] == UNIT_ON_FRONT)
{
if (j == offset)
unit = unitFormation[i];
++j;
}
}
}
else
throw NotFound();
return unit;
}
所以,为了给出一些背景知识,我有一个类Formation
,它包含一个指向UnitBase
个对象的指针数组,称为UnitFormation
。 UnitBase*
数组具有相同大小的数字数组,用于指示每个对应的UnitBase对象的状态,称为UnitSetup
。
我重载了[]
运算符,只返回指向具有特定状态的UnitBase对象的指针,所以如果我要求itsFormation[5]
,该函数不一定返回UnitFormation[5]
,但是UnitFormation的第5个元素,状态为UNIT_ON_FRONT
。
我尝试过使用上面的代码,但根据我的分析器,这花费了太多时间。这是有道理的,因为算法必须在返回请求的指针之前计算所有元素。
我是否需要完全重新思考整个问题,还是可以以某种方式加快速度?
提前致谢。
答案 0 :(得分:7)
一个快速优化就是在你找到它后立即返回单位,而不是继续迭代所有其他单位,例如。
if (j == offset)
unit = unitFormation[i];
变为
if (j == offset)
return unitFormation[i];
当然,这只有在您正在寻找的单位面向unitFormation序列的前端时才有用,但这样做很简单,并且有时会有所帮助。
对于每种状态,更快速,但更有效的方法是使其更快,以构建和维护具有该状态的单元的链接列表。您可以与主单元数组并行执行此操作,链接列表的内容将指向主单元数组,因此您不会复制单元数据。然后,要在状态中找到给定的偏移量,您可以只遍历链表的offset
节点,而不是遍历每个单元。
使其成为双向链表并保持尾指针可以让您找到具有高偏移的元素,就像低偏移一样快(从结束开始并向后退)。
但是,如果有很多具有相同状态的单位并且您正在寻找偏移量接近中间的单位,那么这仍然会很慢。
答案 1 :(得分:4)
如果重新设计你的代码以保持一个“前面的单位”的表格,无论那意味着什么,听起来很有趣:-)。如果真的要查询那部分并且经常不进行修改,那么你将节省一些时间。而不是检查整个或部分完整的单位列表,你将立即得到结果。
P.S。:int
将为您的CPU使用最自然的类型,因此使用ushorts doesn't make necessarily your program faster。
答案 2 :(得分:2)
除了一些人提出的其他建议之外,你可能想看看是否有任何这些函数的调用是不必要的,并消除了这些调用点。例如,如果您发现在结果无法改变的情况下重复调用此选项。最快的代码是永不运行的代码。
答案 3 :(得分:1)
是否可以按状态UNIT_ON_FRONT对数据进行排序(或插入排序)?这将使功能变得微不足道。
答案 4 :(得分:1)
单位状态多久会发生变化?也许您应该保留一个具有正确状态的单元列表,并且只在状态更改时更新该列表。
如果需要最小化状态更改的成本,您可以保留一个数组,其中显示前256个单元中有多少具有特定状态,接下来256个单元中有多少等等。一个可以扫描数组256次尽可能快地扫描单位,直到一个在第N个“好”单位的256个插槽内。更改单元的状态只需要递增或递减一个阵列槽。
考虑到各种使用模式,其他方法可用于平衡更改单位状态的成本与查找单位的成本。
答案 5 :(得分:1)
其中一个问题可能是太频繁地调用此函数。假设UNIT_ON_FRONT的比例是常数,则复杂度是线性的。但是,如果从循环中调用运算符,那么复杂性将上升到O(N ^ 2)。
如果相反,您返回类似boost::filter_iterator
的内容,则可以提高需要迭代UNIT_ON_FRONT的算法的效率。
答案 6 :(得分:1)
我已经完全重新设计了解决方案,使用了两个向量,一个用于前面的单元,一个用于其他单元,并且更改了所有算法,使得状态已更改的单元立即从一个向量移动到另一个向量。因此,我消除了[]运算符中的计数,这是主要的瓶颈。
在使用探查器之前,我的计算时间大约是5500到7000毫秒。看完答案后, 1)我将循环变量从ushort更改为int或uint,这将持续时间缩短了约10%, 2)我在二级算法中做了另一个修改,将持续时间再减少了30%左右, 3)我实现了如上所述的两个向量。这有助于将计算时间从~3300 ms减少到~700 ms,另外40%!
总之,减少了85-90%!感谢SO和分析器。
接下来,我将实现一个中介模式,并且只在需要时调用更新函数,可能会多出几毫秒。 :)
与旧代码段对应的新代码(功能现在完全不同):
UnitBase* Formation::operator[](ushort offset)
{
if (offset < numFightingUnits)
return unitFormation[offset]->getUnit();
else
return NULL;
}
更短,更重要。当然,还有许多其他重大修改,最重要的是unitFormation现在是std::vector<UnitFormationElement*>
,而不仅仅是UnitBase**
。 UnitFormationElement*
包含UnitBase*
以及之前在Formation
课程中闲置的一些其他重要数据。
答案 7 :(得分:0)
这不应该产生很大的影响,但您可以检查程序集以查看每次循环迭代是否加载itsNumFightingUnits
和itsNumUnits
,或者它们是否被放入寄存器。如果每次都加载它们,请尝试在函数开头添加临时值。
答案 8 :(得分:0)
对于最后一点果汁,如果定期抛出异常,可能需要切换到返回错误代码。这是更丑陋的代码,但缺乏堆栈跳跃可能是一个很大的帮助。关闭异常和RTTI在游戏开发中很常见。
答案 9 :(得分:0)
你是在愚弄自己(有时每个人都会这样做)。你做了一个简单的问题O(N ^ 2)。在考虑过载运营商之前,请考虑一下你必须做的事情。
在回复评论时添加:
尝试使用简单的语言(如C)或C ++的C子集。忘记抽象,收集课程,以及所有那些hao-haw。看看你的程序需要做什么,并以这种方式考虑你的算法。然后,如果你可以通过使用容器类和重载来简化它,而不需要再做任何工作,那么就去做吧。大多数性能问题都是由于采用简单的问题并通过尝试使用所有奇特的想法使它们变得复杂而引起的。
例如,您正在使用[]
运算符,通常被认为是O(1),并将其设为O(N)。然后我假设你在一些O(N)循环中使用它,所以得到O(N ^ 2)。你真正想要做的是遍历满足特定条件的数组元素。你可以这样做。如果它们非常少,而且您的频率非常高,那么您可能需要构建一个单独的列表。但请保持您的数据结构简单,简单,简单。最好“浪费”循环,只有在真正需要时才进行优化。