计算两个不同数字的倍数之间的差异

时间:2014-01-27 01:20:51

标签: python c++ algorithm

这是一个算法问题。为了简单起见,我说我有两个双打,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语句,但成本更高(每个分支的操作​​更多)。

3 个答案:

答案 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;
        }
    }    
} 

注意: -

  • 我将值分配给全局gl_result而不是打印它以节省用消息填充我的控制台,并且因为打印到屏幕的操作与其他操作相比需要很长时间,所以它会使结果膨胀。
  • 我必须使用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,则可以将其删除。如果编译器已经优化了它,您可能看不到性能提升。
  • 你的射程为100,但循环将重复3 * 5 = 15次。所以,你可以在电流为15时停止,如果这就是你所需要的,或者你可以存储结果然后把它们写出来(参见模式部分)。

<强>模

看一下算法,我们总是得到一个值的距离,所以一种让人想到的方法是模数(已经有了解决这个问题的答案)。我对性能有点怀疑,因为模数倾向于使用比你的减法运算慢的除法。无论如何,这就是我提出的:

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%的节省!关于线程的一些基本要点: -

  • 错误的复杂性和空间大大增加。
  • 如果线程之间存在任何共享资源,则该资源需要由互斥锁保护。如果线程经常同时需要相同的受保护资源,那么需要该资源的所有线程都需要等待它可用,如果性能非常差,则可能会导致这种情况。

以下是我们目前所处的位置图表:

results

现在,您可以编辑多个值。

多个值

好吧,据我所知,如果你有多个输入值(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上的数学序列 - 那些人非常敏锐。

无论如何,这只是一个选项,根据您的实际性能要求,它可能适用于您,也可能不适用。

其他一些想法:

  1. 您有多大可能获得相同的值集(a,b,c,d ......)?如果可能,您可能希望缓存以前的结果。那么只需要从缓存的数组中读取它们就可以了。
  2. 提高性能的另一种方法是打开编译器优化。如何执行此操作以及它的有效性取决于您的编译器。
  3. 祝你好运。

答案 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