c ++中的自编号

时间:2010-01-12 21:48:11

标签: c++ algorithm optimization

嘿,我和我的朋友们正在努力争取彼此的运行时间,以产生1到100万之间的“Self Numbers”。我用c ++编写了我的文章,我仍在努力减少宝贵的时间。

这是我到目前为止所拥有的,

#include <iostream>

using namespace std;

bool v[1000000];
int main(void) {
  long non_self = 0;
  for(long i = 1; i < 1000000; ++i) {
    if(!(v[i])) std::cout << i << '\n';
    non_self = i + (i%10) + (i/10)%10 + (i/100)%10 + (i/1000)%10 + (i/10000)%10 +(i/100000)%10;
    v[non_self] = 1;
  }
  std::cout << "1000000" << '\n';
  return 0;
}

代码现在工作正常,我只想优化它。 有小费吗?感谢。

15 个答案:

答案 0 :(得分:27)

我构建了一个不需要任何模运算或除法运算的备用C解决方案:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
   int v[1100000];
   int j1, j2, j3, j4, j5, j6, s, n=0;
   memset(v, 0, sizeof(v));
   for (j6=0; j6<10; j6++) {
      for (j5=0; j5<10; j5++) {
         for (j4=0; j4<10; j4++) {
            for (j3=0; j3<10; j3++) {
               for (j2=0; j2<10; j2++) {
                  for (j1=0; j1<10; j1++) {
                     s = j6 + j5 + j4 + j3 + j2 + j1;
                     v[n + s] = 1;
                     n++;
                  }
               }
            }
         }
      }
   }
   for (n=1; n<=1000000; n++) {
      if (!v[n]) printf("%6d\n", n);
   }
}

它生成97786个自身号码,包括1和1000000 有了输出,需要

real        0m1.419s
user        0m0.060s
sys         0m0.152s

当我将输出重定向到/ dev / null时,需要

real     0m0.030s
user     0m0.024s
sys      0m0.004s

在我的3 Ghz四核钻机上。

为了比较,你的版本产生相同数量的数字,所以我认为我们要么是正确的要么是错误的;但你的版本嚼了

real    0m0.064s
user    0m0.060s
sys     0m0.000s

在相同条件下,或大约2倍。

那个,或者你正在使用long的事实,这在我的机器上是不必要的。在这里,int增加到20亿。也许你应该检查你的INT_MAX

<强>更新

我有一种预感,分数计算总和可能会更好。这是我的新代码:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
   char v[1100000];
   int j1, j2, j3, j4, j5, j6, s, n=0;
   int s1, s2, s3, s4, s5;
   memset(v, 0, sizeof(v));
   for (j6=0; j6<10; j6++) {
      for (j5=0; j5<10; j5++) {
         s5 = j6 + j5;
         for (j4=0; j4<10; j4++) {
            s4 = s5 + j4;
            for (j3=0; j3<10; j3++) {
               s3 = s4 + j3;
               for (j2=0; j2<10; j2++) {
                  s2 = s3 + j2;
                  for (j1=0; j1<10; j1++) {
                     v[s2 + j1 + n++] = 1;
                  }
               }
            }
         }
      }
   }
   for (n=1; n<=1000000; n++) {
      if (!v[n]) printf("%d\n", n);
   }
}

...而且您知道什么,这将顶部循环的时间从12毫秒降低到4毫秒。或许8岁,我的时钟似乎在那里有点紧张。

事态,摘要

实际发现自身数量高达1M现在大约需要4毫秒,而且我无法测量任何进一步的改进。另一方面,只要输出到控制台,它将持续大约1.4秒,尽管我尽力利用缓冲。 I / O时间如此彻底地使计算时间相形见绌,任何进一步的优化都将是徒劳的。因此,虽然受到进一步评论的启发,但我决定单独离开。

引用的所有时间都在我(非常快)的机器上,仅用于比较目的。您的里程可能会有所不同。

答案 1 :(得分:13)

生成一次数字,将输出作为巨大的字符串复制到代码中。打印字符串。

答案 2 :(得分:13)

那些mod(%)看起来很贵。如果你被允许移动到base 16(甚至base 2),那么你可以更快地编写代码。如果你必须保持十进制,尝试为每个地方(单位,数十,数百)创建一个数字数组并构建一些翻转代码。这样可以更容易地总结数字。


或者,您可以识别核心自我功能的行为(让我们称之为s):

s = n + f(b,n)

其中f(b,n)是基数n中数字b的数字之和。

对于基数10,很明显,当那些(也称为最低有效)数字从0,1,2,...,9移动时,nf(b,n)继续保持同步当你从n移动到n+1时,它只有10%的时间被9滚到0而不是,所以:

f(b,n+1) = f(b,n) + 1  // 90% of the time

因此核心自我功能s前进为

n+1 + f(b,n+1) = n + 1 + f(b,n) + 1 = n + f(b,n) + 2

s(n+1) = s(n) + 2 // again, 90% of the time

在剩下的(并且容易识别的)10%的时间内,9回滚到零,并在下一个数字中加一,这在最简单的情况下从运行总数中减去(9-1),但可能会级联通过一系列的9s,减去99-1,999-1等。

因此,第一次优化可以从90%的周期中删除大部分工作!

if ((n % 10) != 0) 
{
  n + f(b,n) = n-1 + f(b,n-1) + 2;
}

if ((n % 10) != 0)
{
  s = old_s + 2;
}

这应该足以在不真正改变算法的情况下大幅提升您的表现。

如果你想要更多,那么为剩下的10%的迭代之间的变化制定一个简单的算法。

答案 3 :(得分:5)

如果你想要你的输出很快,可能值得研究用普通的printf()替换iostream输出 - 取决于赢得比赛的规则是否重要。

答案 4 :(得分:3)

多线程(为每个线程使用不同的数组/范围)。另外,不要使用比cpu核心数更多的线程=)

答案 5 :(得分:3)

循环中的 cout或printf会很慢。如果您可以从循环中删除任何打印件,您将看到显着的性能提升。

答案 6 :(得分:3)

由于范围是有限的(1到1000000),数字的最大总和不超过9 * 6 = 54.这意味着要实现筛子,54个元素循环缓冲区应该是完全足够(随着范围的增加,筛子的尺寸变得非常缓慢)。

你已经有了一个基于筛子的解决方案,但它基于预先构建全长缓冲区(100万个元素的筛子),这是相当不优雅的(如果不是完全不可接受的)。您的解决方案的性能也受到内存访问的非本地化的影响。

例如,这是一个非常简单的实现

#define N 1000000U

void print_self_numbers(void)
{
  #define NMARKS 64U /* make it 64 just in case (and to make division work faster :) */

  unsigned char marks[NMARKS] = { 0 };
  unsigned i, imark;

  for (i = 1, imark = i; i <= N; ++i, imark = (imark + 1) % NMARKS)
  {
    unsigned digits, sum;

    if (!marks[imark])
      printf("%u ", i);
    else
      marks[imark] = 0;

    sum = i;
    for (digits = i; digits > 0; digits /= 10)
      sum += digits % 10;

    marks[sum % NMARKS] = 1;
  }
}

(在这里,我不打算获得最佳的CPU时钟性能,只是用循环缓冲区来说明关键思想。)

当然,该范围可以很容易地转换为函数的参数,而曲线缓冲区的大小可以在该范围的运行时轻松计算。

至于“优化”......尝试优化包含I / O操作的代码毫无意义。通过这样的优化,你将无法实现任何目标。如果要分析算法本身的性能,则必须将生成的数字放入输出数组中,然后再打印出来。

答案 7 :(得分:1)

对于这样简单的任务,最好的选择是考虑替代算法来产生相同的结果。 %10通常不被视为快速操作。

答案 8 :(得分:1)

为什么不使用维基百科页面上给出的递归关系呢? 那应该是非常快。

编辑:忽略这个......重现关系会生成一些但不是全部的自编号。 实际上只有极少数。但是,从维基百科页面来看并不是特别清楚:(

答案 9 :(得分:1)

这可能有助于加速C ++ iostreams输出:

cin.tie(0);
ios::sync_with_stdio(false);

在你开始写cout之前把它们放在main中。

答案 10 :(得分:1)

我基于Carl Smotricz的第二个算法创建了一个基于CUDA的解决方案。识别自身号码的代码非常快 - 在我的机器上它执行约45纳秒;这比Carl Smotricz的算法快了大约150倍,该算法在我的机器上运行了7毫秒。

然而,有一个瓶颈,似乎是PCIe接口。我的代码耗费了43毫秒,将计算数据从显卡移回RAM。这可能是可以优化的,我会研究这个。

仍然,45纳米的速度非常快。可怕的是,实际上,我在我的程序中添加了代码,该代码运行了Carl Smotricz的算法,并将结果与​​准确性进行了比较。结果是准确的。这是程序输出(在VS2008 64位,Windows7中编译):

更新

我在发布模式下使用完全优化和使用静态运行时库重新编译了此代码,并带来了显着的结果。优化器似乎与Carl的算法做得很好,将运行时间从7毫秒减少到1毫秒。 CUDA实施也加快了,从35 us到20 us。从视频卡到RAM的存储器复制不受影响。

节目输出:

Running on device: 'Quadro NVS 295'
Reference Implementation Ran In 15603 ticks (7 ms)
Kernel Executed in 40 ms -- Breakdown:
  [kernel] : 35 us (0.09%)
  [memcpy] : 40 ms (99.91%)
CUDA Implementation Ran In 111889 ticks (51 ms)
Compute Slots: 1000448 (1954 blocks X 512 threads)
Number of Errors: 0

代码如下:

file:main.h

#pragma once

#include <cstdlib>
#include <functional>

typedef std::pair<int*, size_t> sized_ptr;
static sized_ptr make_sized_ptr(int* ptr, size_t size)
{
    return make_pair<int*, size_t>(ptr, size);
}

__host__ void ComputeSelfNumbers(sized_ptr hostMem, sized_ptr deviceMemory, unsigned const blocks, unsigned const threads);

inline std::string format_elapsed(double d) 
{
    char buf[256] = {0};

    if( d < 0.00000001 )
    {
        // show in ps with 4 digits
        sprintf(buf, "%0.4f ps", d * 1000000000000.0);
    }
    else if( d < 0.00001 )
    {
        // show in ns
        sprintf(buf, "%0.0f ns", d * 1000000000.0);
    }
    else if( d < 0.001 )
    {
        // show in us
        sprintf(buf, "%0.0f us", d * 1000000.0);
    }
    else if( d < 0.1 )
    {
        // show in ms
        sprintf(buf, "%0.0f ms", d * 1000.0);
    }
    else if( d <= 60.0 )
    {
        // show in seconds
        sprintf(buf, "%0.2f s", d);
    }
    else if( d < 3600.0 )
    {
        // show in min:sec
        sprintf(buf, "%01.0f:%02.2f", floor(d/60.0), fmod(d,60.0));
    }
    // show in h:min:sec
    else 
        sprintf(buf, "%01.0f:%02.0f:%02.2f", floor(d/3600.0), floor(fmod(d,3600.0)/60.0), fmod(d,60.0));

    return buf;
}

inline std::string format_pct(double d)
{
    char buf[256] = {0};
    sprintf(buf, "%.2f", 100.0 * d);
    return buf;
}

file:main.cpp

#define _CRT_SECURE_NO_WARNINGS 

#include <windows.h>
#include "C:\CUDA\include\cuda_runtime.h"
#include <cstdlib>
#include <iostream>
#include <string>
using namespace std;
#include <cmath>
#include <map>
#include <algorithm>
#include <list>

#include "main.h"

int main()
{
    unsigned numVals = 1000000;
    int* gold = new int[numVals];
    memset(gold, 0, sizeof(int)*numVals);

    LARGE_INTEGER li = {0}, li2 = {0};
    QueryPerformanceFrequency(&li);
    __int64 freq = li.QuadPart;

    // get cuda properties...
    cudaDeviceProp cdp = {0};
    cudaError_t err = cudaGetDeviceProperties(&cdp, 0);
cout << "Running on device: '" << cdp.name << "'" << endl;

    // first run the reference implementation
    QueryPerformanceCounter(&li);
    for( int j6=0, n = 0; j6<10; j6++ ) 
    {
        for( int j5=0; j5<10; j5++ ) 
        {
            for( int j4=0; j4<10; j4++ ) 
            {
                for( int j3=0; j3<10; j3++ ) 
                {
                    for( int j2=0; j2<10; j2++ ) 
                    {
                        for( int j1=0; j1<10; j1++ )  
                        {
                            int s = j6 + j5 + j4 + j3 + j2 + j1;
                            gold[n + s] = 1;
                            n++;
                        }
                    }
                }
            }
        }
    }
    QueryPerformanceCounter(&li2);
    __int64 ticks = li2.QuadPart-li.QuadPart;
    cout << "Reference Implementation Ran In " << ticks << " ticks" << " (" << format_elapsed((double)ticks/(double)freq) << ")" << endl;

    // now run the cuda version...
    unsigned threads = cdp.maxThreadsPerBlock;
    unsigned blocks = numVals/threads;
    if( numVals%threads ) ++blocks;
    unsigned computeSlots = blocks * threads;   // this may be != the number of vals since we want 32-thread warps

    // allocate device memory for test
    int* deviceTest = 0;
    err = cudaMalloc(&deviceTest, sizeof(int)*computeSlots);
    err = cudaMemset(deviceTest, 0, sizeof(int)*computeSlots);

    int* hostTest = new int[numVals];   // the repository for the resulting data on the host
    memset(hostTest, 0, sizeof(int)*numVals);

    // run the CUDA code...
    LARGE_INTEGER li3 = {0}, li4={0};
    QueryPerformanceCounter(&li3);
    ComputeSelfNumbers(make_sized_ptr(hostTest, numVals), make_sized_ptr(deviceTest, computeSlots), blocks, threads);
    QueryPerformanceCounter(&li4);

    __int64 ticksCuda = li4.QuadPart-li3.QuadPart;
    cout << "CUDA Implementation Ran In " << ticksCuda << " ticks" << " (" << format_elapsed((double)ticksCuda/(double)freq) << ")" << endl;
    cout << "Compute Slots: " << computeSlots << " (" << blocks << " blocks X " << threads << " threads)" << endl;


    unsigned errorCount = 0;
    for( size_t i = 0; i < numVals; ++i )
    {
        if( gold[i] != hostTest[i] )
        {
            ++errorCount;
        }
    }

    cout << "Number of Errors: " << errorCount << endl;

    return 0;
}

file:self.cu

#pragma warning( disable : 4231)
#include <windows.h>
#include <cstdlib>
#include <vector>
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
#include "main.h"

__global__ void SelfNum(int * slots)
{
    __shared__ int N;
    N = (blockIdx.x * blockDim.x) + threadIdx.x;

    const int numDigits = 10;

    __shared__ int digits[numDigits];
    for( int i = 0, temp = N; i < numDigits; ++i, temp /= 10 )
    {
        digits[numDigits-i-1] = temp - 10 * (temp/10)      /*temp % 10*/;
    }

    __shared__ int s;
    s = 0;
    for( int i = 0; i < numDigits; ++i )
        s += digits[i];

    slots[N+s] = 1;
}

__host__ void ComputeSelfNumbers(sized_ptr hostMem, sized_ptr deviceMem, const unsigned  blocks, const unsigned threads)
{
    LARGE_INTEGER li = {0};
    QueryPerformanceFrequency(&li);
    double freq = (double)li.QuadPart;

    LARGE_INTEGER liStart = {0};
    QueryPerformanceCounter(&liStart);

    // run the kernel
    SelfNum<<<blocks, threads>>>(deviceMem.first);
    LARGE_INTEGER liKernel = {0};
    QueryPerformanceCounter(&liKernel);

    cudaMemcpy(hostMem.first, deviceMem.first, hostMem.second*sizeof(int), cudaMemcpyDeviceToHost); // dont copy the overflow - just throw it away
    LARGE_INTEGER liMemcpy = {0};
    QueryPerformanceCounter(&liMemcpy);

    // display performance stats
    double e = double(liMemcpy.QuadPart - liStart.QuadPart)/freq,
        eKernel = double(liKernel.QuadPart - liStart.QuadPart)/freq,
        eMemcpy = double(liMemcpy.QuadPart - liKernel.QuadPart)/freq;

    double pKernel = eKernel/e,
        pMemcpy = eMemcpy/e;

    cout << "Kernel Executed in " << format_elapsed(e) << " -- Breakdown: " << endl
        << "  [kernel] : " << format_elapsed(eKernel) << " (" << format_pct(pKernel) << "%)" << endl
        << "  [memcpy] : " << format_elapsed(eMemcpy) << " (" << format_pct(pMemcpy) << "%)" << endl;



}

UPDATE2:

我重构了我的CUDA实现,试图加快它的速度。我这样做是通过手动展开循环,修复了__shared__内存的一些可疑用法,这可能是一个错误,并且摆脱了一些冗余。

我的新内核的输出是:

Reference Implementation Ran In 69610 ticks (5 ms)
Kernel Executed in 2 ms -- Breakdown:
  [kernel] : 39 us (1.57%)
  [memcpy] : 2 ms (98.43%)
CUDA Implementation Ran In 62970 ticks (4 ms)
Compute Slots: 1000448 (1954 blocks X 512 threads)
Number of Errors: 0

我改变的唯一代码是内核本身,所以我将在这里发布:

__global__ void SelfNum(int * slots)
{
    int N = (blockIdx.x * blockDim.x) + threadIdx.x;

    int s = 0;

    int temp = N;
    s += temp - 10 * (temp/10)      /*temp % 10*/;
    s += temp - 10 * ((temp/=10)/10)      /*temp % 10*/;
    s += temp - 10 * ((temp/=10)/10)      /*temp % 10*/;
    s += temp - 10 * ((temp/=10)/10)      /*temp % 10*/;
    s += temp - 10 * ((temp/=10)/10)      /*temp % 10*/;
    s += temp - 10 * ((temp/=10)/10)      /*temp % 10*/;
    s += temp - 10 * ((temp/=10)/10)      /*temp % 10*/;
    s += temp - 10 * ((temp/=10)/10)      /*temp % 10*/;
    s += temp - 10 * ((temp/=10)/10)      /*temp % 10*/;
    s += temp - 10 * ((temp/=10)/10)      /*temp % 10*/;

    slots[N+s] = 1;
}

答案 11 :(得分:0)

我想知道多线程是否会有所帮助。这个算法看起来很适合多线程。 (穷人对此的测试:创建程序的两个副本并同时运行它们。如果它在不到200%的时间内运行,多线程可能有帮助。)

答案 12 :(得分:0)

我真的很惊讶下面的代码比其他任何帖子更快。我可能测得错了,但也许有帮助;或者至少是有趣的。

#include <iostream>
#include <boost/progress.hpp>

class SelfCalc
{
private:
    bool    array[1000000];
    int     non_self;

public:
    SelfCalc()
    {
        memset(&array, 0, sizeof(array));
    }

    void operator()(const int i)
    {
        if (!(array[i]))
            std::cout << i << '\n';

        non_self = i + (i%10) + (i/10)%10 + (i/100)%10 + (i/1000)%10 + (i/10000)%10 +(i/100000)%10;
        array[non_self] = true;
    }
};

class IntIterator
{
private:
    int value;

public: 
    IntIterator(const int _value):value(_value){}

    int operator*(){ return value; } 
    bool operator!=(const IntIterator &v){ return value != v.value; }
    int operator++(){ return ++value; }
};

int main()
{
    boost::progress_timer t;

    SelfCalc selfCalc;
    IntIterator i(1), end(100000);

    std::for_each(i, end, selfCalc);

    std::cout << 100000 << std::endl;
    return 0;
}

答案 13 :(得分:0)

有趣的问题。所陈述的问题没有说明它必须具有什么基础。我摆弄了一些并写了一个base-2版本。它会产生额外的几千个条目,因为终止点1,000,000并不像base-2那样自然。这预先计算表查找的字节位数。结果集的生成(没有I / O)花费了2.4毫秒。

一个有趣的事情(假设我写得正确)是base-2版本有大约250,000个“自身号码”高达1,000,000,而在该范围内只有不到100,000个base-10自我号码。

#include <windows.h>
#include <stdio.h>
#include <string.h>

void StartTimer( _int64 *pt1 )
{
   QueryPerformanceCounter( (LARGE_INTEGER*)pt1 );
}

double StopTimer( _int64 t1 )
{
   _int64 t2, ldFreq;

   QueryPerformanceCounter( (LARGE_INTEGER*)&t2 );
   QueryPerformanceFrequency( (LARGE_INTEGER*)&ldFreq );
   return ((double)( t2 - t1 ) / (double)ldFreq) * 1000.0;
}

#define RANGE 1000000

char sn[0x100000 + 32];

int bitCount[256];

 // precompute bitcounts for each byte
void PreCountBits()
{
    int i;

    // generate count of bits in each byte
    memset( bitCount, 0, sizeof( bitCount ));
    for ( i = 0; i < 256; i++ )
        {
        int tmp = i;

        while ( tmp )
            {
            if ( tmp & 0x01 )
                bitCount[i]++;
            tmp >>= 1;
            }
        }
}


void GenBase2( )
{
    int i;
    int *b1, *b2, *b3;
    int b1sum, b2sum, b3sum;

    i = 0;
    for ( b1 = bitCount; b1 < bitCount + 256; b1++ )
        {
        b1sum = *b1;
        for ( b2 = bitCount; b2 < bitCount + 256; b2++ )
            {
            b2sum = b1sum + *b2;
            for ( b3 = bitCount; b3 < bitCount + 256; b3++ )
                {
                sn[i++ + *b3 + b2sum] = 1;
                }
            }

        // 1000000 does not provide a great termination number for base 2.  So check
        // here.  Overshoots the target some but avoids repeated checks
        if ( i > RANGE )
            return;
        }
}


int main( int argc, char* argv[] )
{
    int i = 0;
    __int64 t1;


    memset( sn, 0, sizeof( sn ));
    StartTimer( &t1 );
    PreCountBits();
    GenBase2();
    printf( "Generation time = %.3f\n", StopTimer( t1 ));

    #if 1
    for ( i = 1; i <= RANGE; i++ )
        if ( !sn[i] ) printf( "%d\n", i );
    #endif
    return 0;
}

答案 14 :(得分:-1)

也许只是尝试计算下面定义的递归关系?

http://en.wikipedia.org/wiki/Self_number