这是一个算法问题。为了简单起见,我说我有两个双打,A和B.我想构建一个函数,它会给我差异,直到A的下一个倍数或B的下一个倍数,如果这是有道理的。
例如,假设A为3,B为5。
考虑倍数:(3,6,9,12,15)和(5,10,15)。
我想要输出的功能: (3,2,1,3,1,2,3),因为它需要3个单位才能达到3,然后再多达2个到达5,然后是1到6,然后是3到9等......
我希望这是有道理的。理想情况下,它是一个Python-esque生成器(虽然我在Arduino~C ++中编写它)。我需要快速 - 非常快。
真的很感激任何帮助。我的伪代码在下面,但它并不是那么好。
a = 3
b = 5
current = 0
distToA = a
distToB = b
for i in xrange(100):
if distToA > distToB: #B comes first
print "Adding {0}".format(distToB)
current += distToB
distToA -= distToBb
distToB = b
elif distToB > distToA: #A comes first
print "Adding {0}".format(distToA)
current += distToA
distToB -= distToA
distToA = a
else: #Equal
print "Adding {0}".format(distToA)
current += distToA #Arbitrarily, could be distToB
distToA = a
distToB = b
编辑:如何看待多个值?不只是a和b,还有c,d,e等。 我想我只是做一些if语句,但成本更高(每个分支的操作更多)。
答案 0 :(得分:3)
不清楚为什么你对你的代码不满意。如果是因为有那么多“if
测试,那么很容易做到没有:
def diffgen(a, b):
from itertools import cycle
diffs = []
current = 0
ab = a*b
while current < ab:
nextone = min((current // a + 1) * a,
(current // b + 1) * b)
diffs.append(nextone - current)
yield nextone - current
current = nextone
for d in cycle(diffs):
yield d
请注意,一旦达到a*b
,差异序列会重复,因此不再需要进行计算。
答案 1 :(得分:2)
让我们从一些一般观点开始。从您和您的同事那里了解直观的代码开始几乎总是更好。然后测量性能并找出瓶颈。如果您尝试从一开始就进行超优化,您将: -
最好测量,发现瓶颈,然后改善这些瓶颈的性能。如果您怀疑算法/实现很慢,那么对其进行分析。如果您想知道哪种算法/实现效果最佳,那么请对它们进行竞争。使用不同的数据集进行测试,因为对于一组输入(3,5)表现良好可能不适用于另一组(3,5000000)。
话虽如此,让我们从您拥有的内容开始并探索一些选项,然后最终为您在编辑中描述的案例提供一个开始实现,即多个值。请注意,其中一些实现可能不适合您的情况或适用于您的环境,但它们涉及一般方法。
现状(您的代码按原样)
此代码执行一些条件和算术运算。这些是处理器早餐吃的操作......在它们醒来之前......在纳米粒子的眼睑眨眼间,即非常快。现在,我知道你正在使用Arduino,因此不会拥有世界上最强大的处理器,但是,这些是处理器非常快速的操作。我想创建一些我自己的基准测试,所以我在C ++中实现了一个非常类似的功能(你提到C ++在你的问题中没问题)。我调用了测试ConditionalTest
,因为它遵循if...else
流程,因为我对名称不好。
注意:虽然我已对结果进行了一些基本测试,但这些答案中提供的代码绝不是生产准备好的。它缺少基本的参数检查(例如空值或未初始化的变量),并且有一些性能优化,我通常会优先考虑安全性。无论如何,代码是:
static void ConditionalTest( int startA, int startB, unsigned long long testIterations )
{
gl_result = 0;
gl_current=0;
int distToA = startA;
int distToB = startB;
for( unsigned long long i = 0; i < testIterations; i++ )
{
if( distToA > distToB ) //B comes first
{
gl_result = distToB;
gl_current += distToB;
distToA -= distToB;
distToB = startB;
}
else if( distToB > distToA ) //A comes first
{
gl_result = distToA;
gl_current += distToA;
distToB -= distToA;
distToA = startA;
}
else
{
gl_result = distToA;
gl_current += distToA; //Arbitrarily, could be distToB
distToA = startA;
distToB = startB;
}
}
}
注意: -
unsigned long long
作为testIterations
和其他一些变量,否则int
将会回绕。 gl_
是测试中的全局变量。 这种算法的好处是它使用了非常基本的结构,所以
现在,我正在运行一台相当庞大的机器(i7 2600),所以花了10亿次(10亿次)迭代才开始获得超过一秒钟的结果。在这种情况下,平均花费2400毫秒进行10亿次迭代。我认为这很快,但让我们看看我们如何改进事物。首先让我们看看我们可以调整什么。
对您的实施进行调整
参数为(3,5)
,因此最初distA为3,distB为5.请注意,3小于5.第一个if
将检查distToA > distToB:
然后{{1} }。然而,distToB(最初为5)的可能性是distToA(最初为3)的两倍。为了提高性能,您希望尽可能多地满足第一个elif distToB > distToA:
条件,以便最小化每次迭代中检查的条件数。在说这个时我会对编译器做一些假设,但稍后会有更多的假设。
所以,很简单,我们可以交换if
。然而,事情并非那么简单。我发现这个问题是编译器在第二个if和last else上做了一些很好的优化。您会看到评论ifs
的位置?好吧,Arbitrarily, could be distToB
中的gl_current += distToA;
和else if
中的gl_current += distToA
允许编译器将其优化为一个语句。所以,在我的情况下,它不是任意的(对你而言,它将取决于你的编译器)。因此,我们需要更改else以允许这些优化发生。最终的代码是:
else
注意:static void ConditionalTestV2( int startA, int startB, unsigned long long testIterations )
{
gl_result = 0;
gl_current=0;
int distToA = startA;
int distToB = startB;
for( unsigned long long i = 0; i < testIterations; i++ )
{
if( distToB > distToA ) //A comes first (where a is more likely to be first than b)
{
gl_result = distToA;
gl_current += distToA;
distToB -= distToA;
distToA = startA;
}
else if( distToA > distToB ) //B comes first
{
gl_result = distToB;
gl_current += distToB;
distToA -= distToB;
distToB = startB;
}
else
{
gl_result = distToB; //Should be distToB for optimisations
gl_current += distToB; //Should be distToB for optimisations
distToA = startA;
distToB = startB;
}
}
}
在if( distToB > distToA )
之前,而其他人现在有else if( distToA > distToB )
和gl_result = distToB
。有了这些更改,测试运行的时间是:2108毫秒。很简单,这些简单的调整使执行时间减少了12%。
这方面的最大教训是衡量您为意外后果所做的任何改变。
您的编译器和执行环境可能与我的不同,因此您的结果可能会有所不同。如果你要开始调整这个级别的东西,我建议你熟悉汇编程序并在关键点逐步完成程序集,以确定条件的实际执行情况。我确信还有其他一些可以做出的优化。如果你真的进入它并且正在使用GNU C ++,那么有一些名为gl_current += distToB
的东西,你可以在其中指导编译器关注哪个分支。
您可能无法始终按顺序获取起始值,在这种情况下,您需要根据执行算法的总时间来计算一次性排序的成本。
需要指出的其他一些事项是: -
__builtin_expect
,但不使用它。如果您没有使用current
,则可以将其删除。如果编译器已经优化了它,您可能看不到性能提升。 <强>模强>
看一下算法,我们总是得到一个值的距离,所以一种让人想到的方法是模数(已经有了解决这个问题的答案)。我对性能有点怀疑,因为模数倾向于使用比你的减法运算慢的除法。无论如何,这就是我提出的:
current
结果是23349毫秒。比原来快10倍。
现在,我通常不会写一行static void ModuloTest( int startA, int startB, unsigned long long testIterations )
{
unsigned long long current = 0;
unsigned long long prev = 0;
int distToA = startA;
int distToB = startB;
for( long long i = 0; i < testIterations; i++ )
{
current += (gl_result = FastMin(distToA - (current%distToA), distToB - (current%distToB)));
}
}
,但我试图减少作业的数量。这通常是一个愚蠢的事情,因为编译器将比我更好地优化,并且它更容易出错。不过,这个测试速度相当慢,我想确保给它一个很好的机会。由于流量有点不同,开始将手指指向模数会有点不公平,所以可能还有别的事情需要责备。所以,我做了一个更简单的模数测试:
current += (gl...
其中mod为50000,即使在这种情况下,测试花费的时间比测试长5倍,所以我认为如果我们正在寻找纯粹的性能增益,模数就会消失。我还发现stl min()有一些令人惊讶的低效率,但是详细说明这篇文章的篇幅会更长。
我接下来要做的就是查看数据。有时,如果您可以在数据中找到特征/模式,则可以相应地优化实施。
<强>模式强>
再次查看您的数据,跳出来的是差异将重复每static void PureModuloTest( unsigned long long testIterations, unsigned long long mod )
{
for(long long i = 1; i <= testIterations; i++)
{
gl_result = mod % i;
}
}
个周期。因此,在您的测试中,一旦达到15,距离将重复。您可能已经意识到这一点,但在您的代码段中,您运行了100个周期的测试(a * b
),所以我不确定。
使用这一事实的一种方法是存储值,直到我们到达for i in xrange(100)
,然后重复使用这些值,直到我们完成为止。请注意,这主要是使用您的算法开始,然后从那里迭代列表。
a * b
此测试耗时1711毫秒,比原版快约29%,比当前最佳速度快约18%。我不确定在您的情况下这是多么适用,但它是分析预期数据如何提供一些良好性能增益的一个示例。
线程富矿!
现在,由于您正在使用Arduino,这可能不适用于您的情况。但也许将来可能会支持线程,或者您可以将问题解决到不同的处理器。无论哪种方式,不包括线程基准是不可靠的,因为这是他们的生活。另外,我的电脑有8个内核,其中7个用时间懒散,所以很高兴让它们有机会狂奔。
如果您的数据或算法可以分解为独立的离散部分,那么您可以设计您的程序,以便它在不同的线程上运行独立的操作。现在我们从之前知道序列每static void PatternTest( int startA, int startB, unsigned long long testIterations )
{
int stop = startA * startB;
list<int> resultList;
int distToA = startA;
int distToB = startB;
int val = 0;
long long count = 0;
while( val < stop )
{
if( distToB > distToA ) //A comes first (where a is more likely to be first than b)
{
gl_result = distToA;
distToB -= distToA;
distToA = startA;
}
else if( distToA > distToB ) //B comes first
{
gl_result = distToB;
distToA -= distToB;
distToB = startB;
}
else
{
gl_result = distToB;
distToA = startA;
distToB = startB;
}
val += gl_result;
resultList.push_back(gl_result);
count++;
}
std::list<int>::const_iterator iter;
while( count < testIterations )
{
for( iter = resultList.begin(); iter != resultList.end() && count < testIterations; iter++ )
{
gl_result = *iter;
count++;
}
}
}
重复一次。因此,我们可以开始不同的点a * b
,其中&#39;(n modulo(a * b))== 0&#39;。
但是,我们可以做得更好,首先获取第一个n
的值,然后循环遍历单独线程上的值。这就是我在这里所做的。我选择运行4个线程。
a * b
结果是这花了574毫秒。高达76%的节省!关于线程的一些基本要点: -
以下是我们目前所处的位置图表:
现在,您可以编辑多个值。
多个值
好吧,据我所知,如果你有多个输入值(a,b,c,d ......),你的struct BonanzaThreadInfo
{
long long iterations;
list<int> resultList;
int result;
};
static void BonanzaTestThread( void* param )
{
BonanzaThreadInfo* info = (BonanzaThreadInfo*)param;
std::list<int>::const_iterator iter;
for( long long count = 0; count < info->iterations; )
{
for( iter = info->resultList.begin(); iter != info->resultList.end() && count < info->iterations; iter++ )
{
info->result = *iter;
count++;
}
}
delete param;
}
static void ThreadBonanzaTest( int startA, int startB, unsigned long long testIterations )
{
int stop = startA * startB;
list<int> resultList;
int distToA = startA;
int distToB = startB;
int val = 0;
long long count = 0;
while( val < stop )
{
if( distToB > distToA ) //A comes first (where a is more likely to be first than b)
{
gl_result = distToA;
distToB -= distToA;
distToA = startA;
}
else if( distToA > distToB ) //B comes first
{
gl_result = distToB;
distToA -= distToB;
distToB = startB;
}
else
{
gl_result = distToB;
distToA = startA;
distToB = startB;
}
val += gl_result;
resultList.push_back(gl_result);
count++;
}
long long threadIterations = (testIterations - count) / NUMTHREADS;
long long iterationsLeft = testIterations-count;
thread* bonanzaThreads = new thread[NUMTHREADS];
for( int i = 0; i < NUMTHREADS; i++ )
{
BonanzaThreadInfo* bonanzaThreadInfo = new BonanzaThreadInfo;
if( i == (NUMTHREADS - 1) )
{
bonanzaThreadInfo->iterations = iterationsLeft;
}
else
{
iterationsLeft -= threadIterations;
bonanzaThreadInfo->iterations = (threadIterations);
}
bonanzaThreadInfo->resultList = resultList;
bonanzaThreads[i] = thread(BonanzaTestThread,bonanzaThreadInfo);//http://stackoverflow.com/a/10662506/746754
}
for( int i = 0; i < NUMTHREADS; i++ )
{
bonanzaThreads[i].join();
}
delete [] bonanzaThreads;
}
语句将变得非常嵌套并且非常快。
if
我们通常会尝试订购下一个值,这就是我要开始的地方。我的第一个想法是将值存储在一些有序的数据结构中。我选择使用一个集合,因为一个集合是由一个密钥自然排序的(实际上它是一个多集合,因为我们需要允许欺骗)。在集合中,我放置了一个结构(称为ValuesStruct,因为我非常名称不好),其中包含要增加(a,b,c)的值以及此值将使用的下一个整数是最接近的。 if a < b && a < c && a < d...
运算符是这样的,stl知道将该值放在集合中的位置。
<
然后,我需要做的就是遍历集合。在每次迭代时,集合的前面将具有最小值。所以我可以通过从中减去前一个来计算当前间隔。然后,我只需要一个struct ValuesStruct
{
public:
int Value;
long long Next;
ValuesStruct( int start )
{
Value = start;
Next = start;
}
bool operator < (const ValuesStruct& rOther) const
{
return (Next < rOther.Next);
}
private:
ValuesStruct()
{
}
};
循环从列表中删除此值并将其添加回更新的Next值,以便它将在集合中采取适当的位置。我需要对所有具有此do..while()
的值进行此操作(例如,对于简单的3,5示例,情况就是15)。我调用了测试Next
,因为在这里我们需要检查多个比较条件,因为我对名字这么糟糕。
MultiConditionalTest
该功能使用如下:
static void MultiConditionalTest( multiset<ValuesStruct>& values, unsigned long long testIterations )
{
unsigned long long prev = 0;
for( unsigned long long i = 0; i < testIterations; i++ )
{
multiset<ValuesStruct>::iterator iter = values.begin();
gl_result = (*(iter)).Next - prev;
prev = (*(iter)).Next;
do //handle case where equal
{
ValuesStruct valuesStruct = *iter;
values.erase(iter);
valuesStruct.Next += valuesStruct.Value;
values.insert( valuesStruct );
iter = values.begin();
}while( (*iter).Next == prev );
}
}
正如你所看到的,这里有很多事情发生,所以我预计会有一点性能爆发并得到它:105156毫秒 - 大约慢50倍。这仍然不到每次迭代一微秒,所以它再次取决于你的目标。由于我今晚刚刚对此进行了分析而没有对其进行分析,因此我非常确定可以进行性能优化。首先,该集合通常实现为二叉搜索树。我会做一些研究,并确定这是否是这个问题的最佳数据结构。此外,当在列表中插入新值时,可以给出关于它将被放置的位置的提示。如果我们聪明地选择位置,那么我们可以加快这个操作。此外,和以前一样,当我们到达(a * b * c * d ...)时,序列将重复,因此我们可以存储这些值,然后从那时起将它们写出来。我还会看一下问题空间,看看是否有办法优化算法,可能会询问math.stackexchange.com上的数学序列 - 那些人非常敏锐。
无论如何,这只是一个选项,根据您的实际性能要求,它可能适用于您,也可能不适用。
其他一些想法:
答案 2 :(得分:1)
以下是使用模运算的方法:
a = 3
b = 5
current = 0
def nearest_multiple_of_a_or_b_to_current(current, a, b):
distance_to_a = (a - current%a)
distance_to_b = (b - current%b)
return current + min(distance_to_a, distance_to_b)
for i in range(100):
next = nearest_multiple_of_a_or_b_to_current(current, a, b)
print(next - current)
current = next
输出:
3
2
1
3
1
2
3
3
2
1