登录 免费注册 首页 | 行业黑名单 | 帮助
维库电子市场网
技术交流 | 电路欣赏 | 工控天地 | 数字广电 | 通信技术 | 电源技术 | 测控之家 | EMC技术 | ARM技术 | EDA技术 | PCB技术 | 嵌入式系统
驱动编程 | 集成电路 | 器件替换 | 模拟技术 | 新手园地 | 单 片 机 | DSP技术 | MCU技术 | IC 设计 | IC 产业 | CAN-bus/DeviceNe

八年前,这段C代码给了俺极大的震撼

作者:bbear 栏目:单片机
八年前,这段C代码给了俺极大的震撼
俺从94年开始玩C,98年中上了网,在COMP.LANG.C中看到这段代码,心里的震撼真是难以形容。当时也自以为C已学透了自认为也都有点心得了。但这段代码对C关键词的运用从根本上改变了俺对自己和C的认识:

它的‘学名’叫 Duff's DEVICE'. 各位要有兴趣可自己上网去搜。

/******************************************/
    register n = (count + 7) / 8;    /* count > 0 assumed */
    SWITCH (count % 8)
    {
    case 0:       do { *to = *from++;
    case 7:        *to = *from++;
    case 6:        *to = *from++;
    case 5:        *to = *from++;
    case 4:        *to = *from++;
    case 3:        *to = *from++;
    case 2:        *to = *from++;
    case 1:        *to = *from++;
              } while (--n > 0);
    }
/******************************************/

2楼: >>参与讨论
magusneo
此人nb
c也真强.

3楼: >>参与讨论
hotpower
虽很"震撼"但from[count+8]即可解决战斗
 
4楼: >>参与讨论
jqlilee
我看不出那里会让人震撼。我承认我很菜。
 
5楼: >>参与讨论
赵崇伟
太无聊了
 
6楼: >>参与讨论
ipman
我认为如此不易读懂的代码有故弄玄虚之嫌,完全可以靠编译器优化
我很菜

7楼: >>参与讨论
qjy_dali
从个人的角度是很厉害,从工程的角度就是差差差
 
8楼: >>参与讨论
NE5532
看不懂,我承认我汇编写多了。
 
9楼: >>参与讨论
hhrong
可读性差
好的程序写出来一眼就看懂了!!!~~

10楼: >>参与讨论
newcore
一般
你可以看看Linux源代码,也许受到的“震撼”更大:)

呵呵,可读性往往和技巧性成反比!这个很难说...


11楼: >>参与讨论
木匠
可读性远比技巧性要重要的多,谢谢大家的评论:)
现在不能再依靠英雄主义了!英雄的时代过去了:)

12楼: >>参与讨论
su_mj000
memcpy (to, from, ...
int length = count;

while ( length-- )
    *to = *from++;

看不出原作者写的那段代码有何高明之处. 真正高明的
作品是用简短明了的语句实现复杂的算法.

* - 本贴最后修改时间:2005-9-5 10:27:57 修改者:su_mj000

13楼: >>参与讨论
BitFu
没见过
写出这样的代码可见此人对C语言的理解程度很深,但这样的代码可读性差。
说实话我还没看明白,先问一下,编译能通过吗?

14楼: >>参与讨论
John_Lee
我来说两句...
这段代码的作用等效于 memcpy (to, from, count),但有一点区别:两者的 count 参数定义域不同!

但作者为什么不用 memcpy 而要大费周章地写一段比较晦涩的内存复制程序呢?我们来仔细分析一下(因为这里是 AVR 论坛,我就以 AVR 为运行平台,编译程序用 avr-gcc)。

我把这段程序写成了一个函数,且称为 fast_memcpy:

#include <avr/io.h>
#include <stdlib.h>

void fast_memcpy (uint8_t *to, uint8_t *from, size_t count)
{
    register size_t n = (count + 7) / 8;    /* count > 0 assumed */
    uint8_t c = count % 8;
    SWITCH (c)
    {
    case 0:       do { *to++ = *from++;
    case 7:        *to++ = *from++;
    case 6:        *to++ = *from++;
    case 5:        *to++ = *from++;
    case 4:        *to++ = *from++;
    case 3:        *to++ = *from++;
    case 2:        *to++ = *from++;
    case 1:        *to++ = *from++;
              } while (--n > 0);
    }
}

而标准的 memcpy,大家都想得到应该是:

#include <avr/io.h>
#include <stdlib.h>

void memcpy (uint8_t *to, uint8_t *from, size_t count)
{
    while (count)
    {
        --count;
        *to++ = *from++;
    }
}

编译后的结果正如我们预料的一样,fast_memcpy 编译出来的代码比 memcpy 的代码长,大家可以自己试一试。

既然 fast_memcpy 在代码长度上不占优势,那么就只有在运行速度上做文章了。我们先分析 memcpy:

memcpy 编译后的代码如下:
void memcpy (uint8_t *to, uint8_t *from, size_t count)
{
   0:   dc 01           movw    r26, r24
   2:   fb 01           movw    r30, r22
    while (count)
    {
   4:   41 15           cp      r20, r1
   6:   51 05           cpc     r21, r1
   8:   29 f0           breq    .+10            ; 0x14
        --count;
   a:   41 50           subi    r20, 0x01       ; 1
   c:   50 40           sbci    r21, 0x00       ; 0
        *to++ = *from++;
   e:   81 91           ld      r24, Z+
  10:   8d 93           st      X+, r24
    }
  12:   f8 cf           rjmp    .-16            ; 0x4
}
  14:   08 95           ret

在 avr 中,复制一个字节最基本的代码是:一个 ld 指令加 一个 st 指令,共 4 个 指令周期,这是最小开销了。

memcpy 程序执行主要开销在循环体中(循环体之前的开销可以忽略不计),每个循环复制一个字节,循环占 11 个左右时钟周期。它的效率就是 4 / 11 = 36%。

我们再看 fast_memcpy:

void fast_memcpy (uint8_t *to, uint8_t *from, size_t count)
{
   0:   dc 01           movw    r26, r24
   2:   fb 01           movw    r30, r22
   4:   ca 01           movw    r24, r20
    register size_t n = (count + 7) / 8;    /* count > 0 assumed */
   6:   07 96           adiw    r24, 0x07       ; 7
   8:   9c 01           movw    r18, r24
   a:   43 e0           ldi     r20, 0x03       ; 3
   c:   36 95           lsr     r19
   e:   27 95           ror     r18
  10:   4a 95           dec     r20
  12:   e1 f7           brne    .-8             ; 0xc
  14:   07 97           sbiw    r24, 0x07       ; 7
    uint8_t c = count % 8;
  16:   87 70           andi    r24, 0x07       ; 7
    SWITCH (c)
  18:   83 30           cpi     r24, 0x03       ; 3
  1a:   d1 f0           breq    .+52            ; 0x50
  1c:   84 30           cpi     r24, 0x04       ; 4
  1e:   28 f4           brcc    .+10            ; 0x2a
  20:   81 30           cpi     r24, 0x01
15楼: >>参与讨论
mxh0506
玩C语言的技巧,看这个:
#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO(){
            _-_-_-_
       _-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
  _-_-_-_-_-_-_-_-_-_-_-_-_-_
    _-_-_-_-_-_-_-_-_-_-_-_
         _-_-_-_-_-_-_-_
             _-_-_-_
}


16楼: >>参与讨论
zsmbj
John_Lee讲解的非常好,在网上搜到这个:
泛型<编程>:类型化缓存(II)  
转自:http://www.cuj.com 作者:Andrei Alexandrescu

    我们以电视剧中常见的方式来回顾一下前文的重点。我们勾画了一个非常类似于std::vector的模板类buffer,除了buffer没有容积概念,并且增加了一些基本函数,比如grow_noinit和shrink_nodestroy。此外,前文提到把类型特性(type traits)作为一个提供优化的技术手段。最后,有个恶棍威胁说将要讲到拷贝对象和内存分配的问题。
   本文不直接讲buffer,而是要讲两个你经常用在buffer上的操作:用值填充一个buffer,在buffer间和buffer与不同容器间拷贝对象。我们会在几个填充和拷贝方法间通过计算来做出比较。

填充

   你知道的——就是拷贝同样的值到一个区间内的所有对象。C++标准库提供了两个填充函数:std::fill和std::uninitialized_fill。第二个操作假定被填充的对象完全没有初始化过。 最简单的泛型化内存填充函数可以是这样的//例1:一个简单的填充操作
template <typename T>
void Fill(T* beg, T* end, const T& VALUE)
{
for (; beg != end; ++beg) *beg = VALUE;
}  

   问题在于,这是否是最优实现?通常回答是:“产生优化代码是编译器的责任“——那么我就来测试一下 首先,我查看了一下Microsoft Visual C++ 6.0 (MSVC)和Metrowerls CodeWarrior 6.8(MWCW)为Fill产生的代码。它们都产生了一段简单循环的汇编代码。但是,x86,和其他许多现代的处理器一样,提供了专门的汇编指令来快速填充内存块。C库函数memset可能就用了这些指令。不幸的是,memset只能把内存设成同样的字节,只要你需要以长于一个字节的方式来设内存,你就不再能使用memset了,所以memset对通用代码没有扩展性。 (这里我讲几句题外话。C的内存操作函数memset,memcpy和memcmp是无与伦比的。这些函数可能被编译器厂商高度优化,优化范围包括编译器可能检测到对这些函数的调用并且用内联汇编指令代替它们——MSVC是这样做的。从而,对渴望速度的程序员,使用mem*函数被认为是很酷的方式) 使用拷贝来实现填充是一种方式。就是说,你可以利用这一点:一旦你部分填充了目标区间,你能够使用快速拷贝方式来拷贝已填充部分到未填充部分!酷的是你可以倍增每次被拷贝内存块的尺寸。比如说,你要用1.0填充一个有1000个double型成员的区间。

第一步,你对第一个位值赋以1.0。
第二步,你拷贝刚才赋的位置到它相邻位置。
第三步,你拷贝新赋的两个位置到下两个紧邻的位置。
第四步,你拷贝四个值然后后你得到八个——以此类推。10步内,你填充了整个1000个double的区间。你需要做的大部分事情实际上在最后一步,当512个位置已被填充,那么它们中的488个被拷贝到剩下的488个位置里。假设你的可供利用的快速拷贝函数的原型是:
template<typename T>
void QuickCopy(const T* sourceBegin,
const T* sourceEnd, T* destination);

      那么FillByCopy算法就是这样:

template <typename T>
void FillByCopy(T* beg, T* end, const T& VALUE)
{
if (beg == end) return;
*beg = VALUE;
T* dest = beg + 1;
for (;;)
{
  const size_t alreadyFilled = dest – beg;
  if (alreadyFilled >= end – dest)
  {
   QuickCopy(beg, beg + (end – dest), dest);
   break;
  }
  else
  {
   QuickCopy(beg, dest, dest);
   dest + = alreadyFilled;
  }
}
}  

   如果QuickCopy确实是个很快的函数,FillByCopy就是个很酷的算法。FillByCopy某种程度上类似于“俄国农民算法”,这个算法用最少步骤来计算整数幂[1]。许多人在不同领域发明拷贝填充——其中一个算法是用一个单字节文件来塞满整个磁盘。如果这是我原创的想法,我会赶快把拷贝填充叫做“罗马尼亚农民”算法。(译注:作者是罗马尼亚裔) 然后我迫不及待地写了个测试,得到了有趣的结果。但首先让我来为你介绍参赛的另一个算法。

Duff's DEVICE
   Duff's DEVICE[2]是一个加速循环语句的C编码技巧。其基本思想是,如果在一个for循环中,其中操作执行得如果足够快(比如说,嗯,一个赋值)——那么测试循环条件(上面例1中是beg != end)占用了循环所用时间的很大部分。循环应该被部分解开,这样数个操作一次完成,测试操作也做的较少。比如,如果你填充一个对象区间,你可能要在一次循环中赋二个或更多连续对象的值。
   你必须注意终止条件的细节及其他。在这里Duff's DEVICE是个新颖的,有创造力的解决方案。我们来很快地看一个基于Duff's DEVICE的泛型填充算法的实现。

template <class T> inline void FillDuff
(T* begin, T* end, const T& obj)
{
SWITCH ((end – begin) & 7)
{
case 0:
  while (begin != end)
  {
   *begin = obj; ++begin;
case 7: *begin = obj; ++ begin;
case 6: *begin = obj; ++ begin;
case 5: *begin = obj; ++ begin;
case 4: *begin = obj; ++ begin;
case 3: *begin = obj; ++ begin;
case 2: *begin = obj; ++ begin;
case 1: *begin = obj; ++ begin;
  }
}
}

    现在,如果你以前从没看过这样的代码,再回过头看一次,因为可能你看漏了什么。函数包含一个SWITCH语句,它的case语句同时位于一个while循环体内(有一个case语句在外面)。SWITCH内的表达式计算被八除的余数。执行开始于while循环内的哪个位置由这个余数决定。最终循环退出。(没有break)Duff's DEVICE这样就简单漂亮地解决了边界条件的问题。顺便提一下,为什么“case 0”标记在循环外面呢?这样不是打破了对称的美观吗?
这样做的唯一理由是为了处理空序列。当余数为零,“case 0”内就需要执行一个多余的测试来判断空序列的可能性。总之所有这些都无懈可击。
     结果是begin != end测试少执行八倍。这样,测试本身在循环持续期间所占的开销比例下降了八倍这个因数。当然,你也同样可以尝试用其他因数。
    Duff's DEVICE对效率的负面影响可能来自于代码膨胀(一些处理器更善于处理紧凑的循环而不是大的循环)和特别的结构。优化器被做成当遇到更熟悉简单的循环代码时说“啊哈!”,而遇到一些更加技巧性的结构时可能会不知所措从而生成比较保守的代码。

数量

    必须要知道关于优化的一点是(在你度过了“不要这样做”和“还是不要这样做”的阶段后),被实际测量过的优化才是有效的优化。上述的填充算法可能听上去不错,但只有测试才能证明它们有效。
    我写了个简单的测试程序,填充一个类型为double大小为COUNT的数组,分别使用了上述的三个算法:for循环,拷贝填充,和Duff's DEVICE。测试重复REPEAT次。我在一台PIII750 MHz上用了三个编译器:MSVC, MWCW,和GNU的g++ 2.95.2,测试每个算法若干次。通过改变COUNT和REPEAT,我得到如下结果* 当填充一个大的缓存(COUNT 为100,000和更多),直接for循环和Duff's DEVICE表现几乎一样。拷贝填充算法实际表现慢了1-5%[3]

* 当填充12,000个double的区间时,MSVC和MWCW上的拷贝填充快了23%,但g++是最喜欢for循环的,产生的结果是目前为止所有编译器和所有方法中最好的(20%-80%)。
* 当填充1,000个double的区间时,MSVC和MWCW产生类似的结果。Duff's DEVICE快了20%,拷贝填充快了75%,相比较直接for循环而言。再一次的,g++有不同表现,在所有方法上产生的结果都令人惊讶的快(比其他编译器最快的方法快了100%)
* 100个double,MSVC和MWCW结果一样,再一次g++用一半的时间完成任务(Duff'sdevice和拷贝填充比for循环快了12%)。

    我们通过检查现代PC的架构来寻找这个现象的解释。处理器比主内存总线快5-10倍。为了加速内存访问有二级缓存。一级在处理器内(1级),另一级在处理器边上(在奔腾三中和处理器封装在一起)
    最好情况是,一个操作的所有需要处理的内存都在1级缓存内。最坏情况是,你进行随机分散的内存存取操作,这样你总是不能命中缓存,最终都命中了主内存。 拷贝填充是不利于缓存的,因为每一轮它同时要命中的是两块内存区域—源和目标区域。比如说如果你填充1MB数据,最后一步会从一处拷贝512KB到另一处。这让缓存管理很不愉快。或者说不象处理直接填充循环那么愉快。这是为什么在填充大内存块时拷贝填充比for循环稍慢的原因。
(练习:你可以通过简单地修改FillByCopy一行代码来提高它的缓存友好度。提示:考虑局部存取)
     当填充大量数据时,你不能从缓存中得益,所以填充速度会主要被限定在主内存带宽上。你对循环本身作的优化不能带来很大提高,因为瓶颈在内存,而不是处理器操作。不管你写的循环有多聪明,使用寄存器,或不展开循环,处理器总是会等待主内存。这是为什么Duff'sDEVICE和for循环对大内存块表现一样。 当填充较少量数据时情况起了变化。数据有更多可能被放在缓存内,而缓存和处理器一样快。现在处理器执行的代码决定了循环的速度。
     memcpy(
17楼: >>参与讨论
zhaoxi322
佩服John_Lee  佩服zsmbj 太精妙了
 
18楼: >>参与讨论
hotpower
John_Lee每次都让我这AVR菜鸟感到吃惊
哈哈,又学到一招...

佩服!!!永远不会忘记"成员函数指针"里John_Lee给予的莫大启迪和教诲...

哈哈,那个"成员函数指针"现在用着确实很舒服...同时也不忘testcode海外游子的帮助!!!

19楼: >>参与讨论
su_mj000
John_Lee的帖子不错,可惜
楼主所给的代码并不能用memcpy()函数解决。

请注意
    *to = *from++;

而非
    *to++ = *from++;

20楼: >>参与讨论
John_Lee
*to = *from++ 还是有用
*to = *from++ 或 *to++ = *from 的形式主要用在系统里有硬件 fifo 的情况。在这种应用中,经常会在内存和硬件 fifo 之间传输大量的数据,如果使用 Duff's DEVICE 方法来传输数据,对整个系统效率都有较大的提高,而且 硬件 fifo 越大,效率提高越明显。例如:在一个 ATMEGA64 + RTL8019AS 以太网控制器的 系统中,使用 Duff's DEVICE 方法后,数据吞吐率提高了 5~8。


21楼: >>参与讨论
zsmbj
对,FIFO是不用增加地址的。*to = *from++即可。
而如果是内部RAM搬移,则需要 *to++ = *from++;不过AVR的++是一个语句就可以搞定的。实际这两个语句都是由一个 ld 指令加 一个 st 指令,共 4 个 指令周期组成的。

        *to = *from++;
136:    81 91           ld    r24, Z+
138:    8c 93           st    X, r24


        *to++ = *from++;
136:    81 91           ld    r24, Z+
138:    8d 93           st    X+, r24



22楼: >>参与讨论
testcode
好文章,先收藏了。
 
23楼: >>参与讨论
hudaidai
数据拷贝的代码可以用汇编实现
一般这是最影响数据吞吐率的地方,如确实需要,可以把该函数编译成汇编手工优化。

24楼: >>参与讨论
lhkjg
佩服John_Lee 、zsmbj。太深奥了!我们需要学习啊
 
25楼: >>参与讨论
促狭鬼
AVR不懂
但在C中“/8”用“>>3”代替,速度可提高很多

26楼: >>参与讨论
pheavecn
这样的程序不比Asm好懂。
效率嘛,呵呵。。。。算法比较重要。
鄙人之见,算法+内嵌汇编,再封装成一个子程序,我认为是兼顾C可读性与汇编高效性的方法.


* - 本贴最后修改时间:2005-9-8 22:34:47 修改者:pheavecn

27楼: >>参与讨论
victorymay
楼主把程序注释一下吧!
 
28楼: >>参与讨论
ketp
-
其实就是一次执行8次操作,但如果不满8次怎么办? 靠SWITCH跳转。


倒是那个8是怎么出来的,有什么依据?


29楼: >>参与讨论
micros
哦,来回看了几遍才看明白,各位的经典分析和解答
让俺又学了一招,高人啊

30楼: >>参与讨论
yangtse
收藏,学习。
 
31楼: >>参与讨论
铁匠
如果自以为这样才是高手,太愚昧了。
国外每年都举行C语言混乱代码大赛,
那里的代码更混乱。

如果以为代码写的晦涩难懂,就是高手,那就太愚昧了。


32楼: >>参与讨论
biansf2001
这段程序有问题吧?
如果case的数是7,这样case 0不执行,也就没有do了,那么接下来的}也就没有了{这样对吗?

33楼: >>参与讨论
hbicecream
为什么以8作为除数?
为什么以8作为除数?以其他数做除数行吗?

回楼上:
{}对编译器来说只是跳转位置而已,汇编程序是不管界限问题的

34楼: >>参与讨论
yjh350
没头没尾,写东西应该在解决问题的基础上
 
35楼: >>参与讨论
ar3000a
有点无聊,类似回字的四种写法
但世界需要这样的人,否则四种写法就要失传

36楼: >>参与讨论
John_Lee
说愚昧、无聊的人,我想告诉你们...
你们仔细研究过没有?不要凡是自己看不懂的,就借口“代码晦涩”说愚昧、无聊!

在“桌面系统”里,这种编程方法可能的确没有什么意思;但在嵌入式系统里,资源(空间、时间)往往比较受限。而在资源受限的系统中,“空间换时间”和“时间换空间”的编程方法是非常重要的,我这里说的“编程方法”,也就是所谓的“技巧”!这与“软件系统设计”或“编程语言”无关。

这段程序牺牲了代码空间,但减少了执行时间。这就是典型的“空间换时间”的编程方法。

我前面的帖子说了,如果在你的系统里,“时间”比较受限,并且需要频繁复制数据的话,你正好可以使用它。yjh350,你怎么能说它不能解决问题?

如果你在系统里使用了它,你当然不会在同一系统里再使用 memcpy(因为功能重复)。所以它和 memcpy 一样有用。

有人可能会说:我可以选择一个资源相对宽松的系统设计。但从绝对概念上说,这意味着要增加硬件成本。如果是做低成本的产品,会降低竞争力。


37楼: >>参与讨论
农民讲习所
俺不赞同
“空间换时间”为什么不使用汇编?
为什么你当然不会在同一系统里再使用 memcpy(因为功能重复)。?奇怪的论点。


38楼: >>参与讨论
ketp
我用了
我用他在51(11.0592M)上对128K外部ram清零用时1.5s左右,直接用for循环要3s

39楼: >>参与讨论
John_Lee
回答所长的两个问题。
Q1:“空间换时间”为什么不使用汇编?

A1:我才说了:这与“软件系统设计”或“编程语言”无关。我们当然是用同样的编程语言来比较!否则没有意义。我也可以用汇编来写 fast_memcpy!

Q2:为什么你当然不会在同一系统里再使用 memcpy(因为功能重复)。?奇怪的论点。

A2:我的话很难理解吗?我再说详细一点:如果在系统里使用了 fast_memcpy,就没有必要再使用 memcpy,可以将系统中所有的 memcpy 改为 fast_memcpy,因为两个函数都是复制内存。比如说,你的电脑里一般不会有 Office 和 WPS 同时存在吧?!


40楼: >>参与讨论
zsmbj
这么热闹,非常同意John_Lee的观点。
其实这个程序的本意是非常容易理解的。就是降低循环语句在整个循环中所占的时间比重。一次运行一个语句就进行比较,和一次运行8个语句再比较耗时是不一样的。

看John_Lee 前面的一个帖子,里边有详细的C语言编译出的汇编代码。可以这么说,即使用亲自用汇编编写也不会再有什么大的效率提高。

所以:这个函数效率的提高是靠这个算法实现的,而不是语言本身。

至于有人问为什么是8次,而不是其他。其实是4,8,16等都是可以的。甚至不是2的倍数也是可以的。不过程序要考虑编写方便。还有效率的提高和代码的增加比例。8是一个比较好的选择。当然选16会进一步提高效率,不过已经非常不明显了。而且还会使代码加长好多。

还有人提到“回”字的第4种写法。就更不切合实际了。回字的第4种写法,结果还是一个回字,没有任何其他的意义。而这个函数则是通过牺牲一点代码空间换了执行速度的提高。在一定应用上还是具有非常的意义的。



41楼: >>参与讨论
促狭鬼
非常同意John_Lee和zsmbj的观点
其实我看第一遍的时候也没看懂,但看了John_Lee最早回复的那个帖子(里边有详细的C语言编译出的汇编代码)及zsmbj紧跟其后的长帖。我明白了!
建议没看懂的朋友认真看完那2个回帖再说自己的观点

42楼: >>参与讨论
农民讲习所
这个问题牵涉移植,又扯大了
汇编在嵌入系统中,做低层函数最有效的,前面也说过:标准库中使用了高效率汇编优化的mem*函数的速度具有无与伦比的速度。

那么,我们剩下的话题就是:该不该使用汇编来编写这类的低层函数。

首先,前面各种讨论的C或C++方法,俺认为绝对不是一个嵌入系统设计者设计的,更多的是通用操作系统设计(或从这方面转到嵌入系统)的。俺公司就有很多这种实际的例证,通常他们更喜欢从高层向低层考虑。这也是俺对此歧义的地方。

俺认为嵌入系统上的移植问题,是软件结构和模块层次的移植,而低层能移植更好,但也不能过分讲究限制条件:该用汇编的还是使用汇编,毕竟你在C或C++里面在不同编译器上对硬件描述的是不一样的,同样在移植时需要修改。

C或C++再怎么写,也赶不上汇编,毕竟高级语言存在着寄存器和变量之间的交换(速度的瓶颈),而且一个平台上可以优化到最好的,不一定在另一个编译器上优化的好。所以还不如汇编直接、有效。

* - 本贴最后修改时间:2005-9-12 18:10:33 修改者:农民讲习所

43楼: >>参与讨论
blueboyjf
学习!
 
44楼: >>参与讨论
促狭鬼
与硬件设计关系不大
个人认为这种与硬件设计关系不大的功能(通用功能),还是用高级语言写好,便于移植。
再说,此段程序“看John_Lee 前面的一个帖子,里边有详细的C语言编译出的汇编代码。可以这么说,即使用亲自用汇编编写也不会再有什么大的效率提高。所以:这个函数效率的提高是靠这个算法实现的,而不是语言本身。”

45楼: >>参与讨论
农民讲习所
俺做了测试,标准库的memcpy就可以认为是汇编的
#include <iom8v.h>
#include <string.h>

void fast_memcpy (unsigned CHAR *to, unsigned CHAR *from, unsigned int count)
{
    unsigned int n = (count + 7) / 8;    /* count > 0 assumed */
    unsigned CHAR c = count % 8;
    SWITCH (c)
    {
    case 0:       do { *to++ = *from++;
    case 7:        *to++ = *from++;
    case 6:        *to++ = *from++;
    case 5:        *to++ = *from++;
    case 4:        *to++ = *from++;
    case 3:        *to++ = *from++;
    case 2:        *to++ = *from++;
    case 1:        *to++ = *from++;
              } while (--n > 0);
    }
}

void c_memcpy (unsigned CHAR *to, unsigned CHAR *from, unsigned int count)
{
    while (count--)
    {
        *to++ = *from++;
    }
}

unsigned CHAR aTest1[400];
unsigned CHAR aTest2[400];


//4MHZ下测试
void main(void)
{
    fast_memcpy( aTest1, aTest2, sizeof(aTest1) );      //931.25us   
    
    c_memcpy( aTest1, aTest2, sizeof(aTest1) );            //1414.00us
    
    memcpy( aTest1, aTest2, sizeof(aTest1) );            //807.50us
    
    while(1);   
}


这种C的技巧,不如使用标准库来得直接了当。低层函数使用汇编并被有效封装在函数中,也符合移植的标准。没必要强求C/C++的技巧。
写这些汇编的函数非常简单方便,要么有源代码,要么可以从生成的LIST文件中简化下,要么干脆直接在AVRSTUDIO中查看库的汇编的代码,直接抄下来。这样的移植会被影响吗?如果还说有影响,俺会怀疑你是不是做嵌入设计的。(玩笑)

* - 本贴最后修改时间:2005-9-13 11:52:43 修改者:农民讲习所

46楼: >>参与讨论
农民讲习所
做嵌入式设计,不要过多的提起低层函数的移植问题。
这是俺的个人观点。

47楼: >>参与讨论
zsmbj
To :农民讲习所。你弄错了吧
我用gcc也测试了一下,结果和你的不同:

fast_memcpy   //465.5us
c_memcpy      //1205.5us
memcpy        //801.25us

你的好像是icc吧。不管是哪个编译器吧。我觉得你是把编译器的初始化时间都算在了fast_memcpy的时间里了吧。从理论和实际来讲,你的那个时间都是错误的。

memcpy为800多us是正常的,以下是基本代码:
112:    0d 90           ld    r0, X+
114:    01 92           st    Z+, r0
116:    01 97           sbiw    r24, 0x01    ; 1
118:    e1 f7           brne    .-8          ; 0x112
一个copy为2+2个时钟周期,加上循环语句也是2+2个时钟周期。

所以4+4=8,400×8=3200时钟周期,1/4us的时钟,刚好为800us的语句循环。加上初始几个赋值语句为801.25us。

而fast_memcpy则是通过一次copy8个数据来实现的,以下是基本代码:
    case 0:       do { *to++ = *from++;
  9e:    81 91           ld    r24, Z+
  a0:    89 93           st    Y+, r24
    case 7:        *to++ = *from++;
  a2:    81 91           ld    r24, Z+
  a4:    89 93           st    Y+, r24
    case 6:        *to++ = *from++;
  a6:    81 91           ld    r24, Z+
  a8:    89 93           st    Y+, r24
    case 5:        *to++ = *from++;
  aa:    81 91           ld    r24, Z+
  ac:    89 93           st    Y+, r24
    case 4:        *to++ = *from++;
  ae:    81 91           ld    r24, Z+
  b0:    89 93           st    Y+, r24
    case 3:        *to++ = *from++;
  b2:    81 91           ld    r24, Z+
  b4:    89 93           st    Y+, r24
    case 2:        *to++ = *from++;
  b6:    81 91           ld    r24, Z+
  b8:    89 93           st    Y+, r24
    case 1:        *to++ = *from++;
  ba:    81 91           ld    r24, Z+
  bc:    89 93           st    Y+, r24
              } while (--n > 0);
  be:    21 50           subi    r18, 0x01    ; 1
  c0:    30 40           sbci    r19, 0x00    ; 0
  c2:    69 f7           brne    .-38         ; 0x9e

一个copy为(2+2)×8个时钟周期,加上循环语句也是2+2个时钟周期。

所以32+4=36,因为每次copy8遍,这样50次就可以了,所以50×36=1800时钟周期,1/4us的时钟,为450us的语句循环。加上初始几个赋值语句比较多。结果为465.5us。

* - 本贴最后修改时间:2005-9-13 13:36:49 修改者:zsmbj

48楼: >>参与讨论
农民讲习所
肯定没错,用ICC
从道理解释,memcpy绝对是最快的,但每个C编译器的实现方式不一样,有可能出现你说的fast_memcpy比memcpy快,可能memcpy不是汇编编出来的。

49楼: >>参与讨论
zsmbj
将以上情况归结为一般情况:

假设一个循环语句内部的循环体占a个时钟周期,而循环语句则占b个时钟周期。而我们一次要copy有n个数据。一个时钟周期刚好为1us。那么对于一个普通的循环来说,占用的总时间为:

    X = n*(a+b) = n*a+n*b

而对于Duff's DEVICE'来说,假设他的循环体每次有k个循环(这里我们一般都取8)。那么占用的总时间为:

    Y = n/k(k*a+b) = n*a+n*b/k

当然他们还都有一个初始语句。Duff's DEVICE'会占用的多一些。假设先不考虑这些。则我们可以看出Y是要比X小的。这是肯定的。因为:

    X - Y = n*a+n*b - n*a+n*b/k = n*b*(1-1/k)

那么效率是多少呢:

    Y/X = (n*a+n*b/k) / (n*a+n*b) = (a+b/k) / (a+b)

按照例子,还是一个循环体为4个时钟周期,一个循环语句也是4个时钟周期,而K=8,则:

    Y/X = (a+b/k) / (a+b) = (1+1/k) / 2 = 9/16 =450/800

和刚才的结果基本吻合,至于多出的语句,就是上面提到的初始语句了。但是如果n足够大初始语句占的比例就非常小。效率还是有很大的提高的。


50楼: >>参与讨论
zsmbj
To :农民讲习所。你这样试试:
//4MHZ下测试
void main(void)
{
    memcpy( aTest1, aTest2, sizeof(aTest1) );            //807.50us
    
    c_memcpy( aTest1, aTest2, sizeof(aTest1) );          //1414.00us
    
    fast_memcpy( aTest1, aTest2, sizeof(aTest1) );      //931.25us   
    
    while(1);   
}

我想看看结果。我这里没有icc。如果结果还是931。25us。我就算服。

你说“可能memcpy不是汇编编出来的”这个我就不赞同了,不要动不动就把汇编搬出来吓人。我原来也是写汇编出身的。上万行的汇编也写过。如果你能把这个C代码编译出来的汇编循环语句再精简一下。我可就真的服死你了。嘿嘿!(玩笑)
112:    0d 90           ld    r0, X+
114:    01 92           st    Z+, r0
116:    01 97           sbiw    r24, 0x01    ; 1
118:    e1 f7           brne    .-8          ; 0x112







* - 本贴最后修改时间:2005-9-13 13:54:44 修改者:zsmbj

51楼: >>参与讨论
促狭鬼
就是哦
嘿嘿,好象人家一表扬C,就不懂汇编似的(开玩笑)

52楼: >>参与讨论
农民讲习所
没错,就是931.25us,因为存在着指针变量和寄存器的交换
 
53楼: >>参与讨论
农民讲习所
需要俺把生成的汇编贴出来吗?
不同C编译器,优化方式是不同的。如果优化到极限,当然可以和汇编相比,但好象太理想化了。

用汇编来写这种算法,用空间换时间更好,比如16字节、64字节循环一次更快。:)

还是汇编最快,流程描述更简单,比C容易理解多了。

* - 本贴最后修改时间:2005-9-13 14:44:35 修改者:农民讲习所

54楼: >>参与讨论
农民讲习所
我们再看下在KEIL上的情况:24m,51
#include <reg51.h>
#include <string.h>

void fast_memcpy (unsigned CHAR xdata *to, unsigned CHAR xdata *from, unsigned int count)
{
    unsigned int n = (count + 7) / 8;    /* count > 0 assumed */
    unsigned CHAR c = count % 8;
    SWITCH (c)
    {
    case 0:       do { *to++ = *from++;
    case 7:        *to++ = *from++;
    case 6:        *to++ = *from++;
    case 5:        *to++ = *from++;
    case 4:        *to++ = *from++;
    case 3:        *to++ = *from++;
    case 2:        *to++ = *from++;
    case 1:        *to++ = *from++;
              } while (--n > 0);
    }
}

void c_memcpy (unsigned CHAR xdata *to, unsigned CHAR xdata *from, unsigned int count)
{
    while (count--)
    {
        *to++ = *from++;
    }
}


unsigned CHAR xdata aTest1[400];
unsigned CHAR xdata aTest2[400];


//4MHZ下测试
void main(void)
{
    c_memcpy( aTest1, aTest2, sizeof(aTest1) );    //741600
    fast_memcpy( aTest1, aTest2, sizeof(aTest1) );     //555800    
    memcpy( aTest1, aTest2, sizeof(aTest1) );       //443100
    
    while(1);   
}

还是memcpy最快。

55楼: >>参与讨论
AVRx007
51哪里来双指针?
这段代码放在AVR上才能起作用。
AVR的三个指针,预减/后加指令。

51在大数组运算方面怎样都是慢---一个ACC,一个DPTR.
小的内部SRAM倒是方便一些-------R0~R7,SRAM直接寻址。

56楼: >>参与讨论
农民讲习所
没有,俺才不会做这种傻事。
现在是测试相对代码效率,不是说哪个MCU快慢。
俺主要说明:还是使用标准库是最好的最安全的方法。如果想追求极限的快感,就用汇编吧。

* - 本贴最后修改时间:2005-9-13 17:16:38 修改者:农民讲习所

57楼: >>参与讨论
AVRx007
虽然memcpy是更常用更稳定的方法
但 LZ的方法在GCCAVR中确实比 memcpy快。
不能颠倒事实。

ICCAVR? 我早已放弃,因为这些垃圾代码简直是在侮辱AVR!

郁闷ing
10:           case 0:       do { *to++ = *from++;
+00000081:   01F9        MOVW    R30,R18          Copy register pair
+00000082:   01D8        MOVW    R26,R16          Copy register pair
+00000083:   9001        LD      R0,Z+            Load indirect and postincrement
+00000084:   019F        MOVW    R18,R30          Copy register pair
+00000085:   920D        ST      X+,R0            Store indirect and postincrement
+00000086:   018D        MOVW    R16,R26          Copy register pair
11:           case 7:        *to++ = *from++;
+00000087:   01F9        MOVW    R30,R18          Copy register pair
+00000088:   01D8        MOVW    R26,R16          Copy register pair
+00000089:   9001        LD      R0,Z+            Load indirect and postincrement
+0000008A:   019F        MOVW    R18,R30          Copy register pair
+0000008B:   920D        ST      X+,R0            Store indirect and postincrement
+0000008C:   018D        MOVW    R16,R26          Copy register pair
12:           case 6:        *to++ = *from++;
+0000008D:   01F9        MOVW    R30,R18          Copy register pair
+0000008E:   01D8        MOVW    R26,R16          Copy register pair
+0000008F:   9001        LD      R0,Z+            Load indirect and postincrement
+00000090:   019F        MOVW    R18,R30          Copy register pair
+00000091:   920D        ST      X+,R0            Store indirect and postincrement
+00000092:   018D        MOVW    R16,R26          Copy register pair
13:           case 5:        *to++ = *from++;
+00000093:   01F9        MOVW    R30,R18          Copy register pair
+00000094:   01D8        MOVW    R26,R16          Copy register pair
+00000095:   9001        LD      R0,Z+            Load indirect and postincrement
+00000096:   019F        MOVW    R18,R30          Copy register pair
+00000097:   920D        ST      X+,R0            Store indirect and postincrement
+00000098:   018D        MOVW    R16,R26          Copy register pair
14:           case 4:        *to++ = *from++;
+00000099:   01F9        MOVW    R30,R18          Copy register pair
+0000009A:   01D8        MOVW    R26,R16          Copy register pair
+0000009B:   9001        LD      R0,Z+            Load indirect and postincrement
+0000009C:   019F        MOVW    R18,R30          Copy register pair
+0000009D:   920D        ST      X+,R0            Store indirect and postincrement
+0000009E:   018D        MOVW    R16,R26          Copy register pair
15:           case 3:        *to++ = *from++;
+0000009F:   01F9        MOVW    R30,R18          Copy register pair
+000000A0:   01D8        MOVW    R26,R16          Copy register pair
+000000A1:   9001        LD      R0,Z+            Load indirect and postincrement
+000000A2:   019F        MOVW  &nb
58楼: >>参与讨论
农民讲习所
在GCC上快,不代表其它的快。所以还是标准库好,放之四海。
 
59楼: >>参与讨论
农民讲习所
用汇编写的16字节的空间换时间,是不是更快:)
#include <iom8v.h>
#include <string.h>

void fast_memcpy (unsigned CHAR *to, unsigned CHAR *from, unsigned int count)
{
    unsigned int n = (count + 7) / 8;    /* count > 0 assumed */
    unsigned CHAR c = count % 8;
    
    SWITCH (c)
    {
    case 0:       do { *to++ = *from++;
    case 7:        *to++ = *from++;
    case 6:        *to++ = *from++;
    case 5:        *to++ = *from++;
    case 4:        *to++ = *from++;
    case 3:        *to++ = *from++;
    case 2:        *to++ = *from++;
    case 1:        *to++ = *from++;
              } while (--n > 0);
    }
}

void c_memcpy (unsigned CHAR *to, unsigned CHAR *from, unsigned int count)
{
    while (count--)
    {
        *to++ = *from++;
        
    }
}

void asm_memcpy_16bytes(unsigned CHAR *to, unsigned CHAR *from, unsigned int count)
{
    asm("LDD     R24,Y+0  ");
    asm("LDD     R25,Y+1  ");
    
    asm("MOVW    R26,R16  ");
    asm("MOVW    R30,R18   ");    
    
    
    //取得余数:只取最低4位
    asm("MOV     R17,R24  ");
    asm("ANDI    R17,0x0F ");
    
    //调整/16后的计数器,整数右移四位
    asm("LSR     R25      ");
    asm("ROR     R24      ");
    asm("LSR     R25      ");
    asm("ROR     R24      ");
    asm("LSR     R25      ");
    asm("ROR     R24      ");
    asm("LSR     R25      ");
    asm("ROR     R24      ");

    //根据余数进行循环处理掉
asm("asm_memcpy_PreProc:");
    asm("TST     R17      ");
    asm("BREQ    asm_memcpy_16");
    
    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");
    
    asm("DEC     R17");
    asm("RJMP    asm_memcpy_PreProc");
    
    //存储,整数
asm("asm_memcpy_16:");
    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");
    
    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    &qu
60楼: >>参与讨论
zsmbj
看来讲习所也承认这个算法会产生更快的速度。
呵呵,不过你相当于把那个K给选择了16。我把C也调整到了16:

void fast_memcpy (unsigned CHAR *to, unsigned CHAR *from, unsigned int count)
{
    unsigned int n = (count + 15) / 16;    /* count > 0 assumed */
    unsigned CHAR c = count % 16;
    SWITCH (c)
    {
    case 0:       do { *to++ = *from++;
    case 15:        *to++ = *from++;
    case 14:        *to++ = *from++;
    case 13:        *to++ = *from++;
    case 12:        *to++ = *from++;
    case 11:        *to++ = *from++;
    case 10:        *to++ = *from++;
    case 9:        *to++ = *from++;
    case 8:        *to++ = *from++;
    case 7:        *to++ = *from++;
    case 6:        *to++ = *from++;
    case 5:        *to++ = *from++;
    case 4:        *to++ = *from++;
    case 3:        *to++ = *from++;
    case 2:        *to++ = *from++;
    case 1:        *to++ = *from++;
              } while (--n > 0);
    }
}

结果gcc编译运行时间为441.75us。仅比汇编多8us,看来我们不得不佩服gcc啊!

当K=8时,运行时间为465.5us。看来K=16速度增加的有限。而代码会减少。实际使用会合理的选择K。


61楼: >>参与讨论
农民讲习所
俺好象没有否认算法吧,是认为汇编更好
和这种依赖编译器的写法相比,汇编更好。

62楼: >>参与讨论
John_Lee
看了半天,本不想再说,但实在憋不住了...
所长,“俺好象没有否认算法吧,是认为汇编更好”,你觉得这话它挨得上吗?

“算法”vs“汇编”!!!但愿是你一时情急,说走嘴了。

我还是那句话,算法之间的比较要用同一语言实现才有可比性。

再者,我觉得这个帖子主要是在讨论“算法”,能抛砖引玉,希望可以让初学者多接触一些理论,而不想纠缠于编程语言等无关的东西。

顶撞之处,请多包涵。

* - 本贴最后修改时间:2005-9-13 21:23:06 修改者:John_Lee

63楼: >>参与讨论
zsmbj
呵呵,John_Lee 都急了。所长再看看John_Lee 前面的帖子说的吧:
Q1:“空间换时间”为什么不使用汇编?

A1:我才说了:这与“软件系统设计”或“编程语言”无关。我们当然是用同样的编程语言来比较!否则没有意义。我也可以用汇编来写 fast_memcpy !

所长的,“俺好象没有否认算法吧,是认为汇编更好”,好似关公战秦琼。


64楼: >>参与讨论
农民讲习所
这种C写法也非常不好,以来技巧。看俺改改为标准写法。:)
 
65楼: >>参与讨论
农民讲习所
改为标C写法的程序
#include <iom128v.h>
#include <string.h>

void fast_memcpy (unsigned CHAR *to, unsigned CHAR *from, unsigned int count)
{
    unsigned CHAR i,j;
    
    //处理/16后的余数
    j = (unsigned CHAR)count & 0xf;
    for( i=0; i<j; i++)
        *to++ = *from++;

    count >>= 4;

    while( count-- )
    {
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;

        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
        *to++ = *from++;
    }
}

void c_memcpy (unsigned CHAR *to, unsigned CHAR *from, unsigned int count)
{
    while (count--)
    {
        *to++ = *from++;
        
    }
}

void asm_memcpy_16bytes(unsigned CHAR *to, unsigned CHAR *from, unsigned int count)
{
    asm("LDD     R24,Y+0  ");
    asm("LDD     R25,Y+1  ");
    
    asm("MOVW    R26,R16  ");
    asm("MOVW    R30,R18   ");    
    
    
    //取得余数:只取最低4位
    asm("MOV     R17,R24  ");
    asm("ANDI    R17,0x0F ");
    
    //调整/16后的计数器,整数右移四位
    asm("LSR     R25      ");
    asm("ROR     R24      ");
    asm("LSR     R25      ");
    asm("ROR     R24      ");
    asm("LSR     R25      ");
    asm("ROR     R24      ");
    asm("LSR     R25      ");
    asm("ROR     R24      ");

    //根据余数进行循环处理掉
asm("asm_memcpy_PreProc:");
    asm("TST     R17      ");
    asm("BREQ    asm_memcpy_16");
    
    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");
    
    asm("DEC     R17");
    asm("RJMP    asm_memcpy_PreProc");
    
    //存储,整数
asm("asm_memcpy_16:");
    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");
    
    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

    asm("LD      R0,Z+    ");
    asm("ST      X+,R0    ");

&
66楼: >>参与讨论
农民讲习所
是不是好看非常多?有那个必要写得大家都看不懂吗?
既然这里用算法追求速度,俺就是认为绝对该使用汇编了。因为C体现的速度有限和局限。

当初开始帖子争论的不是算法,而是书写的写法,所以起点好象不对哦。:)

* - 本贴最后修改时间:2005-9-14 8:53:30 修改者:农民讲习所

67楼: >>参与讨论
促狭鬼
再说一句
这个帖子不仅在算法上起到了抛砖引玉的作用,还有看懂后引起的心里震撼!“但这段代码对C关键词的运用从根本上改变了俺对自己和C的认识:”
个人认为它的书写很简洁,还有它使人对C的博大精深有了更进一步的认识。
我想楼主发这个帖的初衷,也一定是站在对C语言的运用与掌握上来说的:)


* - 本贴最后修改时间:2005-9-14 11:17:36 修改者:促狭鬼

68楼: >>参与讨论
农民讲习所
写的人人看得懂才是至高境界。这种技巧,俺不喜欢。
 
69楼: >>参与讨论
农民讲习所
最多一个评价:这段代码好玩。
 
70楼: >>参与讨论
zsmbj
所长修改的C代码还有一个问题,就是当余数为0时,比较好。
当余数为0时,你的处理余数的 for循环没有用到跳过了,而如果余数不为0,比如说刚好为15,那么实际这个余数的for循环就要运行15次。这里还是有一个循环的效率问题。
所以还是把余数分配给swtich语句,根据余数的大小直接跳转到下面的语句是最好的方案。



71楼: >>参与讨论
农民讲习所
这余数几次的循环还追究效率,是不是该考虑汇编了?:)
还是老话。

72楼: >>参与讨论
ipman
终于看完全文,让我等菜鸟从云里雾里走向五体投地。
所长在讨论高效率时需要使用什么工具;
楼主本意可能是在讨论算法;
倒都没错。

73楼: >>参与讨论
kanprin
呵呵,赶紧收入囊中, 谢谢大虾们了!!
 
74楼: >>参与讨论
lag3631
不懂
是局部程序么?
怎么看不明白干嘛用的?

75楼: >>参与讨论
sillboy

 
76楼: >>参与讨论
局外人
感觉所长有点理屈词穷的感觉,楼主的本意是:讨论在同一种语言下的算法执行效率。感觉只要一提及少率,所长就往汇编上引,有点强词夺理。。。。
参与讨论
昵称:
讨论内容:
 
 
相关帖子
新手求助:建立AT89C5131最小系统问题
关于AVR单片机的两个问题
关于ATMEGA162的小问题
帮我看看,以下程序哪出了问题.
6295加27C020害得我好掺啊!
免费注册为维库电子开发网会员,参与电子工程师社区讨论,点此进入


Copyright © 1998-2006 www.dzsc.com 浙ICP证030469号