【问题标题】:Do comments slow down an interpreted language?注释会减慢解释语言的速度吗?
【发布时间】:2011-02-13 10:23:40
【问题描述】:

我之所以这么问是因为我使用 Python,但它也适用于其他解释语言(Ruby、PHP、JavaScript)。

每当我在代码中留下评论时,我是否会减慢解释器的速度?根据我对解释器的有限理解,它将程序表达式作为字符串读取,然后将这些字符串转换为代码。好像每次解析评论都是浪费时间。

是这样吗?解释语言中的 cmets 是否有一些约定,或者效果可以忽略不计?

【问题讨论】:

  • 这肯定是我的旧 Commodore 64 上的 BASIC 中的一个问题。从那时起,语言和硬件都得到了显着改进。
  • 您应该知道,“解释”一词没有多大意义。 Python 是字节码编译的,而不是直接从源代码解释。
  • 在这个问题上考虑 JavaScript 可能会很有趣。例如,我相信 JQuery 的版本去掉了 cmets 和额外的空格以最小化传输时间。
  • 剥离 cmets 和空格(并尽可能地将东西处理在一起)在 JavaScript 中很常见,但并不是为了加速解析或执行;这一切都与网络传输时间(和带宽,对于繁忙的网站)有关。
  • 例如google.com/index.html 的来源实际上被混淆了,因为 Google 已将每个 JS 变量压缩到最多 3 个字母,并删除了所有可能的空格。

标签: python comments interpreter interpreted-language


【解决方案1】:

注释通常在解析阶段或之前被剥离,并且解析非常快,因此有效地 cmets 不会减慢初始化时间。

【讨论】:

  • 必须去掉注释,所以如果 cmets 足够大,它们会减慢程序的速度。但是你必须拥有巨大的 cmets(MB?GB?)才能测量它。
  • 拥有兆字节的 cmets 意味着有超过兆字节的代码。实际解析和编译的时间会超过“很少”的注释剥离时间。
  • 我继续尝试了这个。在我的特定测试系统中,解析和执行大约 10 兆的 Python cmets(和一个赋值语句)需要 349 毫秒。在这种情况下,源字节与时间的比率似乎相当稳定,大约为每毫秒 28,000 字节。键盘上的相同脚本(正如我想象的那样)更慢:codepad.org/Ckevfqmq
  • 嗯,我相信我们可以构建一个相反的病态例子。哦,看,看看 Rich Bradshaw 的答案。当然,出于所有实际目的,您是完全正确的。
【解决方案2】:

对于日常使用,效果可以忽略不计。它很容易测试,但如果您考虑一个简单的循环,例如:

For N = 1 To 100000: Next

您的计算机可以比您眨眼更快地处理(数到 100,000)。忽略以某个字符开头的一行文本会快 10,000 倍以上。

别担心。

【讨论】:

    【解决方案3】:

    拥有 cmets 会减慢启动时间,因为脚本将被解析为可执行形式。但是,在大多数情况下,cmets 不会减慢运行时间。

    另外,在 python 中,您可以将 .py 文件编译成 .pyc,其中不包含 cmets(我希望如此)——这意味着如果脚本已经编译,您也不会获得启动命中。

    【讨论】:

    • s/will slow down the startup time/will slow down the startup time immeasurablys/in most cases comments don't slow down runtime/in all cases comments don't slow down runtime
    【解决方案4】:

    对于 Python,源文件在执行之前会被编译(.pyc 文件),并且在此过程中会剥离 cmets。因此,如果您有大量 cmets,可能会减慢编译时间,但不会影响执行时间。

    【讨论】:

    • +1,因为我真的很喜欢 gazillion 在这种情况下使用
    • 很难想象注释:代码比率必须有多高才能检测到。
    • @Mike: 可能 1 gazillion:1 ?
    • 不太确定多个 gazillions,但我认为您的想法是正确的。
    • 我只是注意到即使编译时间也只发生一次,然后被缓存。
    【解决方案5】:

    好吧,我写了一个这样的简短python程序:

    for i in range (1,1000000):
        a = i*10
    

    这个想法是,做一个简单的计算负载次数。

    通过计时,运行时间为 0.35±0.01 秒。

    然后我用这样插入的整本英王钦定版圣经重写了它:

    for i in range (1,1000000):
        """
    The Old Testament of the King James Version of the Bible
    
    The First Book of Moses:  Called Genesis
    
    
    1:1 In the beginning God created the heaven and the earth.
    
    1:2 And the earth was without form, and void; and darkness was upon
    the face of the deep. And the Spirit of God moved upon the face of the
    waters.
    
    1:3 And God said, Let there be light: and there was light.
    
    ...
    ...
    ...
    ...
    
    Even so, come, Lord Jesus.
    
    22:21 The grace of our Lord Jesus Christ be with you all. Amen.
        """
        a = i*10
    

    这次运行耗时 0.4±0.05 秒。

    所以答案是是的。循环中 4MB 的 cmets 产生了可衡量的差异。

    【讨论】:

    • +1 表示科学实验和同一帖子中的圣经。 8vD
    • 这不是评论。这是一个字符串文字。此外,如果您查看两个代码块的实际字节码,您会发现没有区别。字符串被解析一次,根本不参与计算。如果将字符串放在循环之外,您应该会看到同样的减速。
    • +1 以对抗愚蠢的反对票,以及实际实验的道具,尽管方法存在缺陷。 TIAS(试试看)通常比抽象讨论提供更好的答案。
    • @David,这个测试的案例不是 OP 所描述的,也不代表人们实际编写的任何代码。
    • @Rich,你能把字符串转换成评论并发布新的时间吗?
    【解决方案6】:

    这取决于解释器是如何实现的。大多数合理的现代解释器在任何实际执行之前至少对源代码进行一些预处理,这将包括剥离 cmets,以便从那时起它们不再有任何区别。

    曾经,当内存受到严重限制时(例如,64K 总可寻址内存和用于存储的盒式磁带),您不能认为这样的事情是理所当然的。在 Apple II、Commodore PET、TRS-80 等时代,程序员显式删除 cmets(甚至空白)以提高执行速度是相当常规的。这也只是当时经常使用的众多源代码级黑客攻击之一1

    当然,这些机器的 CPU 一次只能执行一条指令,时钟速度约为 1 MHz,并且只有 8 位处理器寄存器,这也有帮助。即使是你现在只能在垃圾箱里找到的机器也比那些机器快得多,以至于它甚至都不好笑......


    1. 再举一个例子,在 Applesoft 中,您可能会获得或损失一点速度,具体取决于您对行进行编号的方式。如果没记错的话,速度增益是当 goto 语句的目标是 16 的倍数时。

    【讨论】:

      【解决方案7】:

      我对 解释器是它读取程序 作为字符串和转换的表达式 将这些字符串转换为代码。


      大多数解释器读取文件中的文本(代码)并生成抽象语法树数据结构,因为它可以在编译的下一阶段轻松读取。 该结构不包含文本形式的代码,当然也不包含 cmets。仅仅这棵树就足以执行程序。但是解释器出于效率原因,会更进一步并生成字节码。而 Python 正是这样做的。

      我们可以说,您编写的代码和 cmets 只是不存在
      当程序运行时。所以不,cmets 不会在运行时减慢程序的速度。



      注意:解释器不使用其他内部结构来表示文本以外的代码,
      即语法树,必须完全按照您提到的那样做。在运行时反复解释代码。

      【讨论】:

        【解决方案8】:

        用一些 cmets 编写了一个像 Rich's 这样的脚本(只有大约 500kb 文本):

        # -*- coding: iso-8859-15 -*-
        import timeit
        
        no_comments = """
        a = 30
        b = 40
        for i in range(10):
            c = a**i * b**i
        """
        yes_comment = """
        a = 30
        b = 40
        
        # full HTML from http://en.wikipedia.org/
        # wiki/Line_of_succession_to_the_British_throne
        
        for i in range(10):
            c = a**i * b**i
        """
        loopcomment = """
        a = 30
        b = 40
        
        for i in range(10):
            # full HTML from http://en.wikipedia.org/
            # wiki/Line_of_succession_to_the_British_throne
        
            c = a**i * b**i
        """
        
        t_n = timeit.Timer(stmt=no_comments)
        t_y = timeit.Timer(stmt=yes_comment)
        t_l = timeit.Timer(stmt=loopcomment)
        
        print "Uncommented block takes %.2f usec/pass" % (
            1e6 * t_n.timeit(number=100000)/1e5)
        print "Commented block takes %.2f usec/pass" % (
            1e6 * t_y.timeit(number=100000)/1e5)
        print "Commented block (in loop) takes %.2f usec/pass" % (
            1e6 * t_l.timeit(number=100000)/1e5)
        


        C:\Scripts>timecomment.py
        Uncommented block takes 15.44 usec/pass
        Commented block takes 15.38 usec/pass
        Commented block (in loop) takes 15.57 usec/pass
        
        C:\Scripts>timecomment.py
        Uncommented block takes 15.10 usec/pass
        Commented block takes 14.99 usec/pass
        Commented block (in loop) takes 14.95 usec/pass
        
        C:\Scripts>timecomment.py
        Uncommented block takes 15.52 usec/pass
        Commented block takes 15.42 usec/pass
        Commented block (in loop) takes 15.45 usec/pass
        

        根据大卫的评论进行编辑:

         -*- coding: iso-8859-15 -*-
        import timeit
        
        init = "a = 30\nb = 40\n"
        for_ = "for i in range(10):"
        loop = "%sc = a**%s * b**%s"
        historylesson = """
        # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
        # blah blah...
        # --></body></html> 
        """
        tabhistorylesson = """
            # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
            # blah blah...
            # --></body></html> 
        """
        
        s_looped = init + "\n" + for_ + "\n" + tabhistorylesson + loop % ('   ','i','i')
        s_unroll = init + "\n"
        for i in range(10):
            s_unroll += historylesson + "\n" + loop % ('',i,i) + "\n"
        t_looped = timeit.Timer(stmt=s_looped)
        t_unroll = timeit.Timer(stmt=s_unroll)
        
        print "Looped length: %i, unrolled: %i." % (len(s_looped), len(s_unroll))
        
        print "For block takes %.2f usec/pass" % (
            1e6 * t_looped.timeit(number=100000)/1e5)
        print "Unrolled it takes %.2f usec/pass" % (
            1e6 * t_unroll.timeit(number=100000)/1e5)
        


        C:\Scripts>timecomment_unroll.py
        Looped length: 623604, unrolled: 5881926.
        For block takes 15.12 usec/pass
        Unrolled it takes 14.21 usec/pass
        
        C:\Scripts>timecomment_unroll.py
        Looped length: 623604, unrolled: 5881926.
        For block takes 15.43 usec/pass
        Unrolled it takes 14.63 usec/pass
        
        C:\Scripts>timecomment_unroll.py
        Looped length: 623604, unrolled: 5881926.
        For block takes 15.10 usec/pass
        Unrolled it takes 14.22 usec/pass
        

        【讨论】:

        • @Nick,我希望任何非天真的解释器都只解析第一次通过循环的 cmets。您是否尝试过展开循环,或者在代码中粘贴几百行 cmets?
        【解决方案9】:

        正如其他答案已经说明的那样,像 Python 这样的现代解释语言首先会解析源代码并将其编译为字节码,然后解析器会简单地忽略 cmets。这显然意味着任何速度损失只会在启动时实际解析源时发生。

        因为解析器忽略了cmets,所以编译阶段基本上不受你放入的任何cmets的影响。但是cmets本身的字节实际上是被读入的,然后在解析的时候就跳过了。这意味着,如果你有大量的 cmets(例如数百兆字节),这会减慢解释器的速度。但话又说回来,这也会减慢任何编译器。

        【讨论】:

        • 我不确定我是否将其称为最严格意义上的“解释性语言”。动态编译或 JIT 之类的东西似乎更合适。
        【解决方案10】:

        我想知道如何使用 cmets 是否重要。例如,三引号是一个文档字符串。如果您使用它们,则内容将被验证。不久前,我在将库导入 Python 3 代码时遇到了一个问题……我在 \N 上遇到了这个关于语法的错误。我查看了行号,它是三引号注释中的内容。我有些惊讶。 Python 新手,我从没想过块注释会被解释为语法错误。

        只要你输入:

        '''
        (i.e. \Device\NPF_..)
        '''
        

        Python 2 不会抛出错误,但 Python 3 会报告: SyntaxError: (unicode error) 'unicodeescape' codec can't decode bytes in position 14-15: malformed \N character escape

        所以 Python 3 显然是在解释三引号,确保它是有效的语法。

        但是,如果变成单行注释:#(即\Device\NPF_..)
        没有错误结果。

        我想知道是否将三引号 cmets 替换为单行,是否会看到性能变化。

        【讨论】:

          【解决方案11】:

          这个问题真的很老了,但是在阅读了声称它不会影响执行时间的公认答案之后,这是错误的,我给你一个简单的例子,你可以看到并检查它影响执行的数量确实是时间。
          我有一个名为constants.py 的文件。它包含一个列表中所有不同的国际象棋动作:

          LABELS = [ "a1b1"
              "a1c1", 
              "a1d1", 
              "a1e1", 
              "a1f1",....]
          

          LABELS 列表包含 2272 个元素。在我调用的另一个文件中:

          import constants
          np.array(constants.LABELS)
          

          我测量了十次,代码的执行大约需要 0.597 毫秒。 现在我更改了文件并在每个元素旁边插入了一条评论(2272 次):

          LABELS = [ "a1b1",  # 0 
                      "a1c1", # 1
                      "a1d1", # 2
                      "a1e1", # 3
                      "a1f1", # 4
                       ...,
                      "Q@h8", # 2271]
          

          现在在测量np.array(constants.LABELS) 的执行时间十次后,我的平均执行时间为 4.28 毫秒,因此大约慢了 7 倍。
          因此,是的,如果您有很多 cmets,它会影响执行时间。

          【讨论】:

          • “测试 np.array(constants.LABELS)”到底是什么意思?您看到编译后的 .pyc 文件有什么不同吗?
          • @LuperRouch with "testing np.array(constants.LABELS)" 我的意思是运行语句np.array(constant.LABELS) 十次并测量语句的平均执行时间。我会在正文中说明这一点。
          • 如何运行此语句?也许您可以将您的测试设置推送到 github,以便我们可以看到您运行测试的准确程度,因为您看到的差异可能是由于您没有重用已编译的 .pyc 文件(正如我所说,cmets 确实会影响编译时间,但它们不应影响执行时间)。
          猜你喜欢
          • 2016-10-08
          • 2012-05-16
          • 1970-01-01
          • 2020-04-24
          • 2015-12-28
          • 1970-01-01
          • 1970-01-01
          • 2015-07-21
          • 2016-10-16
          相关资源
          最近更新 更多