Skip to content

Conversation

Eureka1024
Copy link
Contributor

拉取/合并请求描述:(PR description)

[
这份PR的目的是增加一种新的查找字节最低非0位的算法,该算法比一般的位图算法更省空间,同时对于 32bit以及更长的字节,该算法查找字节最低非0位的实现路径也更加高效。

该算法的原理为:假设 X 为 uint32_t 类型的变量, 则 X & (X - 1) ^ X 的算式可以得到仅含字节最低非0位的结果,也就是将所有的可能转变为仅有的32种可能,也就是32个 “1在不同bit位” 的数,接着将这32个数对 37 取余,能够得到互不相同的结果,利用这些结果建立一个位图表,就能实现在O(1)的时间复杂度上查找到目标结果。

该算法已经经过测试程序对比,输入所有可能的输入,得到的结果与之前的位图算法相同。

当前拉取/合并请求的状态 Intent for your PR

必须选择一项 Choose one (Mandatory):

  • 本拉取/合并请求是一个草稿版本 This PR is for a code-review and is intended to get feedback
  • 本拉取/合并请求是一个成熟版本 This PR is mature, and ready to be integrated into the repo

代码质量 Code Quality:

我在这个拉取/合并请求中已经考虑了 As part of this pull request, I've considered the following:

  • 已经仔细查看过代码改动的对比 Already check the difference between PR and old code
  • 代码风格正确,包括缩进空格,命名及其他风格 Style guide is adhered to, including spacing, naming and other styles
  • 没有垃圾代码,代码尽量精简,不包含#if 0代码,不包含已经被注释了的代码 All redundant code is removed and cleaned up
  • 所有变更均有原因及合理的,并且不会影响到其他软件组件代码或BSP All modifications are justified and not affect other components or BSP
  • 对难懂代码均提供对应的注释 I've commented appropriately where code is tricky
  • 本拉取/合并请求代码是高质量的 Code in this PR is of high quality
  • 本拉取/合并使用formatting等源码格式化工具确保格式符合RT-Thread代码规范 This PR complies with RT-Thread code specification

@Eureka1024
Copy link
Contributor Author

增加一种新的查找字节最低非0位的算法

@enkiller
Copy link
Contributor

运行效率上有没有啥差距

@Eureka1024
Copy link
Contributor Author

Eureka1024 commented Sep 27, 2021

运行效率上有没有啥差距

首先该方式能够省下很多空间,原来的算法需要256个字节,而现在只需要37个字节的空间。
然后,该操作方式没有使用判断之类的语句,只需要常规的运算符实现即可,肯定比原来的效率高一点,具体高多少,我目前不知道有哪些测试能够有效的得出结论,希望有人能指点一下。

我觉得更重要的是,当该系统使用64位的MPU或者CPU的时候,这种方法绝对效果显著,无论时间还是空间效率,都将大大提高。

@Guozhanxin
Copy link
Member

运行效率上有没有啥差距

首先该方式能够省下很多空间,原来的算法需要256个字节,而现在只需要37个字节的空间。 然后,该操作方式没有使用判断之类的语句,只需要常规的运算符实现即可,肯定比原来的效率高一点,具体高多少,我目前不知道有哪些测试能够有效的得出结论,希望有人能指点一下。

我觉得更重要的是,当该系统使用64位的MPU或者CPU的时候,这种方法绝对效果显著,无论时间还是空间效率,都将大大提高。

写个循环调用的函数试试吧。两个函数都运行个多少万次之后,看看执行时间。

我是感觉之前的方式,只有移位和位运算对于计算机处理会方便不少。新加的算法有指数运算和取模运算,感觉时间上可能会慢一点。

@Eureka1024
Copy link
Contributor Author

Eureka1024 commented Oct 28, 2021

运行效率上有没有啥差距

首先该方式能够省下很多空间,原来的算法需要256个字节,而现在只需要37个字节的空间。 然后,该操作方式没有使用判断之类的语句,只需要常规的运算符实现即可,肯定比原来的效率高一点,具体高多少,我目前不知道有哪些测试能够有效的得出结论,希望有人能指点一下。
我觉得更重要的是,当该系统使用64位的MPU或者CPU的时候,这种方法绝对效果显著,无论时间还是空间效率,都将大大提高。

写个循环调用的函数试试吧。两个函数都运行个多少万次之后,看看执行时间。

我是感觉之前的方式,只有移位和位运算对于计算机处理会方便不少。新加的算法有指数运算和取模运算,感觉时间上可能会慢一点。

首先,新加的算法没有指数运算,那是异或操作。
取模操作在一些支持乘除法指令的单片机中,可以很方便的实现。
下面是该算法在 cortex-M3 的汇编实现:

127: int __rt_ffs(int value) 
128: { 
0x0800122C 4601      MOV      r1,r0 ;将r0的值(函数参数值)放入r1中
   130:   return __lowest_bit_bitmap_new[(uint32_t)( value & (value - 1) ^ value ) % 37]; 
0x0800122E 1E48      SUBS     r0,r1,#1 ; (value-1)结果放入r0
0x08001230 4008      ANDS     r0,r0,r1 ;(value & (value-1) 结果放入r0
0x08001232 4048      EORS     r0,r0,r1 ; (value & (value-1) ^ value))实现异或的结果放入r0
0x08001234 2225      MOVS     r2,#0x25 ; 将37放入r2
0x08001236 FBB0F3F2  UDIV     r3,r0,r2 ;r3为商
0x0800123A FB020013  MLS      r0,r2,r3,r0; 乘加,r0 = r0 - r2*r3
0x0800123E 4A01      LDR      r2,[pc,#4] ; @0x08001244 ;找到查找表的位置
0x08001240 5C10      LDRB     r0,[r2,r0] ;得到查找表中的值
   131: } 
0x08001242 4770      BX       lr ;函数返回

而原先的代码则需要比较多的代码实现,由于代码较多,请看这篇文章:https://blog.csdn.net/m0_37697335/article/details/121026018
但是整体而言,从运行时两者的汇编指令数来看,两者的运行效率应该差不多,之后具体跑跑验证。

@enkiller
Copy link
Contributor

enkiller commented Nov 6, 2021

执行效率

  • 主线版本:最少 9 条,最多 13 条。如果优先级均匀分布在 0 - 31,平均就是 11 指令。( 其中一个判定条件 2 条指令,一次计算 4 条指令。)

  • 精简版本:10 条汇编指令,其中有一次除法运算(2-12)时钟周期,平均约为 17 个指令

理论上精简版本性能会差一点

资源占用

  • 主线版本:表格 256 字节,代码 72 字节,总共 328 字节

  • 精简版本:表格 37 字节,代码 24 字节,总共 61 字节

优化 267 字节

CM3 指令周期

[CM3 指令时钟周期] https://developer.arm.com/documentation/ddi0337/h/programmers-model/instruction-set-summary/cortex-m3-instructions

@Eureka1024 Eureka1024 changed the title 增加一种新的查找字节最低非0位的算法 [kernel]增加一种新的查找字节最低非0位的算法 Nov 7, 2021
@Eureka1024
Copy link
Contributor Author

Eureka1024 commented Nov 7, 2021

执行效率

  • 主线版本:最少 9 条,最多 13 条。如果优先级均匀分布在 0 - 31,平均就是 11 指令。( 其中一个判定条件 2 条指令,一次计算 4 条指令。)
  • 精简版本:10 条汇编指令,其中有一次除法运算(2-12)时钟周期,平均约为 17 个指令

理论上精简版本性能会差一点

我使用 STM32F103 来实际测试,就是对所有可能的输入测试(0x00 - 0xFFFFFFFF),测得运行时间为
主线版本耗时:2510470ms,精简版本耗时:2272475ms。发现精简版本的运行效率更高。
分析了下主线版本的汇编代码,发现部分与运算的实现并不能简单实现,应该是造成效率比较慢的原因。如下所示:

1293:     if (value & 0xff00) 
0x08006F8E F401407F  AND      r0,r1,#0xFF00 //与操作
0x08006F92 B128      CBZ      r0,0x08006FA0

写了一篇验证文章,如有错误或者不严谨的地方,麻烦指教。
验证:为RT-Thread内核增加一种新的查找字节最低非0位的算法

@Eureka1024
Copy link
Contributor Author

写了一篇验证文章,主要是验证的一些过程、数据和方法。
如有错误或者不严谨的地方,麻烦指教。
验证:为RT-Thread内核增加一种新的查找字节最低非0位的算法

@enkiller
Copy link
Contributor

enkiller commented Nov 8, 2021

写了一篇验证文章,主要是验证的一些过程、数据和方法。 如有错误或者不严谨的地方,麻烦指教。 验证:为RT-Thread内核增加一种新的查找字节最低非0位的算法

跳转指令确实不能在一个机器周期内完成,当有多次跳转时,耗时可能比除法还要久。从目前的分析情况来看,这种算法,确实很优秀。

@Guozhanxin
Copy link
Member

写了一篇验证文章,主要是验证的一些过程、数据和方法。 如有错误或者不严谨的地方,麻烦指教。 验证:为RT-Thread内核增加一种新的查找字节最低非0位的算法

跳转指令确实不能在一个机器周期内完成,当有多次跳转时,耗时可能比除法还要久。从目前的分析情况来看,这种算法,确实很优秀。

如果感觉没问题的话,请+1

@Guozhanxin Guozhanxin added the +1 Agree +1 label Nov 8, 2021
@BernardXiong
Copy link
Member

执行效率

  • 主线版本:最少 9 条,最多 13 条。如果优先级均匀分布在 0 - 31,平均就是 11 指令。( 其中一个判定条件 2 条指令,一次计算 4 条指令。)
  • 精简版本:10 条汇编指令,其中有一次除法运算(2-12)时钟周期,平均约为 17 个指令

理论上精简版本性能会差一点

我使用 STM32F103 来实际测试,就是对所有可能的输入测试(0x00 - 0xFFFFFFFF),测得运行时间为 主线版本耗时:2510470ms,精简版本耗时:2272475ms。发现精简版本的运行效率更高。 分析了下主线版本的汇编代码,发现部分与运算的实现并不能简单实现,应该是造成效率比较慢的原因。如下所示:

1293:     if (value & 0xff00) 
0x08006F8E F401407F  AND      r0,r1,#0xFF00 //与操作
0x08006F92 B128      CBZ      r0,0x08006FA0

写了一篇验证文章,如有错误或者不严谨的地方,麻烦指教。 验证:为RT-Thread内核增加一种新的查找字节最低非0位的算法

欢迎提供更优秀的算法、代码 👍

@BernardXiong BernardXiong merged commit 301856a into RT-Thread:master Nov 11, 2021
@pegasusplus
Copy link

在微信文章上看到了,很棒!留了言似乎还能改进,不用额外空间来折半查找计算最低位bit,我这两天来试试看。

@pegasusplus
Copy link

pegasusplus commented Nov 30, 2024

#9729 我写的查找32bit最低非0bit位置的算法如下:

int __rt_ffs(uint32_t value) {
    if (value == 0)
        return 0; // 0 means no bit 1

    int position = 1; // position start from 1

    // search half range
    if ((value & 0xFFFF) == 0) { // is lower 16bit 0
        position += 16;
        value >>= 16;
    }
    if ((value & 0xFF) == 0) { // is lower 8bit 0
        position += 8;
        value >>= 8;
    }
    if ((value & 0xF) == 0) { // is lower 4bit 0
        position += 4;
        value >>= 4;
    }
    if ((value & 0x3) == 0) { // is lower 2bit 0
        position += 2;
        value >>= 2;
    }
    if ((value & 0x1) == 0) { // is lower 1bit 0
        position += 1;
    }

    return position;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
+1 Agree +1
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants