求一个函数的优化

FancyMouse 2013-03-20 06:29:14
发在这个区说明算法层面上的优化我已经想不出来了,只能追求常数上的优化了。瓶颈就一个函数,我想问一下能有什么优化。
先给代码:

typedef unsigned long long uint64;
uint64 dp[33][19];
int n;
uint64 StateToIndex(uint64 state, int blackpos)
{
uint64 result = 0;
int prev = 1;
for(int i=n-1;i>=0;i--)
switch((state >> (2*i)) & 0x3)
{
case 2:
result += dp[i][prev+1] + dp[i][prev];
if(--prev < 0)
return -1; //invalid state
break;
case 1:
result += dp[i][prev];
++prev;
default:
break;
}
return result;
}

以下是几点说明。
1. 代码里n的范围大概在24~28左右,并且可以看成常数,也就是说单个进程里n的值不会改变。dp数组是运行时计算,但是和输入无关,所以也能看成常数。uint64类型是必须的,uint32不够大。
2. 程序逻辑保证数据都合法,prev不会越界。for loop结束以后prev保证==0。
3. 那个if期望是一般不进入if。
4. 目前dp是运行期生成,n是全局变量。dp直接写编译期常数没有问题。n写成template int也没有问题,不过我不知道n做成const对这里有没有好处。坏处目前倒有一个,编译时间会从12秒增加到若干分钟(因为组合爆炸,代码里已经有很多的template了)。
5. 试过预计算dp[i][prev+1] + dp[i][prev],但是效果不明显。试过if(unlikely()),效果也不明显。
6. 最终平台linux 3.2 x64,gcc 4.7.2,开-O3。CPU是i7 3930K。目标是做到几倍的优化(我知道这有可能非常困难,但是做不到几倍的话我这程序就要跑一整年了)。


最后给个dp数组的计算代码:

dp[0][0] = 1;
for(int i=1;i<=32;i++)
for(int j=0;j<=16;j++)
{
if(j <= i-1)
dp[i][j] += dp[i-1][j]; // current value 0
if(j+1 <= i-1)
dp[i][j] += dp[i-1][j+1]; // current value 1
if(j > 0)
dp[i][j] += dp[i-1][j-1]; // current value 2
}
...全文
671 32 打赏 收藏 转发到动态 举报
写回复
用AI写文章
32 条回复
切换为时间正序
请发表友善的回复…
发表回复
纹枰老妖 2014-05-14
  • 打赏
  • 举报
回复
引用 3 楼 CKnightx 的回复:
下标访问换成指针访问 据说while循环比for循环要高效(本人没有测试过)
理论上确实如此
mujiok2003 2013-03-24
  • 打赏
  • 举报
回复
貌似没有写好。
typedef unsigned long long uint64;
uint64 dp[33][19];
int n;
uint64 StateToIndex(uint64 state, int blackpos)
{
    uint64 result = 0;
    int prev = 1;
    for(int i=n-1;i>=0;i--)
        switch((state >> (2*i)) & 0x3)
        {
        case 2:
            result += dp[i][prev+1] + dp[i][prev];
            if(--prev < 0)
                return -1; //invalid state
            break;
        case 1:
            result += dp[i][prev];
            ++prev;
        default:
            break;
        }
    return result;
}


uint64 StateToIndex_24(uint64 state, int blackpos)
{
    uint64 result = 0;
    int prev = 1;
    static uint64 masks[3][24] = {
      {
        (uint64)0x3<<0,	  (uint64)0x3<<2,	  (uint64)0x3<<4,	  (uint64)0x3<<6,	  (uint64)0x3<<8,	  (uint64)0x3<<10,	(uint64)0x3<<12,
        (uint64)0x3<<14,	(uint64)0x3<<16,	(uint64)0x3<<18,	(uint64)0x3<<20,	(uint64)0x3<<22,	(uint64)0x3<<24,	(uint64)0x3<<26,
        (uint64)0x3<<28,	(uint64)0x3<<30,	(uint64)0x3<<32,	(uint64)0x3<<34,	(uint64)0x3<<36,	(uint64)0x3<<38,	(uint64)0x3<<40,
        (uint64)0x3<<42,	(uint64)0x3<<44,	(uint64)0x3<<46
      },
      
      {
        (uint64)0x2<<0,	  (uint64)0x2<<2,	  (uint64)0x2<<4,	(uint64)0x2<<6,	    (uint64)0x2<<8,	(uint64)0x2<<10,	  (uint64)0x2<<12,
        (uint64)0x2<<14,	(uint64)0x2<<16,	(uint64)0x2<<18,	(uint64)0x2<<20,	(uint64)0x2<<22,	(uint64)0x2<<24,	(uint64)0x2<<26,
        (uint64)0x2<<28,	(uint64)0x2<<30,	(uint64)0x2<<32,	(uint64)0x2<<34,	(uint64)0x2<<36,	(uint64)0x2<<38,	(uint64)0x2<<40,
        (uint64)0x2<<42,	(uint64)0x2<<44,	(uint64)0x2<<46
      },
      
      {
        (uint64)0x1<<0,	  (uint64)0x1<<2,	  (uint64)0x1<<4,	  (uint64)0x1<<6,	  (uint64)0x1<<8,	(uint64)0x1<<10,	  (uint64)0x1<<12,
        (uint64)0x1<<14,	(uint64)0x1<<16,	(uint64)0x1<<18,	(uint64)0x1<<20,	(uint64)0x1<<22,	(uint64)0x1<<24,	(uint64)0x1<<26,
        (uint64)0x1<<28,	(uint64)0x1<<30,	(uint64)0x1<<32,	(uint64)0x1<<34,	(uint64)0x1<<36,	(uint64)0x1<<38,	(uint64)0x1<<40,
        (uint64)0x1<<42,	(uint64)0x1<<44,	(uint64)0x1<<46
      }
      
    };
    uint64 v;
#define _It_(i) \
    v = state & masks[0][i];\
    if(masks[1][i] == v)\
    {\
      result += dp[i][prev+1] + dp[i][prev];\
       if(--prev < 0)\
        return -1; \
    }\
    else if(masks[2][i] == v)\
    {\
      result += dp[i][prev];\
      ++prev;\
    }
  _It_(23);
  _It_(22);
  _It_(21);
  _It_(20);
  _It_(19);
  _It_(18);
  _It_(17);
  _It_(16);
  _It_(15);
  _It_(14);
  _It_(13);
  _It_(12);
  _It_(11);
  _It_(10);
  _It_(9);
  _It_(8);
  _It_(7);
  _It_(6);
  _It_(5);
  _It_(4);
  _It_(3);
  _It_(2);
  _It_(1);
  _It_(0);   
  return result;
}

mujiok2003 2013-03-24
  • 打赏
  • 举报
回复
去掉冗余的计算,手动展开代码。


uint64 StateToIndex_24(uint64 state, int blackpos)
{
    uint64 result = 0;
    int prev = 1;
    static uint64 masks[24] = 
    {
      (uint64)0x3<<0,	(uint64)0x3<<2,	(uint64)0x3<<4,	(uint64)0x3<<6,	(uint64)0x3<<8,	(uint64)0x3<<10,	(uint64)0x3<<12,
      (uint64)0x3<<14,	(uint64)0x3<<16,	(uint64)0x3<<18,	(uint64)0x3<<20,	(uint64)0x3<<22,	(uint64)0x3<<24,	(uint64)0x3<<26,
      (uint64)0x3<<28,	(uint64)0x3<<30,	(uint64)0x3<<32,	(uint64)0x3<<34,	(uint64)0x3<<36,	(uint64)0x3<<38,	(uint64)0x3<<40,
      (uint64)0x3<<42,	(uint64)0x3<<44,	(uint64)0x3<<46
    };
    uint64 v;
#define _It_(i) \
    v = state & masks[i];\
    if(2 == v)\
    {\
      result += dp[i][prev+1] + dp[i][prev];\
       if(--prev < 0)\
        return -1; \
    }\
    else if(1 == v)\
    {\
      result += dp[i][prev];\
      ++prev;\
    }
  _It_(23);
  _It_(22);
  _It_(21);
  _It_(20);
  _It_(19);
  _It_(18);
  _It_(17);
  _It_(16);
  _It_(15);
  _It_(14);
  _It_(13);
  _It_(12);
  _It_(11);
  _It_(10);
  _It_(9);
  _It_(8);
  _It_(7);
  _It_(6);
  _It_(5);
  _It_(4);
  _It_(3);
  _It_(2);
  _It_(1);
  _It_(0);   
  return result;
}
moolleychean 2013-03-24
  • 打赏
  • 举报
回复
引用 26 楼 FancyMouse 的回复:
结果发现主要是内存io瓶颈……那为什么性能还能随线程数线性增长呢……
看函数没发现什么内存IO的开销啊,dp应该在cache中,其他的都在寄存器中。
懒佯佯大哥 2013-03-23
  • 打赏
  • 举报
回复
引用 9 楼 FancyMouse 的回复:
引用 4 楼 sduxiaoxiang 的回复:1:上汇编代码 2:用uint32_t和一个int系数表示uint64_t,使用时 uint64_t ii = dp[0][0]*(int64_t)vv;//vv为系数 Plain Text code?12345678910111213141516171819202122232425262728293031323334……
牛!!!!
tofu_ 2013-03-23
  • 打赏
  • 举报
回复
引用 26 楼 FancyMouse 的回复:
结果发现主要是内存io瓶颈……那为什么性能还能随线程数线性增长呢……
此处存取的数据位置集中,非常合CPU缓存的胃口,也许其作用超过了大家的直观感觉吧。
FancyMouse 2013-03-23
  • 打赏
  • 举报
回复
结果发现主要是内存io瓶颈……那为什么性能还能随线程数线性增长呢……
oceanG_Y 2013-03-21
  • 打赏
  • 举报
回复
项下楼主,期望解答。
yong_f 2013-03-21
  • 打赏
  • 举报
回复
看上面的代码好像没什么可优化的的了,循环也只有20多次,剩下就是加减,那你试过没有这个函数的执行时间是多少,要不你把2*i改成i<<1试试,我觉得也差不多
ForestDB 2013-03-21
  • 打赏
  • 举报
回复
高老的题目啊,望其项背。
xiaohuh421 2013-03-21
  • 打赏
  • 举报
回复
uint64 StateToIndex(uint64 state, int blackpos) { uint64 result = 0; int prev = 1; for(int i=n-1;i>=0;i--) switch((state >> (2*i)) & 0x3) { case 2: result += dp[i][prev+1] + dp[i][prev]; if(--prev < 0) return -1; //invalid state break; case 1: result += dp[i][prev]; ++prev; default: break; } return result; } state >> (2*i) 这个应该可以优化, 因为 state是 64位数,而能移动的位数也就64位 后面的每次计算 2*i 即可优化成一个与i相关的数组随机访问.
常如意 2013-03-21
  • 打赏
  • 举报
回复
我只能来观望一下大神的解答了
FancyMouse 2013-03-21
  • 打赏
  • 举报
回复
引用 18 楼 ForestDB 的回复:
不如说想解决什么问题,多讨论总有好办法的。
原问题是这个,我想算这个序列http://oeis.org/A007764的前人未算出来的项(n>24的部分)。这问题本身够大够难。原来的算法瓶颈都在内存上。我把内存需求弄小十几倍以后发现我cpu反而算得非常慢,gprof以后发现瓶颈在这个函数,所以来发了这个帖。 那个算法本身我不觉得发这里能问出什么结果来。倒是优化这个函数可能有些希望。刚才unroll for写出来以后实际提升了15%+左右……
千树之影 2013-03-21
  • 打赏
  • 举报
回复
这代码再优化一些是没问题,优化几倍肯定要完全改思路了
FancyMouse 2013-03-20
  • 打赏
  • 举报
回复
引用 13 楼 lxyppc 的回复:
仅从楼主给的信息来看,不明白楼主到底要做什么 一般说来空间可以换时间 C/C++ code?1234567891011121314for(int i=n-1;i>=0;i--) switch((state >> (2*i)) & 0x3) { case 2: result += dp[i][pre……
正好相反,这代码的设计目的是少量时间换空间。直接依照state来存的话太耗内存,光n=24的时候就要3^24的空间。我这个一压以后只要16G就能存得下。 但是这么一换以后我的瓶颈反而掉到cpu这边了。 >这样有16个状态(实际上只有4个) 实际有9个。case 0的时候是合法状态,虽然只是掉去default里break了。但是如果2个2个做的话0就需要考虑了。 我也想过这里unroll loop,但是switch会变得很大,我不知道会不会反而变慢。
lxyppc 2013-03-20
  • 打赏
  • 举报
回复
仅从楼主给的信息来看,不明白楼主到底要做什么 一般说来空间可以换时间
for(int i=n-1;i>=0;i--)
        switch((state >> (2*i)) & 0x3)
        {
        case 2:
            result += dp[i][prev+1] + dp[i][prev];
            if(--prev < 0)
                return -1; //invalid state
            break;
        case 1:
            result += dp[i][prev];
            ++prev;
        default:
            break;
        }
你的目标是求result 而result和三个东西有关, state某几位,i,prev 你如果可以把这些东西做成一张表,速度应该可以提高一些 比如state的4位一起算,这样有16个状态(实际上只有4个) i有64/4=16种状态,prev最大可能到32,也有32种状态 这样你需要一张 16*16*32 = 8K的表来算result result += tab[i][prev][state>>i&0xf]; 还有就是prev会更改,也需要一张这样的8K表 prev = prev_tab[i][prev][state>>i&0xf] 16K的表把计算化成16次查表,表需要预先计算出来
sduxiaoxiang 2013-03-20
  • 打赏
  • 举报
回复
引用 11 楼 FancyMouse 的回复:
引用 10 楼 sduxiaoxiang 的回复:引用 9 楼 FancyMouse 的回复:引用 4 楼 sduxiaoxiang 的回复:1:上汇编代码 2:用uint32_t和一个int系数表示uint64_t,使用时 uint64_t ii = dp[0][0]*(int64_t)vv;//vv为系数 Plain Text code?123456789101……
统一使用一个系数就是 全部用uint32表示,使用时统一乘以一个系数 dp是uint32数组 使用时每个分量乘以系数 转化为uint64
FancyMouse 2013-03-20
  • 打赏
  • 举报
回复
引用 10 楼 sduxiaoxiang 的回复:
引用 9 楼 FancyMouse 的回复:引用 4 楼 sduxiaoxiang 的回复:1:上汇编代码 2:用uint32_t和一个int系数表示uint64_t,使用时 uint64_t ii = dp[0][0]*(int64_t)vv;//vv为系数 Plain Text code?1234567891011121314151617181920212223……
我所有合法状态经过StateToIndex函数以后将是从0开始的连续整数,当n=28的时候返回值可以从0到385719506619。不用uint64你怎么表示全?返回2个int和返回1个int64有什么区别?
ForestDB 2013-03-20
  • 打赏
  • 举报
回复
不如说想解决什么问题,多讨论总有好办法的。
sduxiaoxiang 2013-03-20
  • 打赏
  • 举报
回复
引用 9 楼 FancyMouse 的回复:
引用 4 楼 sduxiaoxiang 的回复:1:上汇编代码 2:用uint32_t和一个int系数表示uint64_t,使用时 uint64_t ii = dp[0][0]*(int64_t)vv;//vv为系数 Plain Text code?12345678910111213141516171819202122232425262728293031323334……
比如2的33次方 可以用两个数乘法表示,2的30次方<A> 2的3次方<B> A*B不就2的33次方了么 操作和存储都小了,不用uint64了 使用和存储一个系数就好了
加载更多回复(11)

64,687

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧