Fastestlog2(int)andlog2(float)implementation问题是是否有其他(和/或更快)的基本2log实现?应用log2(int)和log2(float)操作在许多不同的上下文中都很有用。仅举几例:压缩算法、3d引擎和机器学习。它们在几乎所有这些上下文中被调用数十亿次的低级代码中使用……尤其是log2(int)操作非常有用。因为我发现自己一直在使用log2,所以我不想给出我正在处理的特定应用程序。这也是一个真正的性能消耗这一事实(如各种应用程序的性能测试所示)。尽可能快地完成这项工作对我来说很关键。所有测试的实现的完整源代码都添加在底部,所以你可以自己看看。当然……至少运行3次测试并确保计数器足够大几秒钟。我还执行“添加”操作以确保整个循环不会被JIT'ter神奇地删除。让我们开始真正的工作。简单实现2log在C#中的简单实现是:(int)(Math.Log(x)/Math.Log(2))这个实现很简单,但也很慢。它需要2次Log操作,这本身就很慢。当然,我们可以通过使1.0/Math.Log(2)成为常量来优化它。请注意,我们需要稍微修改此常量以获得正确的结果(由于浮点错误)或添加一个小数字以获得正确的结果。我选择了后者,但这并不重要——最终结果在所有情况下都很慢。表查找一个更快的解决方案是使用查找表。虽然您可以使用任何2的幂查找表,但我通常使用256或64K条目的表大小。首先我们创建查找表:lookup=newint[256];for(inti=1;i<256;++i){lookup[i]=(int)(Math.Log(i)/Math.Log(2));接下来,我们按如下方式实现2log:privatestaticintLogLookup(inti){if(i>=0x1000000){returnlookup[i>>24]+24;}elseif(i>=0x10000){returnlookup[i>>16]+16;}elseif(i>=0x100){returnlookup[i>>8]+8;}else{返回查找[i];}}如您所见,表查找是一种更快、更快的实现——但作为一个缺点,它不能用于计算log2(float)。分支删除处理器是出了名的不擅长分支,所以我认为可以通过删除分支来改进表查找。代替if的字符串,我引入了第二个表,其中包含值和移位位以查找表中的条目:nobranch=newint[16]{0,0,8,8,16,16,16,16,24,24,24,24,24,24,24,24};privatestaticintLogDoubleLookup(inti){intn=(i|(i>>4));n=(n|(n>>2));n=(n|(n>>1));n=((n&0x1000000)>>21)|((n&0x10000)>>14)|((n&0x100)>>7)|(n&1);intbr=nobranch[n];返回查找[i>>br]+br;如果您运行此测试,您会发现它实际上比if-then-else解决方案慢。然后是Intel80386Intel几年前就知道这是一项重要的操作,因此他们在其处理器中实施了位扫描转发(BSF)。其他处理器也有类似的指令。这是迄今为止我所知道的最快的2log方法-但不幸的是,我现在知道如何在C#中使用这些不错的函数...平板电脑或手机当谈到市场时——我不知道有任何跨平台解决方案可以让我直接使用此功能。其他实现正如l4V指出的(谢谢!)还有其他实现,特别是:除了我喜欢这个名字之外,DeBruijn查找表和普通查找表一样快,这使它成为这里最快的算法之一......所有我尝试过的其他算法要慢得多。完整的测试代码publicclassLog2Test{publicstaticvoidTestNaive(){Stopwatchsw=newStopwatch();sw.开始();诠释n=0;for(inti=1;i>=1)>0)//展开以获得更快的速度...{r++;}返回r;}publicstaticvoidTestTrivialLoop(){秒表sw=newStopwatch();sw.开始();诠释n=0;对于(inti=1;i>20)-0x3FF;}publicstaticvoidTestFloat(){秒表sw=newStopwatch();sw.开始();诠释n=0;对于(inti=1;i<100000000;++i){n+=LogFloat(i);}sw.Stop();Console.WriteLine("Result:{0}-IEEEfloatimplementationtake{1:0.000}s",n,sw.Elapsed.TotalSeconds);}[StructLayout(LayoutKind.Explicit)]privatestructHelper{[FieldOffset(0)]publicintU1;[FieldOffset(4)]publicintU2;[FieldOffset(0)]publicdoubleD;}publicstaticvoidTestConstant(){doublec=1.0/Math.Log(2.0);秒表sw=new秒表();sw.开始();诠释n=0;对于(inti=1;i=0x1000000){返回查找[i>>24]+24;}elseif(i>=0x10000){returnlookup[i>>16]+16;}elseif(i>=0x100){returnlookup[i>>8]+8;}else{返回查找[i];}}publicstaticvoidTestLookup(){lookup=newint[256];对于(inti=1;i<256;++i){lookup[i]=(int)(Math.Log(i)/Math.Log(2));}秒表sw=new秒表();sw.开始();诠释n=0;对于(inti=1;i>4));n=(n|(n>>2));n=(n|(n>>1));n=((n&0x1000000)>>21)|((n&0x10000)>>14)|((n&0x100)>>7)|(n&1);intbr=nobranch[n];返回查找[i>>br]+br;}publicstaticvoidTestDoubleLookup(){//之前已经构建了查找表Stopwatchsw=newStopwatch();sw.开始();诠释n=0;for(inti=1;i=0;i--)//展开速度...{if((v&b[i])!=0){v>>=S[i];r|=S[i];}}返回r;*/intr=(((v>0xFFFF))?0x10:0);v>>=r;int移位=((v&g吨;0xFF)?0x8:0);v>>=转移;r|=转移;移位=((v>0xF)?0x4:0);v>>=转移;r|=转移;移位=((v>0x3)?0x2:0);v>>=转移;r|=转移;r|=(v>>1);返回r;}publicstaticvoidTestBinary(){//之前已经构建了查找表Stopwatchsw=newStopwatch();sw.开始();诠释n=0;for(inti=1;i>1;//第一轮向下取小于2的次方v|=v>>2;v|=v>>4;v|=v>>8;v|=v>>16;returnMultiplyDeBruijnBitPosition[(uint)(v*0x07C4ACDDU)>>27];}publicstaticvoidTestDeBruijn(){//查找表已经在前面构造好Stopwatchsw=newStopwatch();sw.Start();intn=0;for(inti=1;i<100000000;++i){n+=LogDeBruijn(i);}sw.Stop();Console.WriteLine("结果:{0}-deBruijn实现花费了{1:0.000}s",n,sw.Elapsed.TotalSeconds);}privatestaticint[]lookup;privatestaticredo只有int[]nobranch=newint[16]{0,0,8,8,16,16,16,16,24,24,24,24,24,24,24,24};staticvoidMain(string[]args){TestConstant();测试天真();TestDeBruijn();测试二进制();测试浮动();TestTrivialLoop();测试查找();测试双查找();控制台.ReadLine();在C#中:publicstaticuintFloorLog2(uintx){x|=(x>>1);x|=(x>>2);x|=(x>>4);x|=(x>>8);x|=(x>>16);返回(uint)(NumBitsSet(x)-1);}publicstaticuintCeilingLog2(uintx){inty=(int)(x&(x-1));y|=-y;y>>=(WORDBITS-1);x|=(x>>1);x|=(x>>2);x|=(x>>4);|=(x>>8);x|=(x>>16);返回(uint)(NumBitsSet(x)-1-y);}publicstaticintNumBitsSet(uintx){x-=((x>>1)&0x55555555);x=(((x>>2)&0x33333333)+(x&0x33333333));x=(((x>>4)+x)&0x0f0f0f0f);x+=(x>>8);x+=(x>>16);返回(int)(x&0x0000003f);}私有常量intWORDBITS=32;您应该在我链接的站点上查看原始代码以获取上下文,尤其是Log2(0)发生了什么。采用已经提到的二进制解决方案并删除分支。做了一些测试,结果证明它比DeBruijn快1.3倍。publicstaticintLog2(intv){intr=0xFFFF-v>>31&0x10;v>>=r;intshift=0xFF-v>>31&0x8;v>>=转移;r|=转移;移位=0xF-v>>31&0x4;v>>=转移;r|=转移;shift=0x3-v>>31&0x2;v>>=转移;r|=转移;r|=(v>>1);返回r;更多算法见http://www.asmcommunity.net/forums/topic/?id=15010也用C++做了一些测试,我的BSR实现比查表慢代码好://--------------------------------------------------------------------------DWORDlog2_slow(constDWORD&x){DWORDm,i;如果(!x)返回0;如果(x>=0x80000000)返回31;对于(m=1,i=0;m=0x00400000)返回_log2[x>>22]+22;elseif(x>=0x00000800)返回_log2[x>>11]+11;否则返回_log2[x];}//--------------------------------------------------------------------------测试代码:DWORDx,j,i,n=256;请求();对于(i=0;i<32;i++)对于(j=0;jLines->Add(tstr(1));tbeg();对于(i=0;i<32;i++)对于(j=0;jLines->Add(tstr(1));tbeg();for(i=0;i<32;i++)for(j=0;jLines->Add(tstr(1));我在AMDA8-55003.2GHz上的结果:[0.040ms]log2(x)-11位查找表[0.060ms]log2_asm(x)-BSR[0.415ms]log2_slow(x)-shiftloop注意:inlineintfast_log2(register双x){返回(reinterpret_cast(x)>>52)-1023;};这就是我使用的:unsignedlog2(registerunsignedn){registerunsignedi;对于(i=0;(n&1);n>>=1,i++);返回我;}编辑:检查这些(ffz变体):https://patchwork.kernel.org/patch/8845631/以上是C#学习教程:最快的log2(int)和log2(float)实现所有内容共享。如果对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处:
